rubyhexagon 0.4.0 → 0.4.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 34c8430199af7c9d7c8e2efb98cf8da90cbcdf09
4
+ data.tar.gz: 5675d205567c98595d52b5651eefe4fdd99f5dc6
5
+ SHA512:
6
+ metadata.gz: 4907ea016fa1d66489323f71cf10ed2ff9bd07a251f7462c6d7c09d8820115477ae9f5378431b583724ce81d9866fe6626db07cbc2947c94a1e05a94168e7738
7
+ data.tar.gz: 6aa265a2deb00c532ec2fcc0d7ac60a79fea98cd5b237f5c628ea1846dc91ccab034cb045dc121e3db9bb5543bd878eb4c08d7aad0e0b69f3efed6237c37237a
data/lib/api.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  =begin
2
- Copyright 2014 Maxine Red <maxine_red1@yahoo.com>
2
+ Copyright 2014, 2015 Maxine Red <maxine@furfind.net>
3
3
 
4
4
  This file is part of rubyhexagon.
5
5
 
@@ -20,104 +20,60 @@
20
20
  require "standard/http"
21
21
 
22
22
  module E621
23
- attr_reader :user
24
23
  class API
25
- # Variable mod is the module (Post, Pool, Set,...) this API is called for.
26
- def initialize(mod=nil)
24
+ def initialize(user=nil,pass=nil)
27
25
  @http = HTTP.new
28
- @mod = mod
29
- @user = user_login
26
+ login(user,pass) if !defined? @@login
27
+ end
28
+ # A wrapper function to fit into the general theme better.
29
+ def post_list(*args)
30
+ post_index(*args)
30
31
  end
31
32
  # Send commands to API and parse all answers, even errors.
32
33
  # Also make it work with POST and GET requests.
33
34
  def method_missing(method,*args)
34
- if [:get,:post].include?(method) then
35
- action,request,cookie = args
36
- r,tries = request2body(request,cookie),0
37
- begin
38
- json = @http.__send__(method,"/#@mod/#{action}.json",r)
39
- rescue Timeout::Error
40
- sleep 2**tries
41
- tries += 1
42
- #E621.log.debug("#@mod/#{action} failed: #{e.class}")
43
- raise if tries >= 4
44
- # if we see more than 4 timeout errors, then there
45
- # is something wrong
46
- retry
47
- end
48
- #raise APIError,json["reason"] if json.include?("success") && !json["success"]
49
- return json
50
- else
51
- raise NoMethodError, "undefined method #{method} for #{self.class}"
52
- end
35
+ request = request2body(args.first)
36
+ method = method.to_s.split("_")
37
+ m = case method[0]
38
+ when "post" then
39
+ case method[1]
40
+ when "show" then :get
41
+ else :post
42
+ end
43
+ else :post
44
+ end
45
+ @http.__send__(m,"/#{method.join("/")}.json",request)
53
46
  end
54
47
  private
55
48
  # This helper function provides the functionality of translating Ruby Hashes
56
49
  # into HTTP POST body strings.
57
- def request2body(r,c)
58
- s = String.new
59
- r.each do |k,v|
60
- s += "&" unless s == String.new
50
+ def request2body(request)
51
+ query = @@login ? "#@@login&" : String.new
52
+ request = request.map do |k,v|
61
53
  if v.is_a?(Hash) then
62
- t = String.new
63
- v.each do |e,a|
64
- t += "&" unless t == String.new
65
- t += "#{k}[#{e}]=#{a}"
66
- end
67
- s += t
68
- else
69
- s += "#{k}=#{v}"
54
+ raise Argumenterror, "Hashes as request values aren't allowed."
55
+ elsif v.is_a?(Array) then
56
+ v = v.join(",")
70
57
  end
58
+ "#{k}=#{v}"
71
59
  end
72
- s += "&#@@login" if !c
73
- return s
60
+ query += request.join("&")
61
+ return query
74
62
  end
75
63
  # Perform an API and site login. This function needs to be called once
76
64
  # before any actual API call can be made.
77
- def user_login
78
- # Cookie login gets eliminated for now.
79
- name,pass = String.new, String.new
80
- # Perform a re-login if the last time is older than x days. Or if there is
81
- # no cookie.
82
- passwd = File.expand_path(Config.paths["passwd"])
83
- if File.exist?(passwd) then
84
- File.open(passwd){|f|@passwd=f.read.parse}
65
+ def login(user,pass)
66
+ if user == nil && pass == nil then
67
+ @@login = nil
85
68
  else
86
- @passwd = {"name"=>"","pass"=>"","last_login"=>0}
87
- end
88
- if (Time.now.to_i-@passwd["last_login"].to_i) < 3.days then
89
- @@login = @passwd["login"]
90
- else
91
- if @passwd["name"] != "" && @passwd["pass"] != "" then
92
- name,pass = @passwd["name"],@passwd["pass"]
93
- else
94
- puts "No user data found. Please provide user data."
95
- name,pass = get_credentials
96
- @passwd["name"],@passwd["pass"] = name,pass
97
- # Store that data for later use.
98
- end
99
- request = "name=#{name}&password=#{pass}"
69
+ request = {name:name,password:pass}
100
70
  body = @http.get("/user/login.json",request)
101
71
  if body.has_key?("success") && (!body["success"] || body["success"] == "failed") then
102
72
  raise AuthentificationError, "Username or password wrong!"
103
73
  else
104
74
  @@login = "login=#{body["name"]}&password_hash=#{body["password_hash"]}"
