itunes-controller 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/.buildpath +5 -0
  2. data/.gitignore +3 -0
  3. data/.project +17 -0
  4. data/.rvmrc +81 -0
  5. data/LICENSE +674 -0
  6. data/README +27 -0
  7. data/Rakefile +27 -0
  8. data/TODO +8 -0
  9. data/bin/addFiles.rb +35 -0
  10. data/bin/dummyiTunesController.rb +109 -0
  11. data/bin/itunesController.rb +78 -0
  12. data/bin/listDeadTracks.rb +45 -0
  13. data/bin/listNewTracks.rb +88 -0
  14. data/bin/recreateTrackCache.rb +31 -0
  15. data/bin/refreshFiles.rb +42 -0
  16. data/bin/removeDeadTracks.rb +31 -0
  17. data/bin/removeFiles.rb +42 -0
  18. data/bin/trackInfo.rb +50 -0
  19. data/itunes-controller.gemspec +24 -0
  20. data/jar-stuff.sh +24 -0
  21. data/lib/itunesController/application.rb +127 -0
  22. data/lib/itunesController/cachedcontroller.rb +188 -0
  23. data/lib/itunesController/config.rb +95 -0
  24. data/lib/itunesController/controller_creator.rb +29 -0
  25. data/lib/itunesController/controllserver.rb +583 -0
  26. data/lib/itunesController/database/backend.rb +41 -0
  27. data/lib/itunesController/database/database.rb +166 -0
  28. data/lib/itunesController/database/sqlite3_backend.rb +67 -0
  29. data/lib/itunesController/debug.rb +124 -0
  30. data/lib/itunesController/dummy_itunes_track.rb +40 -0
  31. data/lib/itunesController/dummy_itunescontroller.rb +142 -0
  32. data/lib/itunesController/itunescontroller.rb +90 -0
  33. data/lib/itunesController/itunescontroller_factory.rb +51 -0
  34. data/lib/itunesController/kinds.rb +138 -0
  35. data/lib/itunesController/logging.rb +119 -0
  36. data/lib/itunesController/macosx_itunescontroller.rb +204 -0
  37. data/lib/itunesController/platform.rb +53 -0
  38. data/lib/itunesController/sqlite_creator.rb +35 -0
  39. data/lib/itunesController/track.rb +39 -0
  40. data/lib/itunesController/version.rb +25 -0
  41. data/lib/itunesController/windows_itunescontroller.rb +145 -0
  42. data/test/base_server_test_case.rb +125 -0
  43. data/test/dummy_client.rb +44 -0
  44. data/test/test_server.rb +312 -0
  45. metadata +185 -0
