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.
- data/.buildpath +5 -0
- data/.gitignore +3 -0
- data/.project +17 -0
- data/.rvmrc +81 -0
- data/LICENSE +674 -0
- data/README +27 -0
- data/Rakefile +27 -0
- data/TODO +8 -0
- data/bin/addFiles.rb +35 -0
- data/bin/dummyiTunesController.rb +109 -0
- data/bin/itunesController.rb +78 -0
- data/bin/listDeadTracks.rb +45 -0
- data/bin/listNewTracks.rb +88 -0
- data/bin/recreateTrackCache.rb +31 -0
- data/bin/refreshFiles.rb +42 -0
- data/bin/removeDeadTracks.rb +31 -0
- data/bin/removeFiles.rb +42 -0
- data/bin/trackInfo.rb +50 -0
- data/itunes-controller.gemspec +24 -0
- data/jar-stuff.sh +24 -0
- data/lib/itunesController/application.rb +127 -0
- data/lib/itunesController/cachedcontroller.rb +188 -0
- data/lib/itunesController/config.rb +95 -0
- data/lib/itunesController/controller_creator.rb +29 -0
- data/lib/itunesController/controllserver.rb +583 -0
- data/lib/itunesController/database/backend.rb +41 -0
- data/lib/itunesController/database/database.rb +166 -0
- data/lib/itunesController/database/sqlite3_backend.rb +67 -0
- data/lib/itunesController/debug.rb +124 -0
- data/lib/itunesController/dummy_itunes_track.rb +40 -0
- data/lib/itunesController/dummy_itunescontroller.rb +142 -0
- data/lib/itunesController/itunescontroller.rb +90 -0
- data/lib/itunesController/itunescontroller_factory.rb +51 -0
- data/lib/itunesController/kinds.rb +138 -0
- data/lib/itunesController/logging.rb +119 -0
- data/lib/itunesController/macosx_itunescontroller.rb +204 -0
- data/lib/itunesController/platform.rb +53 -0
- data/lib/itunesController/sqlite_creator.rb +35 -0
- data/lib/itunesController/track.rb +39 -0
- data/lib/itunesController/version.rb +25 -0
- data/lib/itunesController/windows_itunescontroller.rb +145 -0
- data/test/base_server_test_case.rb +125 -0
- data/test/dummy_client.rb +44 -0
- data/test/test_server.rb +312 -0
- 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
|