105
- @passwd["login"] = @@login # Save login string for later use.
106
75
  end
107
- @passwd["last_login"] = Time.now.to_i
108
- # Write everything back!
109
- File.open(passwd,"w"){|f|f.print @passwd.to_json}
110
76
  end
111
- return @passwd["name"]
112
- end
113
- # If there is no data saved for login, ask the user. They must know!
114
- def get_credentials
115
- print "Username: "
116
- name = $stdin.gets.chomp
117
- print "Password: "
118
- pass = `stty_o=$(stty -g);stty -echo;read pass;stty $stty_o; echo $pass`.chomp
119
- puts
120
- return [name,pass]
121
77
  end
122
78
  end
123
79
  end
@@ -1,5 +1,5 @@
1
1
  =begin
2
- Copyright 2014 Maxine Red <maxine_red1@yahoo.com>
2
+ Copyright 2014, 2015 Maxine Red <maxine@furfind.net>
3
3
 
4
4
  This file is part of rubyhexagon.
5
5
 
@@ -20,7 +20,7 @@
20
20
  module E621
21
21
  class Container
22
22
  def method_missing(method)
23
- if instance_variables.include?("@#{method}".to_sym) then
23
+ if instance_variables.include?("@#{method}".to_sym) && method != :api then
24
24
  return instance_variable_get("@#{method}")
25
25
  else
26
26
  raise NoMethodError, "undefined method #{method} for #{self.class}"
@@ -1,5 +1,5 @@
1
1
  =begin
2
- Copyright 2014 Maxine Red <maxine_red1@yahoo.com>
2
+ Copyright 2014, 2015 Maxine Red <maxine@furfind.net>
3
3
 
4
4
  This file is part of rubyhexagon.
5
5
 
@@ -1,5 +1,5 @@
1
1
  =begin
2
- Copyright 2014 Maxine Red <maxine_red1@yahoo.com>
2
+ Copyright 2014, 2015 Maxine Red <maxine@furfind.net>
3
3
 
4
4
  This file is part of rubyhexagon.
5
5
 
@@ -22,90 +22,29 @@ require "digest/md5"
22
22
  module E621
23
23
  class Post < Container
24
24
  def initialize(post)