@@ -0,0 +1,95 @@
1
+ #
2
+ # Copyright (C) 2011-2012 John-Paul.Stanford <dev@stanwood.org.uk>
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+ #
17
+ # Author:: John-Paul Stanford <dev@stanwood.org.uk>
18
+ # Copyright:: Copyright (C) 2011 John-Paul.Stanford <dev@stanwood.org.uk>
19
+ # License:: GNU General Public License v3 <http://www.gnu.org/licenses/>
20
+ #
21
+
22
+ require 'rexml/document'
23
+ include REXML
24
+
25
+ module ItunesController
26
+
27
+ # This class is used to save read and store the server configuration
28
+ # @example {
29
+ # <itunesController>
30
+ # <users>
31
+ # <user username="test" password="test"/>
32
+ # </users>
33
+ # </itunesController>
34
+ # }
35
+ # @attr username The username of the user used to connect to login to the server
36
+ # @attr password The password of the user used to connect to login to the server
37
+ # @attr port The port number the server is listening on
38
+ # @attr interfaceAddress The DNS/IP address of the interface the server is binding too.
39
+ class ServerConfig
40
+ attr_accessor :username,:password,:port,:interfaceAddress
41
+
42
+ # The constructor
43
+ def initialize()
44
+ @port =nil
45
+ @interfaceAddress = "localhost"
46
+ end
47
+
48
+ # A class scoped method used to read the server configuration from a file
49
+ # See the class description for a example of the configuration file format.
50
+ # If their are any problems loading the configuration, then the application is
51
+ # exited and a error message is printed to the stderr console stream.
52
+ # @param [String] configFile The file name of the configuration file
53
+ # @return [ItunesController::ServerConfig] The server configuration
54
+ def self.readConfig(configFile)
55
+ if (!File.exists? configFile)
56
+ raise("Unable to find configuration file: "+configFile)
57
+ end
58
+
59
+ config=ServerConfig.new
60
+ begin
61
+ doc=Document.new(File.new( configFile ))
62
+
63
+ rootEl = doc.root.elements["/itunesController"]
64
+ if (rootEl==nil)
65
+ raise("Unable to find parse configuartion file, can't find node /itunesController")
66
+ end
67
+ if (rootEl.attributes["port"]!=nil && rootEl.attributes["port"]!="")
68
+ config.port = rootEl.attributes["port"].to_i
69
+ end
70
+ if (rootEl.attributes["interfaceAddress"]!=nil && rootEl.attributes["interfaceAddress"]!="")
71
+ config.interfaceAddress = rootEl.attributes["interfaceAddress"]
72
+ end
73
+
74
+ doc.elements.each("/itunesController/users/user") { |userElement|
75
+ config.username=userElement.attributes["username"]
76
+ config.password=userElement.attributes["password"]
77
+ }
78
+
79
+ rescue EOFError
80
+ raise("Unable to read or parse the configuration file: " + configFile)
81
+ end
82
+
83
+ if (config.username==nil)
84
+ raise("Username name missing in configuration file")
85
+ end
86
+
87
+ if (config.password==nil)
88
+ raise("Password name missing in configuration file")
89
+ end
90
+
91
+ return config
92
+ end
93
+
94
+ end
95
+ end
@@ -0,0 +1,29 @@
1
+ #
2
+ # Copyright (C) 2011-2012 John-Paul.Stanford <dev@stanwood.org.uk>
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+ #
17
+ # Author:: John-Paul Stanford <dev@stanwood.org.uk>
18
+ # Copyright:: Copyright (C) 2011 John-Paul.Stanford <dev@stanwood.org.uk>
19
+ # License:: GNU General Public License v3 <http://www.gnu.org/licenses/>
20
+ #
21
+
22
+ module ItunesController
23
+
24
+ class ControllerCreator
25
+ def createController()
26
+ raise "ERROR: Your trying to instantiate an abstract class"
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,583 @@
1
+ #
2
+ # Copyright (C) 2011-2012 John-Paul.Stanford <dev@stanwood.org.uk>
3
+ #
4
+ # This program is free software: you can redistribute it and/or modify
5
+ # it under the terms of the GNU General Public License as published by
6
+ # the Free Software Foundation, either version 3 of the License, or
7
+ # (at your option) any later version.
8
+ #
9
+ # This program is distributed in the hope that it will be useful,
10
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
11
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12
+ # GNU General Public License for more details.
13
+ #
14
+ # You should have received a copy of the GNU General Public License
15
+ # along with this program. If not, see <http://www.gnu.org/licenses/>.
16
+ #
17
+ # Author:: John-Paul Stanford <dev@stanwood.org.uk>
18
+ # Copyright:: Copyright (C) 2011 John-Paul.Stanford <dev@stanwood.org.uk>
19
+ # License:: GNU General Public License v3 <http://www.gnu.org/licenses/>
20
+ #
21
+
22
+ require 'gserver'
23
+
24
+ require 'itunesController/version'
25
+ require 'itunesController/debug'
26
+ require 'itunesController/logging'
27
+
28
+ module ItunesController
29
+
30
+ # Used to store the command names used in the server
31
+ class CommandName
32
+ # The HELO command is used to check that the server is respoinding
33
+ HELO="HELO"
34
+ # The QUIT command caused the client to disconnect
35
+ QUIT="QUIT"
36
+ # The login command taks the format LOGIN:<username> and causes the server
37
+ # to prompt for a password. This command is used to start the user login process
38
+ LOGIN="LOGIN"
39
+ # The password command takes the format PASSWORD:<password> and is used to
40
+ # log the user in if the username and password are correct.
41
+ PASSWORD="PASSWORD"
42
+ # The file command is used to tell the server about files that should be worked on.
43
+ # These files are the path as they are found on the server. This command takes the
44
+ # format FILE:<filename>
45
+ FILE="FILE"
46
+ # This command is used to remove and registerd files that were registerd with the FILE
47
+ # command.
48
+ CLEARFILES="CLEARFILES"
49
+ # This command is used to add files registered with the FILE command to itunes then clear
50
+ # the file list.
51
+ ADDFILES="ADDFILES"
52
+ # This command is used to remove files registered with the FILE command from the itunes
53
+ # library then clear the file list.
54
+ REMOVEFILES="REMOVEFILES"
55
+ # This command will remove any files in the itunes library where the path points at a file
56
+ # that does not exist.
57
+ REMOVEDEADFILES="REMOVEDEADFILES"
58
+ # This command is used to tell iTunes to refresh the metadata from the file of files registered
59
+ # with the FILE command from the itunes library then clear the file list.
60
+ REFRESHFILES="REFRESHFILES"
61
+ # This command is used to get version information
62
+ VERSION="VERSION"
63
+ end
64
+
65
+ # Used to store the state within the server of each connected client
66
+ # @attr [Number] state The server state. Either ServerState::NOT_AUTHED, ServerState::DOING_AUTHED or ServerState::AUTHED
67
+ # @attr [Array[String]] files An array that contains the list of registered files the client is working on
68
+ # @attr [String] user The logged in user
69
+ # @attr [ItunesController::ServerConfig] config The server configuration
70
+ class ServerState
71
+
72
+ attr_accessor :state,:files,:user,:config
73
+
74
+ NOT_AUTHED=1
75
+ DOING_AUTH=2
76
+ AUTHED=3
77
+
78
+ def initialize(config)
79
+ @state=ServerState::NOT_AUTHED
80
+ @files=[]
81
+ @config=config
82
+ end
83
+
84
+ def clean
85
+ @state=ServerState::NOT_AUTHED
86
+ @files=[]
87
+ end
88
+ end
89
+
90
+ # This is the base class of all server commands.
91
+ # @abstract Subclass and override {#processData} to implement a server command
92
+ # @attr_reader [Number] requiredLoginState The required login state need for this command. Either nil,
93
+ # ServerState::NOT_AUTHED, ServerState::DOING_AUTH or
94
+ # ServerState::AUTHED. If nil then works in any login state.
95
+ # @attr_reader [String] name The command name
96
+ class ServerCommand
97
+ attr_reader :requiredLoginState,:name,:singleThreaded
98
+
99
+ # The constructor
100
+ # @param [String] name The command name
101
+ # @param [Number] requiredLoginState The required login state need for this command. Either nil,
102
+ # ServerState::NOT_AUTHED, ServerState::DOING_AUTH or
103
+ # ServerState::AUTHED. If nil then works in any login state.
104
+ # @param [ItunesController::ServerState] state The status of the connected client within the server
105
+ # @param [ItunesController::BaseITunesController] itunes The itunes controller class
106
+ def initialize(name,requiredLoginState,singleThreaded,state,itunes)
107
+ @name=name
108
+ @state=state
109
+ @itunes=itunes
110
+ @requiredLoginState=requiredLoginState
111
+ @singleThreaded=singleThreaded
112
+ end
113
+
114
+ # This is a virtual method that must be overridden by command classes. This method
115
+ # is used to perform the commands task and return the result to the server.
116
+ # @param [String] data The commands parameters if their are any
117
+ # @param io A IO Stream that is used to talk to the connected client
118
+ # @return [Boolean,String] The returned status of the command. If the first part is false, then
119
+ # the server will disconnect the client. The second part is a string message
120
+ # send to the client
121
+ def processData(data,io)
122
+ raise "ERROR: Your trying to instantiate an abstract class"
123
+ end
124
+
125
+ # This is executed when the command is popped from the job queue. It is used to force single
126
+ # threaded access to itunes
127
+ # @param [ServerState] state The state of the server
128
+ def executeSingleThreaded(state)
129
+ end
130
+
131
+ # @param [String] line A line of text recived from the client containg the command and it's parameters
132
+ # @param io A IO Stream that is used to talk to the connected client
133
+ # @return [Boolean,String] The returned status of the command. If nil, nil is returned by the command,
134
+ # then the given line does not match this command. If the first part is false,
135
+ # then the server will disconnect the client. The second part is a string message
136
+ # send to the client.
137
+ def processLine(line,io)
138
+ line = line.chop
139
+ if (line.start_with?(@name))
140
+ ItunesController::ItunesControllerLogging::debug("Command recived: #{@name}")
141
+ return processData(line[@name.length,line.length],io)
142
+ end
143
+ return nil,nil
144
+ end
145
+ end
146
+
147
+ # The HELO command is used to check that the server is respoinding
148
+ class HelloCommand < ServerCommand
149
+
150
+ # The constructor
151
+ # @param [ItunesController::ServerState] state The status of the connected client within the server
152
+ # @param [ItunesController::BaseITunesController] itunes The itunes controller class
153
+ def initialize(state,itunes)
154
+ super(ItunesController::CommandName::HELO,nil,false,state,itunes)
155
+ end
156
+
157
+ # Sends a response to the client "220 ok"
158
+ # @param [String] data The commands parameters if their are any
159
+ # @param io A IO Stream that is used to talk to the connected client
160
+ # @return [Boolean,String] The returned status of the command. If the first part is false, then
161
+ # the server will disconnect the client. The second part is a string message
162
+ # send to the client
163
+ def processData(line,io)
164
+ return true, "220 ok\r\n"
165
+ end
166
+ end
167
+
168
+ # The QUIT command caused the client to disconnect
169
+ class QuitCommand < ServerCommand
170
+
171
+ # The constructor
172
+ # @param [ItunesController::ServerState] state The status of the connected client within the server
173
+ # @param [ItunesController::BaseITunesController] itunes The itunes controller class
174
+ def initialize(state,itunes)
175
+ super(ItunesController::CommandName::QUIT,nil,false,state,itunes)
176
+ end
177
+
178
+ # Sends the response to the client "221 bye" and causes the client to disconnect
179
+ # @param [String] data The commands parameters if their are any
180
+ # @param io A IO Stream that is used to talk to the connected client
181
+ # @return [Boolean,String] The returned status of the command. If the first part is false, then
182
+ # the server will disconnect the client. The second part is a string message
183
+ # send to the client
184
+ def processData(line,io)
185
+ return false, "221 bye\r\n"
186
+ end
187
+ end
188
+
189
+ # The login command taks the format LOGIN:<username> and causes the server
190
+ # to prompt for a password. This command is used to start the user login process
191
+ class LoginCommand < ServerCommand
192
+
193
+ # The constructor
194
+ # @param [ItunesController::ServerState] state The status of the connected client within the server
195
+ # @param [ItunesController::BaseITunesController] itunes The itunes controller class
196
+ def initialize(state,itunes)
197
+ super(ItunesController::CommandName::LOGIN,nil,false,state,itunes)
198
+ end
199
+
200
+ # The line is processed to get the username, then the response "222 Password?" is sent to the
201
+ # the client to request the password.
202
+ # @param [String] data The commands parameters if their are any
203
+ # @param io A IO Stream that is used to talk to the connected client
204
+ # @return [Boolean,String] The returned status of the command. If the first part is false, then
205
+ # the server will disconnect the client. The second part is a string message
206
+ # send to the client
207
+ def processData(line,io)
208
+ if (line =~ /^\:(.+)$/)
209
+ @state.user=$1
210
+ @state.state=ServerState::DOING_AUTH
211
+ return true,"222 Password?\r\n"
212
+ end
213
+ return false, "502 ERROR no username\r\n"
214
+ end
215
+ end
216
+
217
+ # The password command takes the format PASSWORD:<password> and is used to
218
+ # log the user in if the username and password are correct.
219
+ class PasswordCommand < ServerCommand
220
+
221
+ # The constructor
222
+ # @param [ItunesController::ServerState] state The status of the connected client within the server
223
+ # @param [ItunesController::BaseITunesController] itunes The itunes controller class
224
+ def initialize(state,itunes)
225
+ super(ItunesController::CommandName::PASSWORD,ServerState::DOING_AUTH,false,state,itunes)
226
+ end
227
+
228
+ # The line is processed to get the password. Then the authentication deatials are checked.
229
+ # If they pass, then the user is now logged in and the server state changes to
230
+ # ItunesController::ServerState::AUTH with a reply to the client of "223 Logged in".
231
+ # If the autentication check fails, then a reply ""501 Incorrect username/password" is sent
232
+ # and the client is disconnected.
233
+ # @param [String] data The commands parameters if their are any
234
+ # @param io A IO Stream that is used to talk to the connected client
235
+ # @return [Boolean,String] The returned status of the command. If the first part is false, then
236
+ # the server will disconnect the client. The second part is a string message
237
+ # send to the client
238
+ def processData(line,io)
239
+ if (line =~ /^\:(.+)$/)
240
+ if (checkAuth(@state.user,$1))
241
+ @state.state = ServerState::AUTHED
242
+ return true,"223 Logged in\r\n"
243
+ end
244
+ end
245
+ return false,"501 Incorrect username/password\r\n"
246
+ end
247
+
248
+ private
249
+
250
+ # Used to check the username and password agaist the details found in the server configuartion
251
+ # @param [String] user The username
252
+ # @param [String] password The password of the user
253
+ # @return [Boolean] True if the authenction check passes, otherwise false
254
+ def checkAuth(user,password)
255
+ return (user==@state.config.username && password==@state.config.password)
256
+ end
257
+ end
258
+
259
+ # This command is used to remove and registerd files that were registerd with the FILE
260
+ # command.
261
+ class ClearFilesCommand < ServerCommand
262
+
263
+ # The constructor
264
+ # @param [ItunesController::ServerState] state The status of the connected client within the server
265
+ # @param [ItunesController::BaseITunesController] itunes The itunes controller class
266
+ def initialize(state,itunes)
267
+ super(ItunesController::CommandName::CLEARFILES,ServerState::AUTHED,false,state,itunes)
268
+ end
269
+
270
+ def processData(line,io)
271
+ @state.files=[]
272
+ return true, "220 ok\r\n"
273
+ end
274
+ end
275
+
276
+ # This command is used to add files registered with the FILE command to itunes then clear
277
+ # the file list. This will return a code 220 if it successeds, or 504 if their is a error.
278
+ class AddFilesCommand < ServerCommand
279
+
280
+ # The constructor
281
+ # @param [ItunesController::ServerState] state The status of the connected client within the server
282
+ # @param [ItunesController::BaseITunesController] itunes The itunes controller class
283
+ def initialize(state,itunes)
284
+ super(ItunesController::CommandName::ADDFILES,ServerState::AUTHED,true,state,itunes)
285
+ end
286
+
287
+ def processData(line,io)
288
+ @state.files=[]
289
+ return true, "220 ok\r\n"
290
+ end
291
+
292
+ # This is executed when the command is popped from the job queue. It is used to force single
293
+ # threaded access to itunes
294
+ # @param [ServerState] state The state of the server
295
+ def executeSingleThreaded(state)
296
+ @itunes.cacheTracks()
297
+ state.files.each do | path |
298
+ @itunes.addTrack(path)
299
+ end
300
+ end
301
+ end
302
+
303
+ # This command is used to refresh files registerd with the FILE command. It tells iTunes
304
+ # to update the meta data from the information stored in the files.
305
+ class RefreshFilesCommand < ServerCommand
306
+
307
+ # The constructor
308
+ # @param [ItunesController::ServerState] state The status of the connected client within the server
309
+ # @param [ItunesController::BaseITunesController] itunes The itunes controller class
310
+ def initialize(state,itunes)
311
+ super(ItunesController::CommandName::REFRESHFILES,ServerState::AUTHED,true,state,itunes)
312
+ end
313
+
314
+ def processData(line,io)
315
+ @state.files=[]
316
+ return true, "220 ok\r\n"
317
+ end
318
+
319
+ # This is executed when the command is popped from the job queue. It is used to force single
320
+ # threaded access to itunes
321
+ # @param [ServerState] state The state of the server
322
+ def executeSingleThreaded(state)
323
+ @itunes.cacheTracks()
324
+ state.files.each do | path |
325
+ @itunes.updateTrack(path)
326
+ end
327
+ end
328
+ end
329
+
330
+ # This command is used to remove files registered with the FILE command from the itunes
331
+ # library then clear the file list.
332
+ class RemoveFilesCommand < ServerCommand
333
+
334
+ # The constructor
335
+ # @param [ItunesController::ServerState] state The status of the connected client within the server
336
+ # @param [ItunesController::BaseITunesController] itunes The itunes controller class
337
+ def initialize(state,itunes)
338
+ super(ItunesController::CommandName::REMOVEFILES,ServerState::AUTHED,true,state,itunes)
339
+ end
340
+
341
+ def processData(line,io)
342
+ @state.files=[]
343
+ return true, "220 ok\r\n"
344
+ end
345
+
346
+ # This is executed when the command is popped from the job queue. It is used to force single
347
+ # threaded access to itunes
348
+ # @param [ServerState] state The state of the server
349
+ def executeSingleThreaded(state)
350
+ @itunes.cacheTracks()
351
+ state.files.each do | path |
352
+ @itunes.removeTrack(path)
353
+ end
354
+ end
355
+ end
356
+
357
+ # This command will remove any files in the itunes library where the path points at a file
358
+ # that does not exist.
359
+ class RemoveDeadFilesCommand < ServerCommand
360
+
361
+ # The constructor
362
+ # @param [ItunesController::ServerState] state The status of the connected client within the server
363
+ # @param [ItunesController::BaseITunesController] itunes The itunes controller class
364
+ def initialize(state,itunes)
365
+ super(ItunesController::CommandName::REMOVEDEADFILES,ServerState::AUTHED,true,state,itunes)
366
+ end
367
+
368
+ def processData(line,io)
369
+
370
+ return true, "220 ok\r\n"
371
+ end
372
+
373
+ # This is executed when the command is popped from the job queue. It is used to force single
374
+ # threaded access to itunes
375
+ # @param [ServerState] state The state of the server
376
+ def executeSingleThreaded(state)
377
+ @itunes.cacheTracks()
378
+ @itunes.removeDeadTracks()
379
+ end
380
+ end
381
+
382
+ # The file command is used to tell the server about files that should be worked on.
383
+ # These files are the path as they are found on the server. This command takes the
384
+ # format FILE:<filename>
385
+ class FileCommand < ServerCommand
386
+
387
+ # The constructor
388
+ # @param [ItunesController::ServerState] state The status of the connected client within the server
389
+ # @param [ItunesController::BaseITunesController] itunes The itunes controller class
390
+ def initialize(state,itunes)
391
+ super(ItunesController::CommandName::FILE,ServerState::AUTHED,false,state,itunes)
392
+ end
393
+
394
+ def processData(line,io)
395
+ if (line =~ /^\:(.+)$/)
396
+ @state.files.push($1)
397
+ return true, "220 ok\r\n"
398
+ end
399
+ return true, "503 ERROR expected file\r\n"
400
+ end
401
+ end
402
+
403
+ # This command is used to return version information
404
+ class VersionCommand < ServerCommand
405
+
406
+ # The constructor
407
+ # @param [ItunesController::ServerState] state The status of the connected client within the server
408
+ # @param [ItunesController::BaseITunesController] itunes The itunes controller class
409
+ def initialize(state,itunes)
410
+ super(ItunesController::CommandName::VERSION,nil,false,state,itunes)
411
+ end
412
+
413
+ def processData(line,io)
414
+ io.puts("ITunes control server:" +ItunesController::VERSION)
415
+ io.puts("Apple iTunes version: "+@itunes.getItunesVersion)
416
+ return true, "220 ok\r\n"
417
+ end
418
+ end
419
+
420
+ class Job
421
+ attr_reader :command,:state
422
+
423
+ def initialize(command,state)
424
+ @state = state
425
+ @command = command
426
+ end
427
+
428
+ def execute()
429
+ @command.executeSingleThreaded(@state)
430
+ end
431
+
432
+ def to_s
433
+ return @command.name
434
+ end
435
+ end
436
+
437
+ class CreateControllerCommand < ServerCommand
438
+ # The constructor
439
+ # @param [ItunesController::ServerState] state The status of the connected client within the server
440
+ # @param [ItunesController::BaseITunesController] itunes The itunes controller class
441
+ def initialize(controllerCreator)
442
+ super("CreateController",nil,false,nil,nil)
443
+ @controllerCreator=controllerCreator
444
+ end
445
+
446
+ def processData(line,io)
447
+ return true, "220 ok\r\n"
448
+ end
449
+
450
+ def executeSingleThreaded(state)
451
+ @controller=@controllerCreator.createController
452
+ end
453
+
454
+ def getController()
455
+ return @controller
456
+ end
457
+ end
458
+
459
+ # The TCP Socket server used to listen on connections and process commands whcih control itunes.
460
+ # see ItunesController::CommandName for a list of supported commands
461
+ class ITunesControlServer < GServer
462
+
463
+ # The constructor
464
+ # @param [ItunesController::ServerConfig] config The server configuration
465
+ # @param [Number] port The port to listen on
466
+ # @param [ItunesController::BaseITunesController] itunes The itunes controller class
467
+ def initialize(config,port,itunes)
468
+ super(port,config.interfaceAddress)
469
+ ItunesController::ItunesControllerLogging::info("Started iTunes controll server on port #{port}")
470
+ @exit=false
471
+ @jobQueue=Queue.new
472
+ @jobQueueThread=Thread.new {
473
+ loop do
474
+ processJobs()
475
+ if (@exit)
476
+ break;
477
+ end
478
+ sleep(1)
479
+ end
480
+ }
481
+ cmd=CreateControllerCommand.new(itunes)
482
+ @jobQueue << Job.new(cmd,nil)
483
+ while (cmd.getController()==nil)
484
+ sleep(1)
485
+ end
486
+
487
+ @itunes=cmd.getController()
488
+ @state=ServerState.new(config)
489
+ @commands=[
490
+ HelloCommand.new(@state,@itunes),
491
+ QuitCommand.new(@state,@itunes),
492
+ LoginCommand.new(@state,@itunes),
493
+ PasswordCommand.new(@state,@itunes),
494
+ ClearFilesCommand.new(@state,@itunes),
495
+ AddFilesCommand.new(@state,@itunes),
496
+ RemoveFilesCommand.new(@state,@itunes),
497
+ RemoveDeadFilesCommand.new(@state,@itunes),
498
+ FileCommand.new(@state,@itunes),
499
+ RefreshFilesCommand.new(@state,@itunes),
500
+ VersionCommand.new(@state,@itunes)
501
+ ]
502
+
503
+ Thread.abort_on_exception = true
504
+
505
+
506
+ start()
507
+ end
508
+
509
+ def waitForEmptyJobQueue
510
+ while (@jobQueue.size()>0)
511
+ sleep(1)
512
+ end
513
+ end
514
+
515
+ def killServer()
516
+ @exit=true
517
+ @jobQueueThread.join
518
+ join()
519
+ end
520
+
521
+ def processJobs()
522
+ job=@jobQueue.pop
523
+ ItunesController::ItunesControllerLogging::debug("Popped command and executeing #{job}")
524
+ job.execute()
525
+ end
526
+
527
+ # This method is called when a client is connected and finished when the client disconnects.
528
+ # @param io A IO Stream that is used to talk to the connected client
529
+ def serve(io)
530
+ ItunesController::ItunesControllerLogging::info("Connected")
531
+ @state.clean
532
+ io.print "001 hello\r\n"
533
+ loop do
534
+ if IO.select([io], nil, nil, 2)
535
+ data = io.readpartial(4096)
536
+ ok,op = processCommands(io,data)
537
+ if (ok!=nil)
538
+ io.print op
539
+ break unless ok
540
+ else
541
+ io.print "500 ERROR\r\n"
542
+ end
543
+ end
544
+ if (@exit)
545
+ break;
546
+ end
547
+ end
548
+ io.print "Send:002 bye\r\n"
549
+ io.close
550
+ @state.clean
551
+ ItunesController::ItunesControllerLogging::info("Disconnected")
552
+ end
553
+
554
+ # This is used to workout which command is been executed by the client and execute it.
555
+ # @param io A IO Stream that is used to talk to the connected client
556
+ # @param data The data recived from the client
557
+ # @return [Boolean,String] The returned status of the command if the command could be found.
558
+ # The first part is false, then the server will disconnect the client.
559
+ # The second part is a string message send to the client. If the
560
+ # command could not be found, then nil,nil is returned.
561
+ def processCommands(io,data)
562
+ @commands.each do | cmd |
563
+ if (cmd.requiredLoginState==nil || cmd.requiredLoginState==@state.state)
564
+ begin
565
+ previousState=@state.clone
566
+ ok, op = cmd.processLine(data,io)
567
+ if (ok!=nil)
568
+ if (cmd.singleThreaded)
569
+ @jobQueue << Job.new(cmd,previousState)
570
+ end
571
+ ItunesController::ItunesControllerLogging::debug("Command processed: #{cmd.name}")
572
+ return ok,op
573
+ end
574
+ rescue => exc
575
+ ItunesController::ItunesControllerLogging::error("Unable to execute command",exc)
576
+ raise exc.exception(exc.message)
577
+ end
578
+ end
579
+ end
580
+ return nil,nil
581
+ end
582
+ end
583
+ end