myflickr 0.0.2
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.
- data/History.txt +8 -0
- data/License.txt +20 -0
- data/Manifest.txt +24 -0
- data/README.txt +20 -0
- data/Rakefile +4 -0
- data/config/hoe.rb +71 -0
- data/config/requirements.rb +17 -0
- data/lib/myflickr/machine_tag.rb +7 -0
- data/lib/myflickr/photo.rb +47 -0
- data/lib/myflickr/query.rb +10 -0
- data/lib/myflickr/set.rb +33 -0
- data/lib/myflickr/size.rb +4 -0
- data/lib/myflickr/tag.rb +10 -0
- data/lib/myflickr/version.rb +9 -0
- data/lib/myflickr.rb +13 -0
- data/lib/vendor/parallel/parallel.rb +121 -0
- data/setup.rb +1585 -0
- data/spec/machine_tag_spec.rb +11 -0
- data/spec/photo_spec.rb +47 -0
- data/spec/query_spec.rb +7 -0
- data/spec/set_spec.rb +40 -0
- data/spec/size_spec.rb +7 -0
- data/spec/spec_helper.rb +5 -0
- data/spec/tag_spec.rb +13 -0
- metadata +88 -0
data/History.txt
ADDED
data/License.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2007 Ben Schwarz
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Manifest.txt
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
History.txt
|
2
|
+
License.txt
|
3
|
+
Manifest.txt
|
4
|
+
README.txt
|
5
|
+
Rakefile
|
6
|
+
config/hoe.rb
|
7
|
+
config/requirements.rb
|
8
|
+
lib/myflickr.rb
|
9
|
+
lib/myflickr/machine_tag.rb
|
10
|
+
lib/myflickr/photo.rb
|
11
|
+
lib/myflickr/query.rb
|
12
|
+
lib/myflickr/set.rb
|
13
|
+
lib/myflickr/size.rb
|
14
|
+
lib/myflickr/tag.rb
|
15
|
+
lib/myflickr/version.rb
|
16
|
+
lib/vendor/parallel/parallel.rb
|
17
|
+
setup.rb
|
18
|
+
spec/machine_tag_spec.rb
|
19
|
+
spec/photo_spec.rb
|
20
|
+
spec/query_spec.rb
|
21
|
+
spec/set_spec.rb
|
22
|
+
spec/size_spec.rb
|
23
|
+
spec/spec_helper.rb
|
24
|
+
spec/tag_spec.rb
|
data/README.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
MyFlickr is for when you're using flickr for only your own images,
|
2
|
+
not the rest of the worlds'.
|
3
|
+
|
4
|
+
All of the methods within the few classes that exist are built around this need for
|
5
|
+
personal images, with the plan of building a portfolio using only flickr as the backend.
|
6
|
+
|
7
|
+
The stats
|
8
|
+
* Using the REST interface because its easy to parse and transport
|
9
|
+
* Using MenTaLguY's parallel_map method to thread all the calls to flickr (for speed)
|
10
|
+
* Most everything else is home-baked, a little code style stolen from toolmantim; Its all love
|
11
|
+
|
12
|
+
Usage
|
13
|
+
require 'myflickr'
|
14
|
+
include Myflickr
|
15
|
+
Myflickr::Photo.search "hk"
|
16
|
+
|
17
|
+
Which will return an array of photo objects
|
18
|
+
|
19
|
+
Questions and comments can be directed to ben at germanforblack.com
|
20
|
+
All code is provided with no warranty and should be considered experimental until otherwise stated (Don't let your mother or girlfriend *near* it)
|
data/Rakefile
ADDED
data/config/hoe.rb
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'myflickr/version'
|
2
|
+
|
3
|
+
AUTHOR = 'Ben Schwarz' # can also be an array of Authors
|
4
|
+
EMAIL = "ben@germanforblack.com"
|
5
|
+
DESCRIPTION = "A wrapper around flickr for you, not the community aspects of flickr, only for you."
|
6
|
+
GEM_NAME = 'myflickr' # what ppl will type to install your gem
|
7
|
+
RUBYFORGE_PROJECT = 'schwarz' # The unix name for your project
|
8
|
+
HOMEPATH = "http://#{RUBYFORGE_PROJECT}.rubyforge.org"
|
9
|
+
DOWNLOAD_PATH = "http://rubyforge.org/projects/#{RUBYFORGE_PROJECT}"
|
10
|
+
|
11
|
+
@config_file = "~/.rubyforge/user-config.yml"
|
12
|
+
@config = nil
|
13
|
+
RUBYFORGE_USERNAME = "bschwarz"
|
14
|
+
def rubyforge_username
|
15
|
+
unless @config
|
16
|
+
begin
|
17
|
+
@config = YAML.load(File.read(File.expand_path(@config_file)))
|
18
|
+
rescue
|
19
|
+
puts <<-EOS
|
20
|
+
ERROR: No rubyforge config file found: #{@config_file}
|
21
|
+
Run 'rubyforge setup' to prepare your env for access to Rubyforge
|
22
|
+
- See http://newgem.rubyforge.org/rubyforge.html for more details
|
23
|
+
EOS
|
24
|
+
exit
|
25
|
+
end
|
26
|
+
end
|
27
|
+
RUBYFORGE_USERNAME.replace @config["username"]
|
28
|
+
end
|
29
|
+
|
30
|
+
|
31
|
+
REV = nil
|
32
|
+
# UNCOMMENT IF REQUIRED:
|
33
|
+
# REV = `svn info`.each {|line| if line =~ /^Revision:/ then k,v = line.split(': '); break v.chomp; else next; end} rescue nil
|
34
|
+
VERS = Myflickr::VERSION::STRING + (REV ? ".#{REV}" : "")
|
35
|
+
RDOC_OPTS = ['--quiet', '--title', 'myflickr documentation',
|
36
|
+
"--opname", "index.html",
|
37
|
+
"--line-numbers",
|
38
|
+
"--main", "README",
|
39
|
+
"--inline-source"]
|
40
|
+
|
41
|
+
class Hoe
|
42
|
+
def extra_deps
|
43
|
+
@extra_deps.reject! { |x| Array(x).first == 'hoe' }
|
44
|
+
@extra_deps
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
# Generate all the Rake tasks
|
49
|
+
# Run 'rake -T' to see list of generated tasks (from gem root directory)
|
50
|
+
hoe = Hoe.new(GEM_NAME, VERS) do |p|
|
51
|
+
p.author = AUTHOR
|
52
|
+
p.description = DESCRIPTION
|
53
|
+
p.email = EMAIL
|
54
|
+
p.summary = DESCRIPTION
|
55
|
+
p.url = HOMEPATH
|
56
|
+
p.rubyforge_name = RUBYFORGE_PROJECT if RUBYFORGE_PROJECT
|
57
|
+
p.test_globs = ["test/**/test_*.rb"]
|
58
|
+
p.clean_globs |= ['**/.*.sw?', '*.gem', '.config', '**/.DS_Store'] #An array of file patterns to delete on clean.
|
59
|
+
|
60
|
+
# == Optional
|
61
|
+
p.changes = p.paragraphs_of("History.txt", 0..1).join("\n\n")
|
62
|
+
p.extra_deps = %w(hpricot) # An array of rubygem dependencies [name, version], e.g. [ ['active_support', '>= 1.3.1'] ]
|
63
|
+
|
64
|
+
#p.spec_extras = {} # A hash of extra values to set in the gemspec.
|
65
|
+
|
66
|
+
end
|
67
|
+
|
68
|
+
CHANGES = hoe.paragraphs_of('History.txt', 0..1).join("\\n\\n")
|
69
|
+
PATH = (RUBYFORGE_PROJECT == GEM_NAME) ? RUBYFORGE_PROJECT : "#{RUBYFORGE_PROJECT}/#{GEM_NAME}"
|
70
|
+
hoe.remote_rdoc_dir = File.join(PATH.gsub(/^#{RUBYFORGE_PROJECT}\/?/,''), 'rdoc')
|
71
|
+
hoe.rsync_args = '-av --delete --ignore-errors'
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
include FileUtils
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
%w[rake hoe newgem rubigen].each do |req_gem|
|
6
|
+
begin
|
7
|
+
require req_gem
|
8
|
+
rescue LoadError
|
9
|
+
puts "This Rakefile requires the '#{req_gem}' RubyGem."
|
10
|
+
puts "Installation: gem install #{req_gem} -y"
|
11
|
+
exit
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
$:.unshift(File.join(File.dirname(__FILE__), %w[.. lib]))
|
16
|
+
|
17
|
+
require 'myflickr'
|
@@ -0,0 +1,47 @@
|
|
1
|
+
module Myflickr
|
2
|
+
class Photo < Struct.new :id, :title, :description, :tags, :machine_tags
|
3
|
+
attr_reader :sizes
|
4
|
+
|
5
|
+
# Get recent photos
|
6
|
+
def self.recent
|
7
|
+
parse(Query.api_call('flickr.photos.search'))
|
8
|
+
end
|
9
|
+
|
10
|
+
# Find a collection of photos by text
|
11
|
+
def self.search(search_string)
|
12
|
+
parse(Query.api_call('flickr.photos.search', "text=#{search_string}"))
|
13
|
+
end
|
14
|
+
|
15
|
+
# Find will grab all sizes of images, process the tags and standard attributes of a photo
|
16
|
+
def self.find(id)
|
17
|
+
photo_call = Query.api_call('flickr.photos.getInfo', "photo_id=#{id}")
|
18
|
+
|
19
|
+
# Set basic attributes
|
20
|
+
photo = Photo.new(id, *%w(title description).map {|a| (photo_call/a).inner_text })
|
21
|
+
|
22
|
+
# Set tags for photo
|
23
|
+
photo.tags = (photo_call/:tag).map{|tag| Tag.new tag.inner_text}
|
24
|
+
photo.machine_tags = (photo_call/:tag).map{|tag| MachineTag.new tag.inner_text }
|
25
|
+
|
26
|
+
return photo
|
27
|
+
end
|
28
|
+
|
29
|
+
# Get the photo sizes for the photo in an array
|
30
|
+
def sizes
|
31
|
+
hash = {}
|
32
|
+
(Query.api_call('flickr.photos.getSizes', "photo_id=#{id}")/:size).parallel_each(MAX_THREADS) do |size|
|
33
|
+
hash[size['label'].downcase.to_sym] = Size.new(*%w(width height source url).map{|a| size[a]})
|
34
|
+
end
|
35
|
+
hash
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
# Parse applies Hpricot to the photos and maps them to a Photo class instance
|
40
|
+
def self.parse collection
|
41
|
+
photos = (collection/:photo)
|
42
|
+
photos.empty? ? [] : photos.parallel_map(MAX_THREADS) do |photo|
|
43
|
+
self.find(photo[:id])
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
module Myflickr
|
2
|
+
class Query #:nodoc:
|
3
|
+
def self.api_call(method, resource_uri='') #:nodoc:
|
4
|
+
resource = "#{API_BASE}/?method=#{method}&api_key=#{API_KEY}&user_id=#{USER_ID}"
|
5
|
+
resource += "&#{resource_uri}" unless resource_uri.empty?
|
6
|
+
puts "Querying: #{resource}"
|
7
|
+
Hpricot.XML(open(resource))
|
8
|
+
end
|
9
|
+
end
|
10
|
+
end
|
data/lib/myflickr/set.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
module Myflickr
|
2
|
+
class Set < Struct.new :id, :photo_count, :title, :description
|
3
|
+
attr_reader :photos
|
4
|
+
|
5
|
+
# Get a list of available photosets
|
6
|
+
def self.list
|
7
|
+
parse(Query.api_call('flickr.photosets.getList'))
|
8
|
+
end
|
9
|
+
|
10
|
+
# Get information regarding set by searching with the set's ID
|
11
|
+
def self.find id
|
12
|
+
parse(Query.api_call('flickr.photosets.getInfo', "photoset_id=#{id}"))[0]
|
13
|
+
end
|
14
|
+
|
15
|
+
# Get the photos within a set
|
16
|
+
# Queries:
|
17
|
+
# > photosets.getPhotos (1 call)
|
18
|
+
# > Photo.find ID (n calls)
|
19
|
+
def photos
|
20
|
+
(Query.api_call('flickr.photosets.getPhotos', "photoset_id=#{id}")/:photo).parallel_map(MAX_THREADS) do |photo|
|
21
|
+
Photo.find photo[:id]
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
def self.parse(collection) #:nodoc:
|
27
|
+
photosets = (collection/:photoset)
|
28
|
+
photosets.empty? ? [] : photosets.parallel_map(MAX_THREADS) do |set|
|
29
|
+
Set.new(set[:id], set[:photos], (set/:title).inner_text, (set/:description).inner_text)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/myflickr/tag.rb
ADDED
data/lib/myflickr.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'hpricot'
|
3
|
+
require 'open-uri'
|
4
|
+
require 'vendor/parallel/parallel'
|
5
|
+
|
6
|
+
module Myflickr
|
7
|
+
API_BASE = "http://api.flickr.com/services/rest"
|
8
|
+
MAX_THREADS = 100
|
9
|
+
end
|
10
|
+
|
11
|
+
%w(query photo tag size set machine_tag version).each do |r|
|
12
|
+
require File.join(File.dirname(__FILE__), './myflickr/' + r)
|
13
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
# Stolen from http://rubyforge.org/projects/concurrent
|
2
|
+
|
3
|
+
#
|
4
|
+
# concurrent/parallel - data-parallel programming for Ruby
|
5
|
+
#
|
6
|
+
# Copyright (C) 2007 MenTaLguY <mental@rydia.net>
|
7
|
+
#
|
8
|
+
# This file is made available under the same terms as Ruby.
|
9
|
+
#
|
10
|
+
|
11
|
+
module Concurrent #:nodoc:
|
12
|
+
module Parallel #:nodoc:
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module Enumerable #:nodoc:
|
17
|
+
def parallel_each( n, &block )
|
18
|
+
parallel_subsets( n ).map do |slice|
|
19
|
+
Thread.new { slice.each &block }
|
20
|
+
end.each do |thread|
|
21
|
+
thread.join
|
22
|
+
end
|
23
|
+
self
|
24
|
+
end
|
25
|
+
|
26
|
+
def parallel_map( n, &block )
|
27
|
+
parallel_subsets( n ).map do |slice|
|
28
|
+
Thread.new { slice.map &block }
|
29
|
+
end.inject( [] ) do |a, thread|
|
30
|
+
a.push *thread.value
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def parallel_select( n, &block )
|
35
|
+
parallel_subsets( n ).map do |slice|
|
36
|
+
Thread.new { slice.select &block }
|
37
|
+
end.inject( [] ) do |a, results|
|
38
|
+
a.push *thread.value
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def parallel_reject( n, &block )
|
43
|
+
parallel_subsets( n ).map do |slice|
|
44
|
+
Thread.new { slice.reject &block }
|
45
|
+
end.inject( [] ) do |a, thread|
|
46
|
+
a.push *thread.value
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def parallel_max( n )
|
51
|
+
parallel_subsets( n ).map do |slice|
|
52
|
+
Thread.new { slice.max }
|
53
|
+
end.map { |t| t.value }.max
|
54
|
+
end
|
55
|
+
|
56
|
+
def parallel_min( n )
|
57
|
+
parallel_subsets( n ).map do |slice|
|
58
|
+
Thread.new { slice.min }
|
59
|
+
end.map { |t| t.value }.min
|
60
|
+
end
|
61
|
+
|
62
|
+
def parallel_partition( n, &block )
|
63
|
+
parallel_subsets( n ).map do |slice|
|
64
|
+
Thread.new { slice.partition &block }
|
65
|
+
end.inject( [ [], [] ] ) do |acc, thread|
|
66
|
+
pair = thread.value
|
67
|
+
acc[0].push *pair[0]
|
68
|
+
acc[1].push *pair[1]
|
69
|
+
acc
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def parallel_grep( re, n, &block )
|
74
|
+
parallel_subsets( n ).map do |slice|
|
75
|
+
Thread.new { slice.grep( re, &block ) }
|
76
|
+
end.inject( [] ) do |acc, thread|
|
77
|
+
acc.push *thread.value
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def parallel_all?( n, &block )
|
82
|
+
parallel_subsets( n ).map do |slice|
|
83
|
+
Thread.new { slice.all? &block }
|
84
|
+
end.inject( true ) do |acc, thread|
|
85
|
+
acc && thread.value
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def parallel_any?( n, &block )
|
90
|
+
parallel_subsets( n ).map do |slice|
|
91
|
+
Thread.new { slice.any? &block }
|
92
|
+
end.inject( false ) do |acc, thread|
|
93
|
+
acc || thread.value
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def parallel_include?( n, obj )
|
98
|
+
parallel_subsets( n ).map do |slice|
|
99
|
+
Thread.new { slice.include? obj }
|
100
|
+
end.inject( false ) do |acc, thread|
|
101
|
+
acc || thread.value
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def parallel_subsets( n )
|
106
|
+
to_a.parallel_subsets( n )
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
class Array #:nodoc:
|
111
|
+
def parallel_subsets( n )
|
112
|
+
if n > 1
|
113
|
+
slice_size = (size.to_f / n.to_f).ceil
|
114
|
+
(0...(size.to_f / slice_size)).map do |i|
|
115
|
+
self[i*slice_size, slice_size.ceil]
|
116
|
+
end
|
117
|
+
else
|
118
|
+
[ self ]
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|