itunes-controller 0.1.0

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