25
- set_variables(post)
26
- end
27
-
28
- def download_data
29
- host,port = @file_url.sub(%r{^.+?//},"").sub(%r{/.+$},""),@file_url.match(/^https/) ? 443 : 80
30
- http = HTTP.new(host,port)
31
- body = String.new
32
- while Digest::MD5.hexdigest(body) != @md5 do
33
- body = http.get(@file_url.sub(/^.+?\.net/,""),"")["body"]
34
- end
35
- return body
36
- end
37
-
38
- def download(where)
39
- if @file_url then
40
- host,port = @file_url.sub(%r{^.+?//},"").sub(%r{/.+$},""),@file_url.match(/^https/) ? 443 : 80
25
+ @api = API.new
26
+ if post.is_a?(Fixnum) || (post.is_a?(String) && post.match(/^[0-9,a-f]+$/i)) then
27
+ post = if post.is_a?(String) && post.length == 32 then {md5:post}
28
+ elsif post.is_a?(Fixnum) || (post.is_a?(String) && post.match(/^\d+$/)) then
29
+ {id:post.to_i}
30
+ else
31
+ raise ArgumentError, "Neither an ID nor a MD5 Hash."
32
+ end
33
+ post = @api.post_show(post)
34
+ elsif post.is_a?(Hash) then # Variable post is ready to use. Do nothing.
41
35
  else
42
- host,port = "static1.e621.net",443
43
- @file_url = "/data/#{@md5[0,2]}/#{@md5[2,2]}/#@md5.#@file_ext"
36
+ raise ArgumentError, "Post only accepts an id, a MD5 hashsum or a Ruby hash."
44
37
  end
45
- http = HTTP.new(host,port)
46
- if !File.exist?(where) then
47
- body = ""
48
- while Digest::MD5.hexdigest(body) != @md5 do
49
- body = http.get(@file_url.sub(/^.+?\.net/,""),"")["body"]
50
- end
51
- File.open(where,"w") do |f|
52
- f.print body
53
- end
54
- else
55
- md5 = String.new
56
- File.open(where,"r") do |f|
57
- md5 = Digest::MD5.hexdigest(f.read)
58
- end
59
- if md5 != @md5 then
60
- body = ""
61
- while Digest::MD5.hexdigest(body) != @md5 do
62
- body = http.get(@file_url.sub(/^.+?\.net/,""),"")["body"]
63
- end
64
- File.open(where,"w") do |f|
65
- f.print body
66
- end
67
- end
68
- end
69
- end
70
-
71
- def add_to_set(id)
72
- api = API.new("set")
73
- api.post("add_post",{"post_id"=>@id,"set_id"=>id})
74
- end
75
-
76
- def remove_from_set(id)
77
- api = API.new("set")
78
- api.post("remove_post",{"post_id"=>@id,"set_id"=>id})
79
- end
80
-
81
- def favorite
82
- api = API.new("favorite")
83
- api.post("create",{"id"=>@id})
84
- end
85
-
86
- def unfavorite
87
- api = API.new("favorite")
88
- api.post("destroy",{"id"=>@id})
89
- end
90
-
91
- def vote(dir)
92
- dir = if dir >= 0 then 1 elsif dir < 0 then -1 end
93
- api = API.new("post")
94
- api.post("vote",{"id"=>@id,"score"=>dir})
95
- end
96
-
97
- def keys
98
- return instance_variables.map{|i|i.to_s.sub("@","")}
38
+ set_variables(post)
99
39
  end
100
-
101
- def to_json(b=nil)
102
- json_hash = Hash.new
103
- instance_variables.each do |i|
104
- v = instance_variable_get(i)
105
- v = v.is_a?(Time) ? v.to_i : v
106
- json_hash.store(i.to_s.sub("@",""),v)
40
+ alias show initialize
41
+ # Cast a vote! Any value above 0 votes up, every value below 0 votes down.
42
+ # Zero itself causes an error.
43
+ def vote(direction)
44
+ if direction > 0 then @api.post_vote(1)
45
+ elsif direction < 0 then @api.post_vote(-1)
46
+ else raise ArgumentError, "Zero isn't allowed for voting results."
107
47
  end
108
- return json_hash.to_json
109
48
  end
110
49
  end
111
50
  end
@@ -1,5 +1,5 @@
1
1
  =begin
2
- Copyright 2014 Maxine Red <maxine_red1@yahoo.com>
2
+ Copyright 2014, 2015 Maxine Red <maxine@furfind.net>
3
3
 
4
4
  This file is part of rubyhexagon.
5
5
 
@@ -23,12 +23,11 @@ require "standard/error"
23
23
  require "standard/time"
24
24
 
25
25
  require "api"
26
- require "config"
27
26
  require "container"
28
27
  require "search"
29
28
  require "pool"
30
29
 
31
30
  module E621
32
31
  Name = "rubyhexagon"
33
- Version = "0.4.0"
32
+ Version = "0.4.1"
34
33
  end
@@ -1,5 +1,5 @@
1
1
  =begin
2
- Copyright 2014 Maxine Red <maxine_red1@yahoo.com>
2
+ Copyright 2014, 2015 Maxine Red <maxine@furfind.net>
3
3
 
4
4
  This file is part of rubyhexagon.
5
5
 
@@ -18,56 +18,64 @@
18
18
  =end
19
19
 
20
20
  require "post"
21
+ require "thread"
21
22
 
22
23
  module E621
23
- attr_reader :posts
24
24
  class Search
25
25
  # commands update/download
26
- def initialize(query)
27
- @api,@mt = API.new("post"),Mutex.new
28
- @bad_tags = /(\s|^)(#{Config.blacklist.map{|x|Regexp.escape(x)}.join("|")})(\s|$)/
29
- @request = {"limit"=>100,"page"=>1,"tags"=>query.join("+")}
30
- fetch
26
+ def initialize(query,page=1)
27
+ @api, @queue = API.new, SizedQueue.new(4096)
28
+ @request = {limit:100,page:page,tags:query.url_encode}
31
29
  end
32
30
 
33
- def next!
34
- @request["page"] += 1
35
- fetch
31
+ def next_page
32
+ @request[:page] += 1
36
33
  end
37
34
 
35
+ def previous_page
36
+ @request[:page] -= 1
37
+ end
38
+ # Return each post via a block and process them individually.
39
+ # This could cause a race condition in the unlikely manner when the queue is
40
+ # empty and not being filled again, but the worker is still runnning.
38
41
  def each_post
39
- while @posts != Array.new do
40
- @posts.each do |post|
42
+ fetch_all
43
+ until !@worker.status do
44
+ until @queue.empty? do
45
+ post = @queue.pop
41
46
  yield post
42
47
  end
43
- next!
44
48
  end
45
49
  end
46
50
 
47
- def page
48
- return @request["page"]
49
- end
50
-
51
- def page=(page)
52
- @request["page"]=page
51
+ def posts
53
52
  fetch
54
53
  end
55
54
 
56
- def previous!
57
- @request["page"] -= 1
58
- fetch
55
+ def page
56
+ return @request[:page]
59
57
  end
60
58
 
61
- def to_json(a=nil,b=nil)
62
- posts = @posts.map{|o|o.to_json}
63
- {"name"=>@name,"queries"=>@queries,"posts"=>posts}.to_json
59
+ def page=(page)
60
+ @request[:page] = page
64
61
  end
62
+
65
63
  private
66
64
  def fetch
67
- @posts = @api.post("index",@request).map do |post|
68
- #Post.new(post) unless post["tags"].match(@bad_tags) || post["score"] <= 30
69
- Post.new(post) unless post["tags"].match(@bad_tags)
65
+ @api.post_list(@request).map do |post|
66
+ Post.new(post)
70
67
  end.compact
71
68
  end
69
+ def fetch_all
70
+ @worker = Thread.new do
71
+ until (posts = @api.post_list(@request)) == Array.new do
72
+ posts.each do |post|
73
+ post = Post.new(post)
74
+ @queue << post if post
75
+ end
76
+ next_page
77
+ end
78
+ end
79
+ end
72
80
  end
73
81
  end
data/lib/set.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  =begin
2
- Copyright 2014 Maxine Red <maxine_red1@yahoo.com>
2
+ Copyright 2014, 2015 Maxine Red <maxine@furfind.net>
3
3
 
4
4
  This file is part of rubyhexagon.
5
5
 
@@ -1,5 +1,5 @@
1
1
  =begin
2
- Copyright 2014 Maxine Red <maxine_red1@yahoo.com>
2
+ Copyright 2014, 2015 Maxine Red <maxine@furfind.net>
3
3
 
4
4
  This file is part of rubyhexagon.
5
5
 
@@ -1,5 +1,5 @@
1
1
  =begin
2
- Copyright 2014 Maxine Red <maxine_red1@yahoo.com>
2
+ Copyright 2014, 2015 Maxine Red <maxine@furfind.net>
3
3
 
4
4
  This file is part of rubyhexagon.
5
5
 
@@ -1,5 +1,5 @@
1
1
  =begin
2
- Copyright 2014 Maxine Red <maxine_red1@yahoo.com>
2
+ Copyright 2014, 2015 Maxine Red <maxine@furfind.net>
3
3
 
4
4
  This file is part of rubyhexagon.
5
5
 
@@ -25,28 +25,25 @@ module E621
25
25
  @http = Net::HTTP.new(host,port)
26
26
  @http.use_ssl = true if port == 443
27
27
  end
28
- # Small wrapper function for post calls. This way a proper logging is
29
- # guaranteed.
30
- def post(url,request)
31
- return parse_body(@http.post(url,request))
32
- end
33
- # Small wrapper function for get calls. This way a proper logging is
34
- # guaranteed.
35
- def get(url,request)
36
- return parse_body(@http.get("#{url}?#{request}"))
37
- end
38
- # #geti gives an immediate response in the form of bytes downloaded. This
39
- # won't be included in the gems though.
40
- def geti(url,request)
41
- uri = "#{url}?#{request}"
42
- count = (@http.head(uri)["content-length"].to_i/1024.0).round(1)
43
- clength,message = count.to_s.length,String.new
44
- @http.get("#{url}?#{request}") do |body|
45
- message += body
46
- mlength = (message.length/1024.0).round(1)
47
- print "Downloaded #{" "*(clength-mlength.to_s.length)}#{mlength}/#{count}kB\r"
28
+ def method_missing(method, *args)
29
+ if [:get,:post].include?(method) then
30
+ tries,url,request = 0, *args
31
+ begin
32
+ response = case method
33
+ when :get then @http.get("#{url}?#{request}")
34
+ when :post then @http.post(url,request)
35
+ end
36
+ rescue
37
+ sleep 2**tries
38
+ tries += 1
39
+ raise if tries >= 8
40
+ retry
41
+ end
42
+ response = parse_body(response)
43
+ else
44
+ raise NoMethodError, "undefined method #{method} for #{self.class}"
48
45
  end
49
- return {"body"=>message}
46
+ return response
50
47
  end
51
48
  private
52
49
  # As every body gets processed the same, just put it into a seperate method.
@@ -64,18 +61,30 @@ module E621
64
61
  # Parse all code that returns not in JSON to actual JSON like hashes.
65
62
  def errorcode(code,url)
66
63
  body = {"success"=>false,"reason"=>""}
67
- body["reason"] = if code.match(/3\d{2}/) then "We got redirected!"
68
- elsif code.match(/403/) then "Access denied!"
69
- elsif code.match(/404/) then "File not found!"
70
- elsif code.match(/420/) then "Record could not be saved!"
71
- elsif code.match(/421/) then "User it throttled, try again later!"
72
- elsif code.match(/422/) then "The resource is locked!"
73
- elsif code.match(/423/) then "Resource already exists!"
74
- elsif code.match(/4\d{2}/) then "We made a bad request!"
75
- elsif code.match(/500/) then "Internal server error!"
76
- elsif code.match(/503/) then "Server has too much load!"
77
- elsif code.match(/5\d{2}/) then "Some unkown server side error!"
78
- else "HTTP response code out of range!!"
64
+ body["reason"] = if code.match(/3\d{2}/) then
65
+ "We got redirected!"
66
+ elsif code.match(/403/) then
67
+ "Access denied!"
68
+ elsif code.match(/404/) then
69
+ "File not found!"
70
+ elsif code.match(/420/) then
71
+ "Record could not be saved!"
72
+ elsif code.match(/421/) then
73
+ "User is throttled, try again later!"
74
+ elsif code.match(/422/) then
75
+ "The resource is locked!"
76
+ elsif code.match(/423/) then
77
+ "Resource already exists!"
78
+ elsif code.match(/4\d{2}/) then
79
+ "We made a bad request!"
80
+ elsif code.match(/500/) then
81
+ "Internal server error!"
82
+ elsif code.match(/503/) then
83
+ "Server has too much load!"
84
+ elsif code.match(/5\d{2}/) then
85
+ "Some unkown server side error!"
86
+ else
87
+ "HTTP response code out of range!!"
79
88
  end
80
89
  return body
81
90
  end
@@ -1,5 +1,5 @@
1
1
  =begin
2
- Copyright 2014 Maxine Red <maxine_red1@yahoo.com>
2
+ Copyright 2014, 2015 Maxine Red <maxine@furfind.net>
3
3
 
4
4
  This file is part of rubyhexagon.
5
5
 
@@ -1,5 +1,5 @@
1
1
  =begin
2
- Copyright 2014 Maxine Red <maxine_red1@yahoo.com>
2
+ Copyright 2014, 2015 Maxine Red <maxine@furfind.net>
3
3
 
4
4
  This file is part of rubyhexagon.
5
5
 
@@ -18,6 +18,7 @@
18
18
  =end
19
19
 
20
20
  require "json"
21
+ require "cgi"
21
22
 
22
23
  class String
23
24
  # Make json parsing a lot easier and less to write.
@@ -27,6 +28,9 @@ class String
27
28
  def to_ascii
28
29
  self.gsub("_"," ").encode("us-ascii", :invalid => :replace, :undef => :replace, :replace => "")
29
30
  end
31
+ def url_encode
32
+ CGI.escape(self)
33
+ end
30
34
  # Remove all tags inside of a string.
31
35
  def clean
32
36
  self.gsub(/<.+?>/,"")
@@ -1,5 +1,5 @@
1
1
  =begin
2
- Copyright 2014 Maxine Red <maxine_red1@yahoo.com>
2
+ Copyright 2014, 2015 Maxine Red <maxine@furfind.net>
3
3
 
4
4
  This file is part of rubyhexagon.
5
5
 
@@ -19,7 +19,7 @@
19
19
  class Time
20
20
  # A general to_s function for Time objects.
21
21
  def to_s
22
- self.strftime("%b %e,%Y %I:%M %p")
22
+ self.strftime("%b %e,%Y %I:%M:%S %p")
23
23
  end
24
24
  # This methods takes a block and returns how long it took to execute that block.
25
25
  def self.measure
metadata CHANGED
@@ -1,64 +1,84 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: rubyhexagon
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
5
- prerelease:
4
+ version: 0.4.1
6
5
  platform: ruby
7
6
  authors:
8
7
  - Maxine Red
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2015-06-11 00:00:00.000000000 Z
13
- dependencies: []
11
+ date: 2016-01-09 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: json
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: pg
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.18'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.18'
14
41
  description: Rubyhexagon provides Ruby bindings for the e621 [dot] net API.
15
- email: maxine_red1@yahoo.com
16
- executables:
17
- - e621
42
+ email: maxine@furfind.net
43
+ executables: []
18
44
  extensions: []
19
45
  extra_rdoc_files: []
20
46
  files:
21
- - lib/set.rb
22
- - lib/examples/sync.rb
23
- - lib/examples/add.rb
24
- - lib/examples/db.rb
47
+ - lib/api.rb
48
+ - lib/container.rb
25
49
  - lib/pool.rb
26
50
  - lib/post.rb
27
- - lib/standard/time.rb
28
- - lib/standard/http.rb
51
+ - lib/rubyhexagon.rb
52
+ - lib/search.rb
53
+ - lib/set.rb
29
54
  - lib/standard/error.rb
30
- - lib/standard/int.rb
31
55
  - lib/standard/hash.rb
56
+ - lib/standard/http.rb
57
+ - lib/standard/int.rb
32
58
  - lib/standard/string.rb
33
- - lib/config.rb
34
- - lib/container.rb
35
- - lib/rubyhexagon.rb
36
- - lib/api.rb
37
- - lib/search.rb
38
- - bin/e621
59
+ - lib/standard/time.rb
39
60
  homepage: https://github.com/maxine-red/rubyhexagon
40
61
  licenses:
41
62
  - GPL-3.0
63
+ metadata: {}
42
64
  post_install_message:
43
65
  rdoc_options: []
44
66
  require_paths:
45
67
  - lib
46
68
  required_ruby_version: !ruby/object:Gem::Requirement
47
- none: false
48
69
  requirements:
49
- - - ! '>='
70
+ - - ">="
50
71
  - !ruby/object:Gem::Version
51
72
  version: 1.9.2
52
73
  required_rubygems_version: !ruby/object:Gem::Requirement
53
- none: false
54
74
  requirements:
55
- - - ! '>='
75
+ - - ">="
56
76
  - !ruby/object:Gem::Version
57
77
  version: '0'
58
78
  requirements: []
59
79
  rubyforge_project:
60
- rubygems_version: 1.8.23
80
+ rubygems_version: 2.2.2
61
81
  signing_key:
62
- specification_version: 3
82
+ specification_version: 4
63
83
  summary: Rubyhexagon, Ruby bindings for e621[dot]net.
64
84
  test_files: []
data/bin/e621 DELETED
@@ -1,27 +0,0 @@
1
- #!/usr/bin/env ruby
2
- =begin
3
- Copyright 2014 Maxine Red <maxine_red1@yahoo.com>
4
-
5
- This file is part of rubyhexahon.
6
-
7
- rubyhexahon is free software: you can redistribute it and/or modify
8
- it under the terms of the GNU General Public License as published by
9
- the Free Software Foundation, either version 3 of the License, or
10
- (at your option) any later version.
11
-
12
- rubyhexahon is distributed in the hope that it will be useful,
13
- but WITHOUT ANY WARRANTY; without even the implied warranty of
14
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
- GNU General Public License for more details.
16
-
17
- You should have received a copy of the GNU General Public License
18
- along with rubyhexahon. If not, see <http://www.gnu.org/licenses/>.
19
- =end
20
-
21
- $:.unshift File.dirname(__FILE__)+"/../lib"
22
-
23
- require "rubyhexagon"
24
-
25
- $0 = "E621 #{ARGV[0]} #{ARGV[1]}"
26
-
27
- require "examples/#{ARGV.shift}"
@@ -1,46 +0,0 @@
1
- =begin
2
- Copyright 2014 Maxine Red <maxine_red1@yahoo.com>
3
-
4
- This file is part of rubyhexagon.
5
-
6
- rubyhexagon is free software: you can redistribute it and/or modify
7
- it under the terms of the GNU General Public License as published by
8
- the Free Software Foundation, either version 3 of the License, or
9
- (at your option) any later version.
10
-
11
- rubyhexagon is distributed in the hope that it will be useful,
12
- but WITHOUT ANY WARRANTY; without even the implied warranty of
13
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
- GNU General Public License for more details.
15
-
16
- You should have received a copy of the GNU General Public License
17
- along with rubyhexagon. If not, see <http://www.gnu.org/licenses/>.
18
- =end
19
- module E621
20
- class Config
21
- def self.config
22
- return @@config
23
- end
24
-
25
- def self.paths
26
- return @@paths
27
- end
28
-
29
- def self.blacklist
30
- return @@blacklist
31
- end
32
-
33
- def self.config=(config)
34
- if File.exist?(config) then
35
- File.open(config) do |f|
36
- c = f.read.parse
37
- @@paths = c.delete("paths")
38
- @@blacklist = c.delete("blacklist")
39
- @@config = c
40
- end
41
- else
42
- raise ArgumentError, "No configuration file specified. Broken installation!"
43
- end
44
- end
45
- end
46
- end
@@ -1,57 +0,0 @@
1
- =begin
2
- Copyright 2014 Maxine Red <maxine_red1@yahoo.com>
3
-
4
- This file is part of rubyhexagon.
5
-
6
- rubyhexagon is free software: you can redistribute it and/or modify
7
- it under the terms of the GNU General Public License as published by
8
- the Free Software Foundation, either version 3 of the License, or
9
- (at your option) any later version.
10
-
11
- rubyhexagon is distributed in the hope that it will be useful,
12
- but WITHOUT ANY WARRANTY; without even the implied warranty of
13
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
- GNU General Public License for more details.
15
-
16
- You should have received a copy of the GNU General Public License
17
- along with rubyhexagon. If not, see <http://www.gnu.org/licenses/>.
18
- =end
19
-
20
- require "json"
21
-
22
- include E621
23
-
24
- mod = ARGV.shift
25
-
26
- $0 = "E621 #{mod}"
27
-
28
- E621::Config.config = File.expand_path("~/.hexagon/conf.json")
29
- Dir.chdir(File.expand_path(E621::Config.paths["home"])) do
30
- pools, api = Array.new, API.new(mod)
31
- File.open(File.expand_path(E621::Config.paths["j#{mod}s"])) do |f|
32
- pools = JSON.parser.new(f.read).parse
33
- end
34
- puts "Old count: #{pools.length}"
35
- candidates = ARGV.map{|x|x.to_i}.reject{|x|x==0}
36
- puts "Adding candidates: #{candidates.length}"
37
- new = ARGV.map{|x|x.to_i}.reject{|x|x==0||pools.include?(x)}
38
- new.each do |id|
39
- pool = api.post("show",{"id"=>id})
40
- if pool["id"] && mod == "pool" then
41
- pools << id
42
- elsif pool["id"] && mod == "set" then
43
- pools << {"id"=>pool["id"],"owner"=>pool["user_id"]}
44
- end
45
- end
46
- puts "Adding: #{new.length}"
47
- if mod == "pool" then
48
- pools = pools.sort.uniq
49
- elsif mod == "set" then
50
- pools = pools.sort{|k1,k2|k1["id"]<=>k2["id"]}.uniq
51
- end
52
- puts "New count: #{pools.length}"
53
- pools = pools.to_json
54
- File.open(File.expand_path(E621::Config.paths["j#{mod}s"]),"w") do |f|
55
- f.print pools
56
- end
57
- end
@@ -1,143 +0,0 @@
1
- =begin
2
- Copyright 2014 Maxine Red <maxine_red1@yahoo.com>
3
-
4
- This file is part of rubyhexagon.
5
-
6
- rubyhexagon is free software: you can redistribute it and/or modify
7
- it under the terms of the GNU General Public License as published by
8
- the Free Software Foundation, either version 3 of the License, or
9
- (at your option) any later version.
10
-
11
- rubyhexagon is distributed in the hope that it will be useful,
12
- but WITHOUT ANY WARRANTY; without even the implied warranty of
13
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
- GNU General Public License for more details.
15
-
16
- You should have received a copy of the GNU General Public License
17
- along with rubyhexagon. If not, see <http://www.gnu.org/licenses/>.
18
- =end
19
-
20
- require "logger"
21
- require "thread"
22
- require "pg"
23
-
24
- include E621
25
-
26
- $form = "%b %e, %Y %I:%M:%S %p"
27
-
28
- require "search"
29
- require "pool"
30
- require "set"
31
-
32
- Thread.abort_on_exception = true
33
-
34
- class DB
35
- def initialize(database)
36
- @pg = PG.connect(dbname:database)
37
- @mt = Mutex.new
38
- end
39
- def exec(query)
40
- @mt.synchronize do
41
- result = @pg.exec(query)
42
- return result
43
- end
44
- end
45
- def escape_string(str)
46
- @pg.escape_string(str)
47
- end
48
- end
49
-
50
- class Sync
51
- def initialize
52
- E621::Config.config = File.expand_path("~/.hexagon/conf.json")
53
- if $stdout.tty? then
54
- @log = Logger.new($stdout)
55
- @log.level = Logger::DEBUG
56
- else
57
- @log = Logger.new(File.expand_path(E621::Config.paths["logging"]))
58
- @log.level = Logger::INFO
59
- end
60
- @log.formatter = proc do |sev,dat,prog,msg|
61
- "#{Time.now.strftime($form)} [#{sev.pad(5)}] #{msg}#$/"
62
- end
63
- api = API.new("user") # Used for login
64
- @log.info("Program #$0 started.")
65
- @uid = api.get("index",{"name"=>api.user}).first["id"]
66
- @max_wait = 1
67
- Dir.chdir(File.expand_path(E621::Config.paths["files"]))
68
- @db = DB.new("danbooru")
69
- @queue = Queue.new
70
- @mt = Mutex.new
71
- end
72
-
73
- def threads
74
- return E621::Config.threads
75
- end
76
-
77
- def worker(mod)
78
- if mod == "post" then
79
- fetch_posts
80
- end
81
- end
82
-
83
- def fetch_posts
84
- lid = @db.exec("SELECT id FROM e621.posts ORDER BY id DESC LIMIT 1;").first
85
- if lid then
86
- lid = lid["id"].to_i
87
- else
88
- lid = 0
89
- end
90
- posts = Search.new(["order:id", "id:#{lid}.."])
91
- @cols = ["id","tags","artist","created_at","score","md5","file_ext","description","rating","width","height","has_children","parent_id","file_size"]
92
- posts.each_post do |post|
93
- @queue << [post,"#{post.md5[0,2]}/#{post.md5[2,2]}/#{post.md5}.#{post.file_ext}"]
94
- end
95
- end
96
- def download
97
- Thread.current.exit if @queue.empty?
98
- post,path = @queue.pop
99
- m = String.new
100
- path.split("/")[0,2].each do |d|
101
- m += "#{d}/"
102
- Dir.mkdir(m) unless File.exists?(m)
103
- end
104
- tries = 0
105
- begin
106
- post.download(path)
107
- rescue
108
- raise if tries > 8
109
- tries += 1
110
- sleep 2
111
- retry
112
- end
113
- data = @cols.map do |x|
114
- y=post.__send__(x.to_sym)
115
- y = y.to_i if x == "created_at"
116
- y = y.join(" ") if x == "artist"
117
- y != nil ? @db.escape_string(y.to_s) : "NULL"
118
- end
119
- if !@db.exec("SELECT id FROM e621.posts WHERE id = #{post.id};").first then
120
- query = "INSERT INTO e621.posts (#{@cols.join(",")}) VALUES ('#{data.join("','")}');".gsub("'NULL'","NULL")
121
- @db.exec(query)
122
- @mt.synchronize do
123
- @log.info("Added post ##{post.id.pad(6," ")} to library.")
124
- end
125
- end
126
- end
127
- end
128
- sync = Sync.new
129
- posts = Thread.new do
130
- sync.worker("post")
131
- end
132
- sleep 4
133
- downloads = Array.new
134
- 10.times do
135
- downloads << Thread.new do
136
- loop do
137
- sync.download
138
- end
139
- end
140
- end
141
-
142
- posts.join
143
- downloads.each{|d|d.join}
@@ -1,187 +0,0 @@
1
- =begin
2
- Copyright 2014 Maxine Red <maxine_red1@yahoo.com>
3
-
4
- This file is part of rubyhexagon.
5
-
6
- rubyhexagon is free software: you can redistribute it and/or modify
7
- it under the terms of the GNU General Public License as published by
8
- the Free Software Foundation, either version 3 of the License, or
9
- (at your option) any later version.
10
-
11
- rubyhexagon is distributed in the hope that it will be useful,
12
- but WITHOUT ANY WARRANTY; without even the implied warranty of
13
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14
- GNU General Public License for more details.
15
-
16
- You should have received a copy of the GNU General Public License
17
- along with rubyhexagon. If not, see <http://www.gnu.org/licenses/>.
18
- =end
19
-
20
- require "logger"
21
-
22
- include E621
23
-
24
- mod = ARGV.shift
25
-
26
- $form = "%b %e, %Y %I:%M:%S %p"
27
-
28
- require mod
29
-
30
- class Sync
31
- def initialize
32
- E621::Config.config = File.expand_path("~/.hexagon/conf.json")
33
- if $stdout.tty? then
34
- @log = Logger.new($stdout)
35
- @log.level = Logger::DEBUG
36
- else
37
- @log = Logger.new(File.expand_path(E621::Config.paths["logging"]))
38
- @log.level = Logger::INFO
39
- end
40
- @log.formatter = proc do |sev,dat,prog,msg|
41
- "#{Time.now.strftime($form)} [#{sev.pad(5)}] #{msg}#$/"
42
- end
43
- api = API.new("user") # Used for login
44
- @log.info("Program #$0 started.")
45
- @uid = api.get("index",{"name"=>api.user}).first["id"]
46
- @max_wait = 1
47
- end
48
-
49
- def worker(mod)
50
- @mod = mod
51
- Dir.chdir(File.expand_path("~/Pictures/e621/#{mod}s")) do
52
- sets = Array.new
53
- File.open(File.expand_path(E621::Config.paths["j#{mod}s"])) do |f|
54
- sets = f.read.parse
55
- end
56
- @set_files = File.expand_path(E621::Config.paths["#{mod}s"])
57
- api = API.new(mod)
58
- sets.each do |oset|
59
- id = mod == "set" ? oset["id"] : oset
60
- if mod == "set" then
61
- set = Set.new(id)
62
- elsif mod == "pool" then
63
- set = Pool.new(id)
64
- end
65
- begin
66
- set.name = set.name.gsub("_"," ")
67
- rescue
68
- @log.error("Error on #{mod} #{id}")
69
- net = Net::HTTP.new("e621.net",443)
70
- net.use_ssl = true
71
- body = net.get("/#{mod}/show/#{id}").body
72
- File.open("/tmp/e621.html","w"){|f|f.print body}
73
- if body.match("ActiveRecord::RecordNotFound")
74
- pools_f, pools = File.expand_path("~/.hexagon/#{mod}s.json"), Array.new
75
- File.open(pools_f) do |f|
76
- pools = f.read.parse
77
- end
78
- pools.delete(id)
79
- File.open(pools_f,"w") do |f|
80
- f.print pools.to_json
81
- end
82
- @log.info("Removed: #{mod} ##{id} doesn't exist anymore.")
83
- puts "Removed: #{mod} ##{id} doesn't exist anymore."
84
- end
85
- next
86
- end
87
- @log.info("Fetching #{mod} \"#{set.name}\"")
88
- set.name = "Love_Can_Be_Different" if set.name == "Love_can_be_different"
89
- set.name = set.name.gsub("_"," ").encode("us-ascii", :invalid => :replace, :undef => :replace, :replace => "")
90
- set.name = set.name.gsub(/[^0-9, ,_,a-z,\-]/i,"").sub(/\s+$/,"")
91
- name = mod == "set" ? set.shortname : id.pad(5)
92
- if !File.exists?(@set_files+"/#{name}.json") then
93
- File.open(@set_files+"/#{name}.json","w"){|f|f.print "{\"downloads\":[],\"updated_at\":0}"}
94
- end
95
- File.open(@set_files+"/#{name}.json"){|f|@posts = f.read.parse}
96
- @posts.store("updated_at",0) if !@posts.has_key?("updated_at")
97
- @posts.store("downloads",@posts["posts"]) if @posts["posts"]
98
- next if set.updated_at.to_i <= @posts["updated_at"].to_i
99
- dname = mod == "pool" ? set.name : name
100
- Dir.mkdir(dname) unless File.exist?(dname)
101
- i = 0
102
- set.each_post do |post|
103
- i += 1
104
- next if @posts["downloads"].include?(post.id)
105
- download(post,dname,set,i)
106
- end
107
- advanced_set(oset,name,set) if mod == "set"
108
- @posts["updated_at"] = set.updated_at.to_i
109
- jposts = @posts.to_json
110
- File.open(@set_files+"/#{name}.json","w"){|f|f.print jposts}
111
- end
112
- end
113
- end
114
-
115
- def advanced_set(set,name,s)
116
- sid, owner, query = set["id"], set["owner"], set["search"]
117
- if owner == @uid then
118
- Search.new("fav:maxine_red #{query} order:id".split(" ")).each_post do |post|
119
- next if @posts["downloads"].include?(post.id)
120
- download(post,name,s)
121
- end
122
- api = API.new("set")
123
- set = api.get("show", {"id"=>sid})
124
- posts = set["posts"].map{|post|Post.new(post).id}
125
- name = set["shortname"]
126
- local = Dir["#{name}/*"].reject{|x|x.match(/db$/)}.map{|x|x.split(".")[1].to_i}.sort.uniq
127
- (posts-local).each do |id|
128
- post = Post.new({"id"=>id})
129
- f = post.remove_from_set(sid)
130
- if f["success"] then
131
- s = "Removed Post #{" "*(6-post.id.to_s.length)}#{post.id} from \"#{set["name"]}\""
132
- puts "#{Time.now.strftime($form)}: \e[1;33m#{s}\e[0m"
133
- @log.info(s)
134
- else
135
- s = "#{f["reason"].to_s.gsub(/<.+?>/,"")}."
136
- puts "#{Time.now.strftime($form)}: \e[1;31m#{s}\e[0m"
137
- @log.info(s)
138
- end
139
- end
140
- (local-posts).each do |id|
141
- next if id == 0
142
- post = Post.new({"id"=>id})
143
- f = post.add_to_set(sid)
144
- if f["success"] then
145
- s = "Added Post #{" "*(6-post.id.to_s.length)}#{post.id} to \"#{set["name"]}\"."
146
- puts "#{Time.now.strftime($form)}: #{s}"
147
- @log.info(s)
148
- else
149
- s = "#{f["reason"].gsub(/<.+?>/,"")}. Removing local file."
150
- puts "#{Time.now.strftime($form)}: #{s}"
151
- @log.info(s)
152
- File.unlink(Dir["#{set["shortname"]}/*.#{"0"*(8-post.id.to_s.length)}#{post.id}.*"].first)
153
- end
154
- end
155
- end
156
- end
157
- def download(post,name,set,id=0)
158
- artist = post.artist.first
159
- if @mod == "set" then
160
- string = [post.created_at.to_i,post.id.pad(8),artist+"_"+name,post.file_ext]
161
- elsif @mod == "pool" then
162
- string = [set.id.pad(5),id.pad(4),post.id.pad(8),artist,name.downcase.gsub(/\s/,"_"),post.file_ext]
163
- end
164
- string = string.join(".")
165
- tries = 0
166
- begin
167
- post.download("#{name}/#{string}")
168
- rescue
169
- raise if tries > 4
170
- tries += 1
171
- puts "#{Time.now.strftime($form)}: #$!"
172
- @log.info("#$!")
173
- sleep 1
174
- retry
175
- end
176
- File.utime(post.created_at,post.created_at,"#{name}/#{string}")
177
- @posts["downloads"] << post.id
178
- jposts = @posts.to_json
179
- File.open(@set_files+"/#{name}.json","w"){|f|f.print jposts}
180
- s = "Downloaded post #{" "*(6-post.id.to_s.length)}#{post.id} from \"#{set.name}\"."
181
- puts "#{Time.now.strftime($form)}: #{s}"
182
- sleep(rand*@max_wait)
183
- @log.info(s)
184
- end
185
- end
186
- sync = Sync.new
187
- sync.worker(mod)