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 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