myflickr 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
data/History.txt ADDED
@@ -0,0 +1,8 @@
1
+ == 0.0.1 2007-12-15
2
+
3
+ * Initial release
4
+
5
+ == 0.0.2 2007-12-19
6
+
7
+ * Specs written, classes revised
8
+ * All 'works'
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
@@ -0,0 +1,4 @@
1
+ require 'config/requirements'
2
+ require 'config/hoe' # setup Hoe + all gem configuration
3
+
4
+ Dir['tasks/**/*.rake'].each { |rake| load rake }
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,7 @@
1
+ module Myflickr
2
+ class MachineTag < Struct.new :namespace, :predicate, :value #:nodoc:
3
+ def self.from_s string
4
+ new($1, $2, $3) if string =~ /(.*)\:(.*)\=(.*)/
5
+ end
6
+ end
7
+ end
@@ -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
@@ -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
@@ -0,0 +1,4 @@
1
+ module Myflickr
2
+ class Size < Struct.new :width, :height, :source, :url #:nodoc:
3
+ end
4
+ end
@@ -0,0 +1,10 @@
1
+ module Myflickr
2
+ class Tag < Struct.new :value
3
+ # List all tags
4
+ def self.list
5
+ (Query.api_call('flickr.tags.getListUser')/:tag).parallel_map(MAX_THREADS) do |tag|
6
+ Tag.new(tag.to_s)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,9 @@
1
+ module Myflickr
2
+ module VERSION #:nodoc:
3
+ MAJOR = 0
4
+ MINOR = 0
5
+ TINY = 2
6
+
7
+ STRING = [MAJOR, MINOR, TINY].join('.')
8
+ end
9
+ end
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