shortwave 0.0.1

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.
Files changed (105) hide show
  1. data/.document +5 -0
  2. data/.gitignore +10 -0
  3. data/.gitmodules +3 -0
  4. data/LICENSE +20 -0
  5. data/README.rdoc +74 -0
  6. data/Rakefile +133 -0
  7. data/VERSION +1 -0
  8. data/lib/shortwave.rb +12 -0
  9. data/lib/shortwave/authentication.rb +107 -0
  10. data/lib/shortwave/facade.rb +3 -0
  11. data/lib/shortwave/facade/build/facade_builder.rb +199 -0
  12. data/lib/shortwave/facade/build/facade_template.erb +17 -0
  13. data/lib/shortwave/facade/lastfm.rb +878 -0
  14. data/lib/shortwave/facade/remote.rb +86 -0
  15. data/lib/shortwave/model/album.rb +33 -0
  16. data/lib/shortwave/model/artist.rb +62 -0
  17. data/lib/shortwave/model/base_model.rb +112 -0
  18. data/lib/shortwave/model/chart_dates.rb +15 -0
  19. data/lib/shortwave/model/comparison.rb +15 -0
  20. data/lib/shortwave/model/event.rb +55 -0
  21. data/lib/shortwave/model/group.rb +26 -0
  22. data/lib/shortwave/model/location.rb +45 -0
  23. data/lib/shortwave/model/playlist.rb +36 -0
  24. data/lib/shortwave/model/shout.rb +21 -0
  25. data/lib/shortwave/model/tag.rb +47 -0
  26. data/lib/shortwave/model/track.rb +71 -0
  27. data/lib/shortwave/model/user.rb +70 -0
  28. data/lib/shortwave/model/venue.rb +37 -0
  29. data/lib/shortwave/model/weekly_charts.rb +31 -0
  30. data/lib/shortwave/providers.rb +161 -0
  31. data/shortwave.gemspec +178 -0
  32. data/test/authentication_test.rb +64 -0
  33. data/test/build/build_test.rb +25 -0
  34. data/test/build/data/intro.yml +2 -0
  35. data/test/build/data/screens/album_addTags.html +1238 -0
  36. data/test/build/data/screens/intro_truncated.html +426 -0
  37. data/test/build/data/screens/tasteometer_compare.html +1274 -0
  38. data/test/build/data/screens/user_getLovedTracks.html +1278 -0
  39. data/test/build/data/screens/venue_search.html +1261 -0
  40. data/test/build/facade_builder_test.rb +23 -0
  41. data/test/build/parameter_test.rb +43 -0
  42. data/test/build/remote_method_test.rb +47 -0
  43. data/test/build/ruby_class_test.rb +12 -0
  44. data/test/build/ruby_method_test.rb +137 -0
  45. data/test/helper.rb +35 -0
  46. data/test/model/album_test.rb +62 -0
  47. data/test/model/artist_test.rb +103 -0
  48. data/test/model/chart_dates_test.rb +11 -0
  49. data/test/model/comparison_test.rb +18 -0
  50. data/test/model/data/album_info.xml +38 -0
  51. data/test/model/data/album_search.xml +210 -0
  52. data/test/model/data/artist_info.xml +58 -0
  53. data/test/model/data/artist_search.xml +109 -0
  54. data/test/model/data/artist_shouts.xml +546 -0
  55. data/test/model/data/artist_top_fans.xml +405 -0
  56. data/test/model/data/event_info.xml +47 -0
  57. data/test/model/data/group_members.xml +242 -0
  58. data/test/model/data/group_weekly_album_chart.xml +1754 -0
  59. data/test/model/data/group_weekly_artist_chart.xml +604 -0
  60. data/test/model/data/group_weekly_track_chart.xml +1005 -0
  61. data/test/model/data/location_events.xml +383 -0
  62. data/test/model/data/ok.xml +2 -0
  63. data/test/model/data/playlist_fetch.xml +227 -0
  64. data/test/model/data/tag_search.xml +110 -0
  65. data/test/model/data/tag_similar.xml +254 -0
  66. data/test/model/data/tag_top_albums.xml +805 -0
  67. data/test/model/data/tag_top_artists.xml +605 -0
  68. data/test/model/data/tag_top_tags.xml +1254 -0
  69. data/test/model/data/tag_top_tracks.xml +843 -0
  70. data/test/model/data/tag_weekly_chart_list.xml +57 -0
  71. data/test/model/data/tasteometer_compare.xml +54 -0
  72. data/test/model/data/track_info.xml +53 -0
  73. data/test/model/data/track_search.xml +195 -0
  74. data/test/model/data/user_chartlist.xml +90 -0
  75. data/test/model/data/user_info.xml +16 -0
  76. data/test/model/data/user_neighbours.xml +484 -0
  77. data/test/model/data/user_playlists.xml +17 -0
  78. data/test/model/data/user_recent_tracks.xml +124 -0
  79. data/test/model/data/user_recommended_artists.xml +454 -0
  80. data/test/model/data/user_shouts.xml +9 -0
  81. data/test/model/data/user_weekly_artist_chart.xml +478 -0
  82. data/test/model/data/venue_events.xml +556 -0
  83. data/test/model/data/venue_past_events.xml +1778 -0
  84. data/test/model/data/venue_search.xml +355 -0
  85. data/test/model/event_test.rb +63 -0
  86. data/test/model/group_test.rb +45 -0
  87. data/test/model/location_test.rb +25 -0
  88. data/test/model/playlist_test.rb +51 -0
  89. data/test/model/shout_test.rb +23 -0
  90. data/test/model/tag_test.rb +39 -0
  91. data/test/model/track_test.rb +67 -0
  92. data/test/model/user_test.rb +125 -0
  93. data/test/model/venue_test.rb +60 -0
  94. data/test/provider/album_provider_test.rb +26 -0
  95. data/test/provider/artist_provider_test.rb +25 -0
  96. data/test/provider/group_provider_test.rb +9 -0
  97. data/test/provider/location_provider_test.rb +9 -0
  98. data/test/provider/playlist_provider_test.rb +12 -0
  99. data/test/provider/tag_provider_test.rb +24 -0
  100. data/test/provider/track_provider_test.rb +26 -0
  101. data/test/provider/user_provider_test.rb +11 -0
  102. data/test/provider/venue_provider_test.rb +15 -0
  103. data/test/provider_test_helper.rb +27 -0
  104. data/test/remote_test.rb +26 -0
  105. metadata +209 -0
