/atlib/tags/at2dist080710/demo/mobiTunes.at

http://ambienttalk.googlecode.com/ · Unknown · 250 lines · 223 code · 27 blank · 0 comment · 0 complexity · a3db117f88c65c465ece954a24fa1802 MD5 · raw file

  1. /**
  2. * AmbientTalk/2 Project
  3. * (c) Programming Technology Lab, 2006 - 2007
  4. * Authors: Tom Van Cutsem & Stijn Mostinckx
  5. *
  6. * Permission is hereby granted, free of charge, to any person
  7. * obtaining a copy of this software and associated documentation
  8. * files (the "Software"), to deal in the Software without
  9. * restriction, including without limitation the rights to use,
  10. * copy, modify, merge, publish, distribute, sublicense, and/or
  11. * sell copies of the Software, and to permit persons to whom the
  12. * Software is furnished to do so, subject to the following
  13. * conditions:
  14. *
  15. * The above copyright notice and this permission notice shall be
  16. * included in all copies or substantial portions of the Software.
  17. *
  18. * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  19. * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
  20. * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  21. * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
  22. * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
  23. * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  24. * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
  25. * OTHER DEALINGS IN THE SOFTWARE.
  26. */
  27. /* The MobiTunes music player is a peer-to-peer application meant to be
  28. * deployed on mobile ad hoc networks. Each music player has a local
  29. * music library filled with Song objects. When a music player discovers
  30. * another player in its environment (its 'ambient'), both music players
  31. * engage in a library exchange session, where they exchange the (index of)
  32. * their music library. This way, a music player can e.g. notify its owner
  33. * whenever someone is found in the environment with a similar taste in music.
  34. *
  35. * For more detailed information, cfr. the paper 'Object-oriented Coordination
  36. * in Mobile Ad Hoc Networks', in Proceedings of the 9th internation conference
  37. * on coordination models and languages (COORDINATION '07), 2007.
  38. *
  39. * Added changes for leasing DSAL paper - egonzale
  40. * @author tvcutsem
  41. */
  42. def AmbientRefsM := /.at.lang.ambientrefs_old; // for discover:
  43. import /.at.lang.futures; // for when:becomes:
  44. enableFutures(false);
  45. import /.at.lang.leasedrefs exclude minutes, seconds, millisec; // for leasing primitives
  46. // the library consists of simple song objects
  47. def Song := object: {
  48. def artist := nil;
  49. def title := nil;
  50. def timesPlayed := 0;
  51. def init(artist, title) {
  52. self.artist := artist;
  53. self.title := title;
  54. self.timesPlayed := 0;
  55. };
  56. // override equality of songs to be based on their artist and title
  57. // rather than on their object identity
  58. def ==(other) {
  59. (artist == other.artist).and: {title == other.title};
  60. };
  61. def play() {
  62. timesPlayed := timesPlayed + 1;
  63. };
  64. def toString() {
  65. artist + " - " + title + "(" + timesPlayed + ")";
  66. };
  67. };
  68. // we represent song libraries as vectors, hence import the Java Vector class
  69. def Vector := jlobby.java.util.Vector;
  70. // when users share 25% of their songs, we signal a match
  71. def THRESHOLD := 25;
  72. // music players are exported using this service type
  73. deftype MusicPlayer;
  74. // this prototype is the result of loading the mobiTunes file
  75. def MobiTunesPrototype := object: {
  76. def myLib := Vector.new(); // the local user's songs library
  77. def userName := jlobby.java.lang.System.getProperty("user.name");
  78. def notifyOnMatch := { |user, percentage| nil };
  79. def init(userName, notifier := notifyOnMatch) {
  80. self.userName := userName;
  81. myLib := Vector.new();
  82. notifyOnMatch := notifier;
  83. };
  84. def notification(@texts) {
  85. system.println("[mobiTunes "+userName+"] ", @texts);
  86. };
  87. // create an the interface object that will be exported
  88. def createInterface() {
  89. // this object remains exported forever
  90. object: {
  91. // returns a session object encapsulating the state of the music library exchange process
  92. def openSession(remoteUser) {
  93. notification("opening new session for " + remoteUser);
  94. def senderLib := Vector.new(); // to store the sender's music library
  95. // garbage collect resources held by this session upon disconnection
  96. /*when: remotePlayer disconnected: {
  97. senderLib := nil;
  98. };*/
  99. /*
  100. * The session is exported using a lease for 10 minutes that is renewed
  101. * each time it receives a message. The lease is explicitly
  102. * revoked when a client terminates the exchange by calling the endExchange.
  103. */
  104. def session := renewOnCallLease: minutes(1) for: ( object: {
  105. // invoked by remote peer to transmit one single song
  106. def downloadSong(artist, title) {
  107. notification("downloaded song: " + artist + " - " + title + " from " + remoteUser);
  108. senderLib.add(Song.new(artist, title));
  109. "ok"; // tell sender that song was successfully received
  110. };
  111. // invoked by remote peer to signal that all songs have been sent
  112. // (after having invoked this method, the session object expires)
  113. def endExchange() {
  114. revoke: session; // takes the session offline
  115. notification("finished exchanging library with " + remoteUser + " sharing " + senderLib.size() + " songs");
  116. senderLib.retainAll(myLib); // senderLib := intersection(senderLib, myLib)
  117. def matchRatio := (senderLib.size() * 100 / (myLib.size()+0.01)).round();
  118. if: (matchRatio >= THRESHOLD) then: {
  119. notification("Found user ", remoteUser, " with similar taste in music (",matchRatio,"% match)");
  120. notifyOnMatch(remoteUser, matchRatio);
  121. } else: {
  122. notification("User ", remoteUser, " does not share your taste in music (",matchRatio,"% match)");
  123. };
  124. "done";
  125. };
  126. });
  127. // Notifies when a session with another remote music player expires.
  128. // Note this is a when:expired: listener registered on a server lease.
  129. when: session expired: {
  130. notification("session with " + remoteUser + " timed out.");
  131. };
  132. // return session object (which will be leased referenced) to client
  133. session;
  134. };
  135. def getSizeOfLibrary() { myLib.size() };
  136. };
  137. };
  138. // invoke this method to make the music player discover another music player
  139. def goOnline() {
  140. export: createInterface() as: MusicPlayer;
  141. // uses an ambient reference to discover one other music player
  142. def musicPlayerFuture := AmbientRefsM.ambient: MusicPlayer;
  143. when: musicPlayerFuture becomes: { |ambientReference|
  144. notification("discovered new music player: " + ambientReference);
  145. // upon discovery, ask for the creation of a new session object
  146. when: ambientReference<-openSession(userName)@Due(minutes(1)) becomes: { |session|
  147. // start to exchange libraries by pushing own library to remote peer via the session
  148. def iterator := myLib.iterator(); // to iterate over own music library
  149. def sendSongs() { // auxiliary function to send each song
  150. if: (iterator.hasNext()) then: {
  151. def song := iterator.next();
  152. when: session<-downloadSong(song.artist, song.title)@Due(leaseTimeLeft: session) becomes: { |ack|
  153. notification("sent song " + song.artist + " - " + song.title);
  154. sendSongs(); // recursive call to send the rest of the songs
  155. } catch: { |exception| notification("stopping exchange: " + exception) };
  156. } else: {
  157. // all songs sent, signal the end of the exchange
  158. session<-endExchange()@OneWayMessage;
  159. };
  160. // it is better to return nil than to return a future that will resolve the future
  161. // of the previous recursive call to sendSongs()
  162. nil;
  163. };
  164. notification("starting to send songs");
  165. sendSongs();
  166. } catch: TimeoutException using: { |e|
  167. notification("unable to open a session");
  168. };
  169. // failure handling
  170. whenever: ambientReference disconnected: {
  171. notification("music player disconnected: ", ambientReference);
  172. // do nothing upon disconnection: keeps the session alive until reconnect
  173. // alternative strategy is to clean up the session:
  174. // discard all messages still buffered by the ref + resolve their futures
  175. // with exception + respond to all future incoming messages by resolving their
  176. // future with an exception
  177. /*rebind: session to: (object: { nil } mirroredBy: (mirror: {
  178. def doesNotUnderStand(selector) {
  179. deftype DisconnectedException <: /.at.lang.types.Exception;
  180. raise: object: { nil } taggedAs: [ DisconnectedException ]
  181. };
  182. }));*/
  183. };
  184. whenever: ambientReference reconnected: {
  185. notification("music player reconnected: ", ambientReference);
  186. };
  187. };
  188. };
  189. def addSong(artist, title) {
  190. myLib.add(Song.new(artist, title));
  191. };
  192. // DEMO: run within one VM, creates two local mobiTunes applications, one within
  193. // the root actor, the other within another local actor
  194. def MobiTunesTest() {
  195. extend: /.at.unit.test.UnitTest.new("mobiTunes test") with: {
  196. def testAsyncMobiTunes() {
  197. def [fut,res] := /.at.lang.futures.makeFuture();
  198. def test := self;
  199. // expecting peerA to find peerB and to have a 66% match
  200. def peerA := /.demo.mobiTunes.new("A", { |usr, match|
  201. test.assertEquals("B", usr);
  202. test.assertEquals(66, match);
  203. res.resolve(true);
  204. });
  205. peerA.addSong("1", "AB");
  206. peerA.addSong("2", "AB");
  207. peerA.addSong("3", "A");
  208. peerA.goOnline();
  209. def otherPeerHost := actor: {
  210. def peerB := /.demo.mobiTunes.new("B");
  211. peerB.addSong("1", "AB");
  212. peerB.addSong("2", "AB");
  213. peerB.addSong("3", "B");
  214. peerB.addSong("4", "B");
  215. peerB.addSong("5", "B");
  216. peerB.addSong("6", "B");
  217. peerB.goOnline();
  218. };
  219. fut;
  220. };
  221. };
  222. };
  223. };