fleakr 0.6.3 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +88 -57
- data/Rakefile +28 -9
- data/lib/fleakr.rb +25 -31
- data/lib/fleakr/api/option.rb +34 -34
- data/lib/fleakr/api/parameter_list.rb +23 -23
- data/lib/fleakr/objects.rb +4 -1
- data/lib/fleakr/objects/metadata.rb +35 -0
- data/lib/fleakr/objects/metadata_collection.rb +36 -0
- data/lib/fleakr/objects/photo.rb +15 -11
- data/lib/fleakr/objects/set.rb +17 -13
- data/lib/fleakr/objects/url.rb +83 -0
- data/lib/fleakr/objects/user.rb +8 -0
- data/lib/fleakr/support.rb +3 -1
- data/lib/fleakr/support/attribute.rb +2 -2
- data/lib/fleakr/support/object.rb +44 -29
- data/lib/fleakr/support/url_expander.rb +37 -0
- data/lib/fleakr/support/utility.rb +66 -0
- data/lib/fleakr/version.rb +5 -5
- data/test/fixtures/photos.getExif.xml +362 -0
- data/test/test_helper.rb +44 -47
- data/test/unit/fleakr/api/authentication_request_test.rb +12 -12
- data/test/unit/fleakr/api/file_parameter_test.rb +15 -15
- data/test/unit/fleakr/api/method_request_test.rb +1 -1
- data/test/unit/fleakr/api/option_test.rb +44 -44
- data/test/unit/fleakr/api/parameter_list_test.rb +40 -40
- data/test/unit/fleakr/api/response_test.rb +10 -10
- data/test/unit/fleakr/api/upload_request_test.rb +28 -28
- data/test/unit/fleakr/api/value_parameter_test.rb +10 -10
- data/test/unit/fleakr/core_ext/false_class_test.rb +5 -5
- data/test/unit/fleakr/core_ext/hash_test.rb +9 -9
- data/test/unit/fleakr/core_ext/true_class_test.rb +5 -5
- data/test/unit/fleakr/objects/authentication_token_test.rb +23 -23
- data/test/unit/fleakr/objects/collection_test.rb +23 -23
- data/test/unit/fleakr/objects/comment_test.rb +15 -15
- data/test/unit/fleakr/objects/contact_test.rb +11 -11
- data/test/unit/fleakr/objects/error_test.rb +8 -8
- data/test/unit/fleakr/objects/group_test.rb +13 -13
- data/test/unit/fleakr/objects/image_test.rb +4 -4
- data/test/unit/fleakr/objects/metadata_collection_test.rb +55 -0
- data/test/unit/fleakr/objects/metadata_test.rb +27 -0
- data/test/unit/fleakr/objects/photo_context_test.rb +23 -23
- data/test/unit/fleakr/objects/photo_test.rb +71 -64
- data/test/unit/fleakr/objects/search_test.rb +22 -22
- data/test/unit/fleakr/objects/set_test.rb +62 -40
- data/test/unit/fleakr/objects/tag_test.rb +32 -31
- data/test/unit/fleakr/objects/url_test.rb +185 -0
- data/test/unit/fleakr/objects/user_test.rb +43 -16
- data/test/unit/fleakr/support/attribute_test.rb +15 -15
- data/test/unit/fleakr/support/object_test.rb +77 -33
- data/test/unit/fleakr/support/request_test.rb +12 -12
- data/test/unit/fleakr/support/url_expander_test.rb +44 -0
- data/test/unit/fleakr/support/utility_test.rb +70 -0
- data/test/unit/fleakr_test.rb +48 -41
- metadata +43 -23
data/lib/fleakr/objects/set.rb
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
module Fleakr
|
2
2
|
module Objects # :nodoc:
|
3
|
-
|
3
|
+
|
4
4
|
# = Set
|
5
5
|
#
|
6
6
|
# == Attributes
|
7
|
-
#
|
7
|
+
#
|
8
8
|
# [id] The ID for this photoset
|
9
9
|
# [title] The title of this photoset
|
10
10
|
# [description] The description of this set
|
11
11
|
# [count] Count of photos in this set
|
12
|
-
#
|
12
|
+
#
|
13
13
|
# == Associations
|
14
|
-
#
|
14
|
+
#
|
15
15
|
# [photos] The collection of photos for this set. See Fleakr::Objects::Photo
|
16
16
|
# [comments] All comments associated with this set. See Fleakr::Objects::Comment
|
17
17
|
#
|
@@ -27,18 +27,18 @@ module Fleakr
|
|
27
27
|
flickr_attribute :user_id, :from => '@owner'
|
28
28
|
|
29
29
|
find_all :by_user_id, :call => 'photosets.getList', :path => 'photosets/photoset'
|
30
|
-
|
30
|
+
|
31
31
|
find_one :by_id, :using => :photoset_id, :call => 'photosets.getInfo', :path => 'photoset'
|
32
32
|
|
33
33
|
lazily_load :user_id, :with => :load_info
|
34
34
|
|
35
35
|
# Save all photos in this set to the specified directory for the specified size. Allowed
|
36
|
-
# Sizes include <tt>:square</tt>, <tt>:small</tt>, <tt>:thumbnail</tt>, <tt>:medium</tt>,
|
37
|
-
# <tt>:large</tt>, and <tt>:original</tt>. When saving the set, this method will create
|
36
|
+
# Sizes include <tt>:square</tt>, <tt>:small</tt>, <tt>:thumbnail</tt>, <tt>:medium</tt>,
|
37
|
+
# <tt>:large</tt>, and <tt>:original</tt>. When saving the set, this method will create
|
38
38
|
# a subdirectory based on the set's title.
|
39
39
|
#
|
40
40
|
def save_to(path, size)
|
41
|
-
target = "#{path}/#{
|
41
|
+
target = "#{path}/#{folder_name}"
|
42
42
|
FileUtils.mkdir(target) unless File.exist?(target)
|
43
43
|
|
44
44
|
self.photos.each_with_index do |photo, index|
|
@@ -46,34 +46,38 @@ module Fleakr
|
|
46
46
|
image.save_to(target, file_prefix(index)) unless image.nil?
|
47
47
|
end
|
48
48
|
end
|
49
|
-
|
49
|
+
|
50
50
|
def file_prefix(index) # :nodoc:
|
51
51
|
sprintf("%0#{self.count.length}d_", (index + 1))
|
52
52
|
end
|
53
53
|
|
54
|
+
def folder_name # :nodoc:
|
55
|
+
title.gsub("/", ' ').squeeze(' ')
|
56
|
+
end
|
57
|
+
|
54
58
|
# Primary photo for this set. See Fleakr::Objects::Photo for more details.
|
55
59
|
#
|
56
60
|
def primary_photo
|
57
61
|
@primary_photo ||= Photo.find_by_id(primary_photo_id)
|
58
62
|
end
|
59
|
-
|
63
|
+
|
60
64
|
# The URL for this set.
|
61
65
|
#
|
62
66
|
def url
|
63
67
|
"http://www.flickr.com/photos/#{user_id}/sets/#{id}/"
|
64
68
|
end
|
65
|
-
|
69
|
+
|
66
70
|
# The user who created this set.
|
67
71
|
#
|
68
72
|
def user
|
69
73
|
User.find_by_id(user_id)
|
70
74
|
end
|
71
|
-
|
75
|
+
|
72
76
|
def load_info # :nodoc:
|
73
77
|
response = Fleakr::Api::MethodRequest.with_response!('photosets.getInfo', :photoset_id => self.id)
|
74
78
|
self.populate_from(response.body)
|
75
79
|
end
|
76
|
-
|
80
|
+
|
77
81
|
end
|
78
82
|
end
|
79
83
|
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
module Fleakr
|
2
|
+
module Objects
|
3
|
+
|
4
|
+
class Url
|
5
|
+
|
6
|
+
def initialize(url)
|
7
|
+
@url = url
|
8
|
+
end
|
9
|
+
|
10
|
+
def path
|
11
|
+
expanded_path || original_path
|
12
|
+
end
|
13
|
+
|
14
|
+
def user_identifier
|
15
|
+
(resource_type == Set) ? parts[1] : parts[2]
|
16
|
+
end
|
17
|
+
|
18
|
+
def shortened?
|
19
|
+
!original_path.match(%r{^/p/[123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ]+$}).nil?
|
20
|
+
end
|
21
|
+
|
22
|
+
def resource_identifier
|
23
|
+
parts[3]
|
24
|
+
end
|
25
|
+
|
26
|
+
def user
|
27
|
+
@user ||= User.find_by_identifier(user_identifier)
|
28
|
+
end
|
29
|
+
|
30
|
+
def resource_type
|
31
|
+
if parts[1] == 'people'
|
32
|
+
User
|
33
|
+
elsif parts[1] == 'photos'
|
34
|
+
Photo
|
35
|
+
elsif parts[2] == 'sets'
|
36
|
+
Set
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def collection?
|
41
|
+
resource_identifier.nil?
|
42
|
+
end
|
43
|
+
|
44
|
+
def resource
|
45
|
+
if resource_type == User
|
46
|
+
user
|
47
|
+
else
|
48
|
+
collection? ? resource_type.find_all_by_user_id(user.id) : resource_type.find_by_id(resource_identifier)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def expanded_path
|
55
|
+
Fleakr::Support::UrlExpander.expand(@url) if shortened?
|
56
|
+
end
|
57
|
+
|
58
|
+
def original_path
|
59
|
+
URI.parse(@url).path
|
60
|
+
end
|
61
|
+
|
62
|
+
def parts
|
63
|
+
path.match(matching_pattern)
|
64
|
+
end
|
65
|
+
|
66
|
+
def matching_pattern
|
67
|
+
@matching_pattern ||= patterns.detect {|p| path.match(p) }
|
68
|
+
end
|
69
|
+
|
70
|
+
def patterns
|
71
|
+
[
|
72
|
+
%r{^/photos/([^/]+)/(sets)/(\d+)},
|
73
|
+
%r{^/photos/([^/]+)/(sets)},
|
74
|
+
%r{^/(photos)/([^/]+)/(\d+)},
|
75
|
+
%r{^/(people)/([^/]+)},
|
76
|
+
%r{^/(photos)/([^/]+)}
|
77
|
+
]
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
end
|
83
|
+
end
|
data/lib/fleakr/objects/user.rb
CHANGED
@@ -68,6 +68,14 @@ module Fleakr
|
|
68
68
|
|
69
69
|
scoped_search
|
70
70
|
|
71
|
+
def self.user_id?(username_or_user_id)
|
72
|
+
(username_or_user_id =~ /^\d+@N\d{2}$/) ? true : false
|
73
|
+
end
|
74
|
+
|
75
|
+
def self.find_by_identifier(identifier)
|
76
|
+
user_id?(identifier) ? find_by_id(identifier) : find_by_username(identifier)
|
77
|
+
end
|
78
|
+
|
71
79
|
# Is this a pro account?
|
72
80
|
def pro?
|
73
81
|
(self.pro.to_i == 0) ? false : true
|
data/lib/fleakr/support.rb
CHANGED
@@ -15,7 +15,7 @@ module Fleakr
|
|
15
15
|
|
16
16
|
def split(source)
|
17
17
|
location, attribute = source.split('@')
|
18
|
-
location = self.name.to_s if
|
18
|
+
location = self.name.to_s if Utility.blank?(location)
|
19
19
|
|
20
20
|
[location, attribute]
|
21
21
|
end
|
@@ -36,7 +36,7 @@ module Fleakr
|
|
36
36
|
def value_from(document)
|
37
37
|
values = sources.map do |source|
|
38
38
|
node = node_for(document, source)
|
39
|
-
[node.attributes[attribute(source)], node.inner_text].reject{|v|
|
39
|
+
[node.attributes[attribute(source)], node.inner_text].reject{|v| Utility.blank?(v) }.first unless node.nil?
|
40
40
|
end
|
41
41
|
values.compact.first
|
42
42
|
end
|
@@ -1,72 +1,76 @@
|
|
1
1
|
module Fleakr
|
2
2
|
module Support # :nodoc:all
|
3
3
|
module Object
|
4
|
-
|
4
|
+
|
5
5
|
module ClassMethods
|
6
|
-
|
6
|
+
|
7
7
|
def attributes
|
8
8
|
@attributes ||= []
|
9
9
|
end
|
10
|
-
|
10
|
+
|
11
11
|
def flickr_attribute(*names_and_options)
|
12
|
-
options =
|
12
|
+
attributes, options = Utility.extract_options(names_and_options)
|
13
13
|
|
14
|
-
|
14
|
+
attributes.each do |name|
|
15
15
|
self.attributes << Attribute.new(name, options[:from])
|
16
16
|
class_eval "attr_accessor :#{name}"
|
17
17
|
end
|
18
18
|
end
|
19
|
-
|
19
|
+
|
20
20
|
def has_many(*attributes)
|
21
21
|
class_name = self.name
|
22
22
|
|
23
23
|
attributes.each do |attribute|
|
24
|
-
target =
|
25
|
-
finder_attribute =
|
24
|
+
target = Utility.class_name_for('Fleakr::Objects', attribute)
|
25
|
+
finder_attribute = Utility.id_attribute_for(class_name)
|
26
26
|
|
27
27
|
class_eval <<-CODE
|
28
|
-
def #{attribute}
|
29
|
-
|
28
|
+
def #{attribute}(options = {})
|
29
|
+
options = authentication_options.merge(options)
|
30
|
+
sorted_options = options.sort {|a, b| a[0].to_s <=> b[0].to_s }
|
31
|
+
key = '#{attribute}_' + sorted_options.to_s
|
32
|
+
|
33
|
+
associations[key] ||= #{target}.send("find_all_by_#{finder_attribute}".to_sym, self.id, options)
|
30
34
|
end
|
31
35
|
CODE
|
32
36
|
end
|
33
37
|
end
|
34
|
-
|
38
|
+
|
35
39
|
def find_all(condition, options)
|
36
40
|
attribute = options[:using].nil? ? condition.to_s.sub(/^by_/, '') : options[:using]
|
37
41
|
target_class = options[:class_name].nil? ? self.name : "Fleakr::Objects::#{options[:class_name]}"
|
38
|
-
|
42
|
+
|
39
43
|
class_eval <<-CODE
|
40
44
|
def self.find_all_#{condition}(value, options = {})
|
41
45
|
options.merge!(:#{attribute} => value)
|
42
|
-
|
46
|
+
|
43
47
|
response = Fleakr::Api::MethodRequest.with_response!('#{options[:call]}', options)
|
44
48
|
(response.body/'rsp/#{options[:path]}').map {|e| #{target_class}.new(e, options) }
|
45
49
|
end
|
46
50
|
CODE
|
47
51
|
end
|
48
|
-
|
52
|
+
|
49
53
|
def find_one(condition, options)
|
50
54
|
attribute = options[:using].nil? ? condition.to_s.sub(/^by_/, '') : options[:using]
|
51
|
-
|
55
|
+
|
52
56
|
class_eval <<-CODE
|
53
57
|
def self.find_#{condition}(value, options = {})
|
54
58
|
options.merge!(:#{attribute} => value)
|
55
|
-
|
59
|
+
|
56
60
|
response = Fleakr::Api::MethodRequest.with_response!('#{options[:call]}', options)
|
57
61
|
#{self.name}.new(response.body, options)
|
58
62
|
end
|
59
63
|
CODE
|
60
64
|
end
|
61
|
-
|
65
|
+
|
62
66
|
def scoped_search
|
63
|
-
key =
|
67
|
+
key = Utility.id_attribute_for(self.name)
|
64
68
|
|
65
69
|
class_eval <<-CODE
|
66
70
|
def search(*parameters)
|
67
71
|
options = {:#{key} => self.id}
|
68
72
|
options.merge!(self.authentication_options)
|
69
|
-
|
73
|
+
|
70
74
|
parameters << options
|
71
75
|
Fleakr::Objects::Search.new(*parameters).results
|
72
76
|
end
|
@@ -74,30 +78,30 @@ module Fleakr
|
|
74
78
|
end
|
75
79
|
|
76
80
|
def lazily_load(*attributes)
|
77
|
-
options =
|
81
|
+
attributes, options = Utility.extract_options(attributes)
|
78
82
|
|
79
83
|
attributes.each do |attribute|
|
80
84
|
class_eval <<-CODE
|
81
|
-
|
85
|
+
alias_method :#{attribute}_without_loading, :#{attribute}
|
86
|
+
def #{attribute}
|
82
87
|
self.send(:#{options[:with]}) if @#{attribute}.nil?
|
83
88
|
#{attribute}_without_loading
|
84
89
|
end
|
85
|
-
alias_method_chain :#{attribute}, :loading
|
86
90
|
CODE
|
87
91
|
end
|
88
92
|
end
|
89
|
-
|
93
|
+
|
90
94
|
end
|
91
|
-
|
95
|
+
|
92
96
|
module InstanceMethods
|
93
|
-
|
97
|
+
|
94
98
|
attr_reader :document, :authentication_options
|
95
|
-
|
99
|
+
|
96
100
|
def initialize(document = nil, options = {})
|
97
101
|
self.populate_from(document) unless document.nil?
|
98
102
|
@authentication_options = options.extract!(:auth_token)
|
99
103
|
end
|
100
|
-
|
104
|
+
|
101
105
|
def populate_from(document)
|
102
106
|
@document = document
|
103
107
|
self.class.attributes.each do |attribute|
|
@@ -105,14 +109,25 @@ module Fleakr
|
|
105
109
|
self.send("#{attribute.name}=".to_sym, value) unless value.nil?
|
106
110
|
end
|
107
111
|
end
|
108
|
-
|
112
|
+
|
113
|
+
def inspect
|
114
|
+
names = instance_variables.reject {|n| %w(@associations @document).include?(n.to_s) }
|
115
|
+
attributes = names.map {|n| "#{n}=#{instance_variable_get(n).inspect}" }
|
116
|
+
|
117
|
+
"#<#{self.class} #{attributes.join(', ')}>"
|
118
|
+
end
|
119
|
+
|
120
|
+
def associations
|
121
|
+
@associations ||= {}
|
122
|
+
end
|
123
|
+
|
109
124
|
end
|
110
125
|
|
111
126
|
def self.included(other)
|
112
127
|
other.send(:extend, Fleakr::Support::Object::ClassMethods)
|
113
128
|
other.send(:include, Fleakr::Support::Object::InstanceMethods)
|
114
129
|
end
|
115
|
-
|
130
|
+
|
116
131
|
end
|
117
132
|
end
|
118
133
|
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Fleakr
|
2
|
+
module Support # :nodoc:all
|
3
|
+
|
4
|
+
class UrlExpander
|
5
|
+
|
6
|
+
def self.expand(url)
|
7
|
+
new(url).expanded_path
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(source_url)
|
11
|
+
@source_url = source_url
|
12
|
+
end
|
13
|
+
|
14
|
+
def path_to_expand
|
15
|
+
"/photo.gne?short=#{short_photo_id}"
|
16
|
+
end
|
17
|
+
|
18
|
+
def expanded_path
|
19
|
+
response_headers['location']
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def response_headers
|
25
|
+
response = nil
|
26
|
+
Net::HTTP.start('www.flickr.com', 80) {|c| response = c.head(path_to_expand) }
|
27
|
+
response
|
28
|
+
end
|
29
|
+
|
30
|
+
def short_photo_id
|
31
|
+
@source_url.match(%r{([^/]+)$})[1]
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
module Fleakr
|
2
|
+
module Support # :nodoc:
|
3
|
+
|
4
|
+
# = Utility
|
5
|
+
#
|
6
|
+
# Helpful utility methods.
|
7
|
+
#
|
8
|
+
class Utility
|
9
|
+
|
10
|
+
# Given a module name and an underscored name, generate the fully
|
11
|
+
# namespaced class name. For example:
|
12
|
+
#
|
13
|
+
# >> Utility.class_name_for('Fleakr::Api', 'method_request')
|
14
|
+
# => "Fleakr::Api::MethodRequest"
|
15
|
+
#
|
16
|
+
def self.class_name_for(module_name, name)
|
17
|
+
class_name = name.to_s.sub(/^(\w)/) {|m| m.upcase }
|
18
|
+
class_name = class_name.sub(/(_(\w))/) {|m| $2.upcase }
|
19
|
+
class_name = class_name.sub(/s$/, '')
|
20
|
+
|
21
|
+
"#{module_name}::#{class_name}"
|
22
|
+
end
|
23
|
+
|
24
|
+
# Given a class name as a string with an optional namespace, generate
|
25
|
+
# an attribute ID parameter suitable for retrieving the ID of an associated
|
26
|
+
# object. For example:
|
27
|
+
#
|
28
|
+
# >> Utility.id_attribute_for('Fleakr::Objects::Set')
|
29
|
+
# => "set_id"
|
30
|
+
#
|
31
|
+
def self.id_attribute_for(class_name)
|
32
|
+
class_name = class_name.match(/([^:]+)$/)[1]
|
33
|
+
class_name.gsub!(/([A-Z])([A-Z][a-z])/, '\1_\2')
|
34
|
+
class_name.gsub!(/([a-z])([A-Z])/, '\1_\2')
|
35
|
+
|
36
|
+
"#{class_name.downcase}_id"
|
37
|
+
end
|
38
|
+
|
39
|
+
# Determine if the passed value is blank. Blank values include nil,
|
40
|
+
# the empty string, and a string with only whitespace.
|
41
|
+
#
|
42
|
+
def self.blank?(object)
|
43
|
+
object.to_s.sub(/\s+/, '') == ''
|
44
|
+
end
|
45
|
+
|
46
|
+
# Extract the options from an array if present and return the new array
|
47
|
+
# and any available options. For example:
|
48
|
+
#
|
49
|
+
# >> Utility.extract_options([:sets, {:using => :key}])
|
50
|
+
# => [[:sets], {:using => :key}]
|
51
|
+
#
|
52
|
+
# Note that this method does not modify the supplied parameter.
|
53
|
+
#
|
54
|
+
def self.extract_options(array_with_possible_options)
|
55
|
+
array = array_with_possible_options.dup
|
56
|
+
|
57
|
+
options = array.pop if array.last.is_a?(Hash)
|
58
|
+
options ||= {}
|
59
|
+
|
60
|
+
[array, options]
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
end
|