@@ -0,0 +1,86 @@
1
+ require 'restclient'
2
+ require 'uri'
3
+ require 'forwardable'
4
+
5
+ module Shortwave
6
+ # Includes thin wrapper classes around the Last.fm web services API.
7
+ #
8
+ # These classes map one-to-one to the Last.fm API, and are in fact generated from the
9
+ # HTML documentation the Last.fm provide. They make no attempt to OOify or XMLify the
10
+ # responses - the results of these methods are just the string responses from Last.fm.
11
+ # As such, they are a suitable base for building a "real" API on top of, which is
12
+ # what classes in Shortwave::Model do.
13
+ #
14
+ # The general use is:
15
+ #
16
+ # # Create a session for your platform
17
+ # session = Shortwave::Authentication::Mobile.new("api_key", "app_secret")
18
+ #
19
+ # # Authenticate if necessary (if you want to use write methods)
20
+ # session.authenticate("user", "password")
21
+ #
22
+ # # Create a facade, passing in the session
23
+ # facade = Shortwave::Facade::Artist.new(session)
24
+ #
25
+ # # Call methods on the facade
26
+ # facade.top_tags("The feelies")
27
+ # # => returns <lfm status="ok"> ... etc.
28
+ #
29
+ # Required method parameters in the web api are real method parameters - if the method
30
+ # has optional parameters, these appear in an options hash after all the required parameters.
31
+ # You do not need to provide any authentication parameters (+api_key+, +api_sig+ & +sk+) - these
32
+ # are merged in from the session for you automatically.
33
+ #
34
+ # If the request fails, the facade method will raise a RemoteError.
35
+ #
36
+ # Each of the facades has further documentation, taken directly from the Last.fm documentation
37
+ # at http://last.fm/api
38
+ module Facade
39
+ # Wraps a RestClient::RequestFailed exception, and delegates +message+, +http_code+
40
+ # and +response+ to the wrapped exception.
41
+ #
42
+ # This class is mainly provided for api stability in case we switch from using RestClient.
43
+ class RemoteError < RuntimeError
44
+ extend Forwardable
45
+ def_delegators :@cause, :message, :to_s, :http_code, :response
46
+
47
+ def initialize(cause)
48
+ @cause = cause
49
+ end
50
+ end
51
+
52
+ # Base class for each remote facade.
53
+ #
54
+ # Remote methods may raise a RemoteError if an http error response is returned from
55
+ # the Last.fm service.
56
+ class Remote
57
+ attr_reader :session
58
+
59
+ # Creates a new facade, given a user session
60
+ def initialize(session)
61
+ @session = session
62
+ end
63
+
64
+ protected
65
+
66
+ BASE_URI = "http://ws.audioscrobbler.com/2.0/"
67
+ DISALLOWED = Regexp.new("[^#{URI::PATTERN::UNRESERVED}]")
68
+ AGENT = "Shortwave 0.0.1 http://shortwave.rubyforge.org/"
69
+
70
+ def get(type, data)
71
+ @session.merge!(type, data)
72
+ uri = BASE_URI + "?" + data.map {|k,v| "#{k.to_s}=#{URI.escape(v.to_s, DISALLOWED)}"}.join("&")
73
+ RestClient.get uri, {"User-Agent" => AGENT}
74
+ rescue RestClient::RequestFailed => e
75
+ raise RemoteError.new(e)
76
+ end
77
+
78
+ def post(type, data)
79
+ @session.merge!(type, data, {"User-Agent" => AGENT})
80
+ RestClient.post BASE_URI, data
81
+ rescue RestClient::RequestFailed => e
82
+ raise RemoteError.new(e)
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,33 @@
1
+ module Shortwave
2
+ module Model
3
+ # An album on Last.fm
4
+ #
5
+ # === Attributes
6
+ #
7
+ # +name+:: Album name
8
+ # +url+:: Last.fm site url
9
+ # +id+:: Last.fm id
10
+ # +release_date+:: Release date
11
+ # +listeners+:: Number of listeners
12
+ # +play_count+:: Number of times tracks from this album have been played
13
+ # +images+:: An array of album images
14
+ # +mbid+:: Musicbrainz ID
15
+ # +artist_name+:: Album's artist
16
+ # +tags+:: Last.fm user tags
17
+ class Album < BaseModel
18
+ element :name, String, :tag => "name|title"
19
+ element :url, String
20
+ element :id, Integer
21
+ element :release_date, Time, :tag => "releasedate"
22
+ element :listeners, Integer
23
+ element :play_count, Integer, :tag => "playcount"
24
+ element :images, String, :tag => "image", :single => false
25
+ element :mbid, String
26
+ element :artist_name, String, :tag => "artist"
27
+ has_many :tags, "Shortwave::Model::Tag", :tag => "toptags/tag"
28
+
29
+ identified_by :artist_name, :name
30
+ taggable
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,62 @@
1
+ module Shortwave
2
+ module Model
3
+ # An artist. Artists are taggable, shoutable and sharable by a user.
4
+ #
5
+ # === Attributes
6
+ #
7
+ # +name+:: Artist name
8
+ # +mbid+:: Musicbrainz ID
9
+ # +url+:: Url of artist page on the Last.fm site
10
+ # +play_count+:: The number of times this artist has been played
11
+ # +listeners+:: The number of listeners this artist has
12
+ # +biography_summary+:: Textual description of this artist
13
+ # +biography+:: Full textual description of this artist
14
+ # +images+:: Images of this artist
15
+ class Artist < BaseModel
16
+ element :name, String
17
+ element :mbid, String
18
+ element :url, String
19
+ element :streamable, Boolean
20
+ element :play_count, Integer, :tag => "stats/playcount"
21
+ element :listeners, Integer, :tag => "stats/listeners"
22
+ element :biography_summary, String, :tag => "bio/content"
23
+ element :biography, String, :tag => "bio/content"
24
+ element :images, String, :tag => "image", :single => false
25
+
26
+ identified_by :name
27
+ taggable
28
+ shoutable
29
+ sharable
30
+
31
+ # Events this artist is playing at.
32
+ def events
33
+ link :events, :Event, name
34
+ end
35
+
36
+ # Artists similar to this one.
37
+ def similar
38
+ link :similar, :Artist, name
39
+ end
40
+
41
+ # This artist's top albums
42
+ def albums
43
+ link :top_albums, :Album, name
44
+ end
45
+
46
+ # This artist's top fans
47
+ def fans
48
+ link :top_fans, :User, name
49
+ end
50
+
51
+ # This artist's top tags
52
+ def tags
53
+ link :top_tags, :Tag, name
54
+ end
55
+
56
+ # This artist's top tracks
57
+ def tracks
58
+ link :top_tracks, :Track, name
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,112 @@
1
+ module Shortwave
2
+ module Model
3
+ # Root class for all Shortwave models.
4
+ class BaseModel
5
+ # Sets the session on this model, and all nested models
6
+ def session=(session)
7
+ @session = session
8
+ self.class.has_ones.each do |sym|
9
+ obj = send(sym)
10
+ obj.session = session if obj
11
+ end
12
+ self.class.has_manys.each {|s| (send(s) || []).each {|o| o.session = session } }
13
+ session
14
+ end
15
+
16
+ class << self
17
+ def facade_name
18
+ @facade_name ||= (name.split("::").last.downcase + "_facade").to_sym
19
+ end
20
+
21
+ def identified_by(*methods)
22
+ @lastfm_keys = methods
23
+ end
24
+
25
+ # Adds methods to a model to deal with shouts: shouts and shout
26
+ def shoutable
27
+ class_eval <<-EOV
28
+ def shout(message)
29
+ @session.#{facade_name}.shout(#{@lastfm_keys.join(",")}, message)
30
+ end
31
+
32
+ link_to "Shout", :shouts
33
+ EOV
34
+ end
35
+
36
+ # Adds methods to a model to deal with tags: tags, add_tags and remove_tag
37
+ #
38
+ # Up to ten tags can be added, either strings, or Tag models
39
+ # A single tag can be removed
40
+ def taggable
41
+ class_eval <<-EOV
42
+ def add_tags(*tags)
43
+ tag_param = tags[0...10].map {|t| t.to_s }.join(",")
44
+ @session.#{facade_name}.add_tags(#{@lastfm_keys.join(",")}, tag_param)
45
+ end
46
+
47
+ def remove_tag(tag)
48
+ @session.#{facade_name}.remove_tag(#{@lastfm_keys.join(",")}, tag.to_s)
49
+ end
50
+
51
+ link_to "Tag", :my_tags, :tags
52
+ EOV
53
+ end
54
+
55
+ def sharable
56
+ class_eval <<-EOV
57
+ def share(recipients, message=nil)
58
+ params = {}
59
+ params[:message] = message if message
60
+ @session.#{facade_name}.share(#{@lastfm_keys.join(",")}, recipients[0...10].map{|r| r.to_s }.join(','), params)
61
+ end
62
+ EOV
63
+ end
64
+
65
+ def link_to(klass, method, remote_method=nil)
66
+ remote_method ||= method
67
+ class_eval <<-EOV
68
+ def #{method}
69
+ response = @session.#{facade_name}.#{remote_method}(#{@lastfm_keys.join(',')})
70
+ #{klass}.parse(response).each {|obj| obj.session = @session }
71
+ end
72
+ EOV
73
+ end
74
+
75
+ def inherited(klass)
76
+ klass.send(:include, HappyMapper)
77
+ klass.send(:tag, klass.name.split("::").last.downcase)
78
+
79
+ # Store every has_one/has_many declaration and use this information
80
+ # to decide which attributes need a session adding
81
+ class << klass
82
+ def has_ones
83
+ @has_ones ||= []
84
+ end
85
+
86
+ def has_manys
87
+ @has_manys ||= []
88
+ end
89
+
90
+ def has_one(sym, *args)
91
+ super
92
+ has_ones << sym
93
+ end
94
+
95
+ def has_many(sym, *args)
96
+ super
97
+ has_manys << sym
98
+ end
99
+ end
100
+ end
101
+ end
102
+
103
+ protected
104
+
105
+ def link(remote_method, klass, *args)
106
+ response = @session.send(self.class.facade_name).send(remote_method, *args)
107
+ klass = ::Shortwave::Model.const_get(klass)
108
+ klass.parse(response).each {|obj| obj.session = @session }
109
+ end
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,15 @@
1
+ module Shortwave
2
+ module Model
3
+ # A chart date range. This is only really used internally.
4
+ #
5
+ # === Attributes
6
+ #
7
+ # from:: the date the chart starts
8
+ # to:: the date the chart finishes
9
+ class ChartDates < BaseModel
10
+ tag "chart"
11
+ attribute :from, Time
12
+ attribute :to, Time
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ module Shortwave
2
+ module Model
3
+ # A comparison between two Last.fm users
4
+ #
5
+ # === Attributes
6
+ #
7
+ # +score+:: A number between 0 and 1 representing how similar users are.
8
+ # +artists+:: Artists both users have in common
9
+ class Comparison < BaseModel
10
+ tag "comparison"
11
+ element :score, Float, :tag => "result/score"
12
+ has_many :artists, "Model::Artist", :tag => "result/artists/artist"
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,55 @@
1
+ module Shortwave
2
+ module Model
3
+ # An event.
4
+ #
5
+ # == Attributes
6
+ # +id+:: Last.fm id
7
+ # +name+:: Event name
8
+ # +description+:: Event description
9
+ # +attendance_count+:: Number of last.fm user's attending
10
+ # +review_count+:: Number of reviews
11
+ # +starts_at+:: The start time
12
+ # +url+:: Last.fm site url
13
+ # +venue+:: Event venue
14
+ class Event < BaseModel
15
+ element :id, Integer
16
+ element :description, String
17
+ element :url, String
18
+ element :attendance_count, Integer, :tag => "attendance"
19
+ element :review_count, Integer, :tag => "reviews"
20
+ element :starts_at, Time, :tag => "startDate"
21
+ element :name, String, :tag => "title"
22
+ element :headliner_raw, String, :tag => "artists/headliner"
23
+ element :artists_raw, String, :tag => "artists/artist", :single => false
24
+ has_one :venue, "Model::Venue"
25
+
26
+ identified_by :id
27
+ shoutable
28
+ sharable
29
+
30
+ # Returns the list of users attending this event
31
+ def attendees
32
+ link :attendees, :User, id
33
+ end
34
+
35
+ # Mark the current session user's attendance at this event
36
+ #
37
+ # Possible statuses are +:yes+, +:no+ and +:maybe+
38
+ def attend(status=:yes)
39
+ i = [:yes, :maybe, :no].index(status)
40
+ @session.event_facade.attend(id, i)
41
+ true
42
+ end
43
+
44
+ # Returns the headline act for this event
45
+ def headliner
46
+ @headliner ||= artists.detect {|a| a.name == headliner_raw }
47
+ end
48
+
49
+ # Returns the list of artists playing at this event
50
+ def artists
51
+ @artists ||= artists_raw.map {|a| @session.artist.build(:name => a) }
52
+ end
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,26 @@
1
+ require File.dirname(__FILE__) + "/weekly_charts"
2
+
3
+ module Shortwave
4
+ module Model
5
+ # A Last.fm user group.
6
+ #
7
+ # === Attributes
8
+ #
9
+ # name:: the group name
10
+ class Group < BaseModel
11
+ attr_accessor :name
12
+ identified_by :name
13
+ include WeeklyCharts
14
+
15
+ # Returns the group's members. Currently only 50 users are returned
16
+ def members
17
+ link :members, "User", name
18
+ end
19
+
20
+ # The group name
21
+ def to_s
22
+ name
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,45 @@
1
+ module Shortwave
2
+ module Model
3
+ # A location
4
+ #
5
+ # === Attributes
6
+ # +latitude+:: Latitude
7
+ # +longitude+:: Longitude
8
+ # +street_address+:: Street address
9
+ # +city+:: City
10
+ # +postcode+:: Post code
11
+ # +country+:: Country name
12
+ class Location < BaseModel
13
+ tag "location"
14
+ element :latitude, Float, :tag => "geo:point/geo:lat"
15
+ element :longitude, Float, :tag => "geo:point/geo:long"
16
+ element :city, String
17
+ element :country, String
18
+ element :street_address, String, :tag => "street"
19
+ element :postcode, String, :tag => "postalcode"
20
+
21
+ # Popular artists in this country. Needs country to be defined
22
+ def artists
23
+ link :top_artists, :Artist, country
24
+ end
25
+
26
+ # Popular tracks in this location
27
+ def tracks
28
+ args = [country]
29
+ args << {:location => city} if city
30
+ link :top_tracks, :Track, *args
31
+ end
32
+
33
+ # Return events near this location
34
+ def events
35
+ if latitude && longitude
36
+ link :events, :Event, :lat => latitude, :long => longitude
37
+ elsif city
38
+ link :events, :Event, :location => city
39
+ elsif country
40
+ link :events, :Event, :location => country
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end