|
1 /* |
|
2 * Copyright (c) 2005 Nokia Corporation and/or its subsidiary(-ies). |
|
3 * All rights reserved. |
|
4 * This component and the accompanying materials are made available |
|
5 * under the terms of "Eclipse Public License v1.0" |
|
6 * which accompanies this distribution, and is available |
|
7 * at the URL "http://www.eclipse.org/legal/epl-v10.html". |
|
8 * |
|
9 * Initial Contributors: |
|
10 * Nokia Corporation - initial contribution. |
|
11 * |
|
12 * Contributors: |
|
13 * |
|
14 * Description: Managing of commands registered to Mediator |
|
15 * |
|
16 */ |
|
17 |
|
18 |
|
19 // INCLUDE FILES |
|
20 #include <e32base.h> |
|
21 |
|
22 #include "MediatorServerCommandHandler.h" |
|
23 #include "MediatorServerObjects.h" |
|
24 #include "MediatorServiceDefs.h" |
|
25 #include "Debug.h" |
|
26 |
|
27 |
|
28 // ============================ MEMBER FUNCTIONS =============================== |
|
29 |
|
30 // ----------------------------------------------------------------------------- |
|
31 // CMediatorServerCommandHandler::CMediatorServerCommandHandler |
|
32 // ----------------------------------------------------------------------------- |
|
33 // |
|
34 CMediatorServerCommandHandler::CMediatorServerCommandHandler( |
|
35 CMediatorServerObjectHandler& aObjectHandler ) |
|
36 : iObjectHandler( aObjectHandler ) |
|
37 { |
|
38 } |
|
39 |
|
40 // ----------------------------------------------------------------------------- |
|
41 // CMediatorServerCommandHandler::ConstructL |
|
42 // ----------------------------------------------------------------------------- |
|
43 // |
|
44 void CMediatorServerCommandHandler::ConstructL() |
|
45 { |
|
46 } |
|
47 |
|
48 // ----------------------------------------------------------------------------- |
|
49 // CMediatorServerCommandHandler::NewL |
|
50 // ----------------------------------------------------------------------------- |
|
51 // |
|
52 CMediatorServerCommandHandler* CMediatorServerCommandHandler::NewL( |
|
53 CMediatorServerObjectHandler& aObjectHandler ) |
|
54 { |
|
55 LOG(_L("[Mediator Server]\t CMediatorServerCommandHandler::NewL")); |
|
56 CMediatorServerCommandHandler* self |
|
57 = new( ELeave ) CMediatorServerCommandHandler( aObjectHandler ); |
|
58 |
|
59 CleanupStack::PushL( self ); |
|
60 self->ConstructL(); |
|
61 CleanupStack::Pop( self ); |
|
62 |
|
63 return self; |
|
64 } |
|
65 |
|
66 // ----------------------------------------------------------------------------- |
|
67 // CMediatorServerCommandHandler::~CMediatorServerCommandHandler |
|
68 // ----------------------------------------------------------------------------- |
|
69 // |
|
70 CMediatorServerCommandHandler::~CMediatorServerCommandHandler() |
|
71 { |
|
72 iCommandPendingList.ResetAndDestroy(); |
|
73 } |
|
74 |
|
75 // ----------------------------------------------------------------------------- |
|
76 // CMediatorServerCommandHandler::RegisterCommandListL |
|
77 // |
|
78 // (other items were commented in a header). |
|
79 // ----------------------------------------------------------------------------- |
|
80 // |
|
81 void CMediatorServerCommandHandler::RegisterCommandListL( |
|
82 TMediatorCategory aCategory, |
|
83 const RCommandList& aCommands, |
|
84 TSecureId aSecureId, |
|
85 MMediatorCommandObserver* aObserver ) |
|
86 { |
|
87 LOG(_L("[Mediator Server]\t CMediatorServerCommandHandler::RegisterCommandListL")); |
|
88 // Check that domain exists --> if not add new |
|
89 CDomain* domain = iObjectHandler.FindDomain( aCategory.iDomain ); |
|
90 if ( !domain ) |
|
91 { |
|
92 domain = iObjectHandler.AddDomainL( aCategory.iDomain ); |
|
93 } |
|
94 |
|
95 // Check that category exists --> if not add new |
|
96 TInt ignore = 0; // not used here |
|
97 CCategory* category = domain->FindCategory( aCategory.iCategory, ignore ); |
|
98 if ( !category ) |
|
99 { |
|
100 category = domain->AddCategoryL( aCategory.iCategory ); |
|
101 } |
|
102 |
|
103 // Loop through the commands and add them to list |
|
104 // Take the possible error to variable |
|
105 TInt error = KErrNone; |
|
106 TBool stop = EFalse; |
|
107 TInt index = 0; |
|
108 for ( index = 0; index < aCommands.Count() && !stop; index++ ) |
|
109 { |
|
110 CCommand* newCommand = CCommand::NewL( aCommands[index] ); |
|
111 CleanupStack::PushL( newCommand ); |
|
112 newCommand->SetSecureId( aSecureId ); // For unregistering |
|
113 newCommand->SetObserver( aObserver ); // For getting the commands |
|
114 newCommand->SetCommitState( CItem::EAdded ); // For transaction handling |
|
115 TInt addError = category->AddCommand( newCommand ); |
|
116 if ( addError ) |
|
117 { |
|
118 ERROR_TRACE(Print(_L("[Mediator] CMediatorServerCommandHandler::RegisterCommandListL: addError=%d\n"), addError ) ); |
|
119 ERROR_TRACE(Print(_L("[Mediator] Failed to add command %d to category %d of domain %d\n"), newCommand->Id(), |
|
120 aCategory.iCategory.iUid, |
|
121 aCategory.iDomain.iUid ) ); |
|
122 // in case of error, delete event and take error |
|
123 CleanupStack::PopAndDestroy( newCommand ); |
|
124 error = addError; |
|
125 stop = ETrue; |
|
126 } |
|
127 else |
|
128 { |
|
129 // Event has been added properly --> just pop |
|
130 CleanupStack::Pop( newCommand ); |
|
131 } |
|
132 newCommand = NULL; |
|
133 } |
|
134 |
|
135 TRACE(Print(_L("[Mediator Server]\t Commands registered:\n"))); |
|
136 TRACE(Print(_L("[Mediator Server]\t Success/count: %d/%d \tstatus: %d"), index, aCommands.Count(), error )); |
|
137 |
|
138 // Check error if we need to do partial recovery |
|
139 if ( error != KErrNone ) |
|
140 { |
|
141 // Remove the registered commands |
|
142 category->RollbackCommands(); |
|
143 } |
|
144 else |
|
145 { |
|
146 // Complete command registration |
|
147 category->CommitCommands(); |
|
148 |
|
149 // Use the object handler to notify command registration |
|
150 iObjectHandler.CommandsAdded( aCategory.iDomain, |
|
151 aCategory.iCategory, |
|
152 aCommands ); |
|
153 } |
|
154 // In the end leave if error --> client gets error code |
|
155 User::LeaveIfError( error ); |
|
156 } |
|
157 |
|
158 // ----------------------------------------------------------------------------- |
|
159 // CMediatorServerCommandHandler::UnregisterCommandListL |
|
160 // |
|
161 // (other items were commented in a header). |
|
162 // ----------------------------------------------------------------------------- |
|
163 // |
|
164 void CMediatorServerCommandHandler::UnregisterCommandListL( TMediatorCategory aCategory, |
|
165 const RCommandList& aCommands, |
|
166 TSecureId aSecureId ) |
|
167 { |
|
168 LOG(_L("[Mediator Server]\t CMediatorServerCommandHandler::UnregisterCommandListL")); |
|
169 CCategory* category = iObjectHandler.CategoryL( aCategory ); |
|
170 TInt error = KErrNone; |
|
171 if ( category ) |
|
172 { |
|
173 TBool stop = EFalse; |
|
174 TInt index = 0; |
|
175 |
|
176 // Loop through the list of commands and unregister those. |
|
177 for ( index = 0; index < aCommands.Count() && !stop; index++ ) |
|
178 { |
|
179 TInt commandIndex = 0; |
|
180 TCommand removeCommand = aCommands[index]; |
|
181 CCommand* commandPtr = category->FindCommand( removeCommand.iCommandId, |
|
182 commandIndex ); |
|
183 if ( !commandPtr ) |
|
184 { |
|
185 ERROR_LOG(_L("[Mediator] CMediatorServerCommandHandler::UnregisterCommandListL: Command not found\n") ); |
|
186 ERROR_TRACE(Print(_L("[Mediator] Failed to remove command %d in category %d of domain %d\n"), removeCommand.iCommandId, |
|
187 aCategory.iCategory.iUid, |
|
188 aCategory.iDomain.iUid ) ); |
|
189 error = KErrMediatorCommandNotFound; |
|
190 stop = ETrue; |
|
191 } |
|
192 else |
|
193 { |
|
194 // Found the command --> is it own registration? |
|
195 if ( commandPtr->SecureId() != aSecureId ) |
|
196 { |
|
197 ERROR_LOG(_L("[Mediator] CMediatorServerCommandHandler::UnregisterCommandListL: Secure ID mismatch\n") ); |
|
198 ERROR_TRACE(Print(_L("[Mediator] commandPtr()->SecureId()=0x%x, aSecureId=0x%x\n"), commandPtr->SecureId().iId, |
|
199 aSecureId.iId ) ); |
|
200 error = KErrMediatorSecureIdMismatch; |
|
201 stop = ETrue; |
|
202 } |
|
203 else // Should be ok to unregister |
|
204 { |
|
205 commandPtr->SetCommitState( CItem::ERemoved ); |
|
206 } |
|
207 } |
|
208 } |
|
209 |
|
210 TRACE(Print(_L("[Mediator Server]\t Commands unregistered:\n"))); |
|
211 TRACE(Print(_L("[Mediator Server]\t Processed/Total: %d/%d \tstatus: %d"), index, aCommands.Count(), error )); |
|
212 |
|
213 // Check error status --> if there's error, need to roll back |
|
214 if ( error != KErrNone ) |
|
215 { |
|
216 category->RollbackCommands(); |
|
217 } |
|
218 else |
|
219 { |
|
220 category->CommitCommands(); |
|
221 |
|
222 // loop through unregistered commands, and if they can be found from pending commands list, |
|
223 // inform the command issuer |
|
224 for ( TInt i = 0; i < aCommands.Count(); i++ ) |
|
225 { |
|
226 // ignore return value |
|
227 IssueResponse( aCategory, aCommands[i], KNullDesC8, KErrMediatorCommandRemoved ); |
|
228 } |
|
229 |
|
230 // Use the object handler to notify command registration |
|
231 iObjectHandler.CommandsRemoved( aCategory.iDomain, |
|
232 aCategory.iCategory, |
|
233 aCommands ); |
|
234 } |
|
235 |
|
236 } |
|
237 // Leave in case of error situation |
|
238 User::LeaveIfError( error ); |
|
239 } |
|
240 |
|
241 // ----------------------------------------------------------------------------- |
|
242 // CMediatorServerCommandHandler::IssueCommandL |
|
243 // |
|
244 // (other items were commented in a header). |
|
245 // ----------------------------------------------------------------------------- |
|
246 // |
|
247 void CMediatorServerCommandHandler::IssueCommandL( |
|
248 TMediatorCategory aCategory, |
|
249 MediatorService::TCommand aCommand, |
|
250 const TDesC8& aData, |
|
251 TCapabilitySet aCaps, |
|
252 MMediatorCommandResponseObserver* aObserver ) |
|
253 { |
|
254 LOG(_L("[Mediator Server]\t CMediatorServerCommandHandler::IssueCommandL")); |
|
255 CCategory* category = iObjectHandler.CategoryL( aCategory ); |
|
256 if ( category ) |
|
257 { |
|
258 // Find the command from register list |
|
259 TInt ignore = 0; |
|
260 CCommand* commandPtr = category->FindCommand( aCommand.iCommandId, ignore ); |
|
261 if ( !commandPtr ) |
|
262 { |
|
263 ERROR_TRACE(Print(_L("[Mediator] CMediatorServerCommandHandler::IssueCommandL: Command %d not found in category %d of domain %d\n"), aCommand.iCommandId, |
|
264 aCategory.iCategory.iUid, |
|
265 aCategory.iDomain.iUid ) ); |
|
266 |
|
267 User::Leave( KErrMediatorCommandNotFound ); |
|
268 } |
|
269 // Then check the capabilities && the version information |
|
270 // Capabilities are checked so that boolean ETrue is returned |
|
271 // when all parameter caps can be found from aCaps |
|
272 if ( !aCaps.HasCapabilities( commandPtr->Policy() ) ) |
|
273 { |
|
274 #ifdef _DEBUG |
|
275 for ( TInt index = 0; index < ECapability_Limit; index++ ) |
|
276 { |
|
277 TCapabilitySet commandCaps = commandPtr->Policy(); |
|
278 TBool command = commandCaps.HasCapability( (TCapability) index ); |
|
279 TBool requestor = aCaps.HasCapability( (TCapability) index ); |
|
280 if ( command && !requestor ) |
|
281 { |
|
282 ERROR_TRACE(Print(_L("[Mediator] CMediatorServerCommandHandler::IssueCommandL: capability %d missing\n"), index )); |
|
283 ERROR_TRACE(Print(_L("[Mediator] Capability error when issuing command %d in category %d of domain %d\n"), aCommand.iCommandId, |
|
284 aCategory.iCategory.iUid, |
|
285 aCategory.iDomain.iUid ) ); |
|
286 } |
|
287 } |
|
288 #endif |
|
289 User::Leave( KErrPermissionDenied ); |
|
290 } |
|
291 // Check (major) version match |
|
292 if ( aCommand.iVersion.iMajor != commandPtr->Version().iMajor ) |
|
293 { |
|
294 ERROR_TRACE(Print(_L("[Mediator] CMediatorServerCommandHandler::IssueCommandL: registered=%d, issued=%d\n"), |
|
295 commandPtr->Version().iMajor, |
|
296 aCommand.iVersion.iMajor )); |
|
297 ERROR_TRACE(Print(_L("[Mediator] Version error when issuing command %d in category %d of domain %d\n"), aCommand.iCommandId, |
|
298 aCategory.iCategory.iUid, |
|
299 aCategory.iDomain.iUid ) ); |
|
300 // There's a major version mismatch |
|
301 User::Leave( KErrMediatorVersionMismatch ); |
|
302 } |
|
303 |
|
304 // If ok, issue to command to client |
|
305 // Make the new command, set initiator and responder |
|
306 // We don't need to command data for the pending list |
|
307 CCommand* newCommand = CCommand::NewL( aCommand ); |
|
308 CleanupStack::PushL( newCommand ); |
|
309 |
|
310 newCommand->SetResponseObserver( aObserver ); |
|
311 newCommand->SetObserver( commandPtr->Observer() ); |
|
312 newCommand->SetDomain( aCategory.iDomain ); |
|
313 newCommand->SetCategory( aCategory.iCategory ); |
|
314 newCommand->SetTimeout( commandPtr->Timeout() ); |
|
315 |
|
316 // Start command timing, if it is not an infinite command |
|
317 if ( commandPtr->Timeout() != KMediatorTimeoutInfinite ) |
|
318 { |
|
319 // Start timeout timer ( request callback here ) |
|
320 newCommand->StartTimer( this ); |
|
321 } |
|
322 |
|
323 iCommandPendingList.AppendL( newCommand ); |
|
324 |
|
325 CleanupStack::Pop( newCommand ); |
|
326 |
|
327 // Now send the command to correct responder --> we have it pending |
|
328 commandPtr->Observer()->MediatorCommandL( aCategory.iDomain, |
|
329 aCategory.iCategory, |
|
330 aCommand.iCommandId, |
|
331 aCommand.iVersion, |
|
332 aData ); |
|
333 } |
|
334 } |
|
335 |
|
336 |
|
337 // ----------------------------------------------------------------------------- |
|
338 // CMediatorServerCommandHandler::CancelCommandL |
|
339 // |
|
340 // (other items were commented in a header). |
|
341 // ----------------------------------------------------------------------------- |
|
342 // |
|
343 void CMediatorServerCommandHandler::CancelCommand( |
|
344 TMediatorCategory aCategory, |
|
345 MediatorService::TCommand aCommand ) |
|
346 { |
|
347 LOG(_L("[Mediator Server]\t CMediatorServerCommandHandler::CancelCommand")); |
|
348 TBool found = EFalse; |
|
349 for ( TInt index = 0; index < iCommandPendingList.Count() && !found; index ++ ) |
|
350 { |
|
351 CCommand* commandPtr = iCommandPendingList[index]; |
|
352 if ( commandPtr ) |
|
353 { |
|
354 TUid domain = aCategory.iDomain; |
|
355 TUid category = aCategory.iCategory; |
|
356 TInt commandId = aCommand.iCommandId; |
|
357 if ( ( domain == commandPtr->Domain() ) |
|
358 && ( category == commandPtr->Category() ) |
|
359 && ( commandId == commandPtr->Id() ) ) |
|
360 { |
|
361 // We have found the correct command --> cancel it |
|
362 TRAP_IGNORE( commandPtr->Observer()->CancelMediatorCommandL( domain, |
|
363 category, |
|
364 commandId ) ); |
|
365 // Remove the command from list, free memory && stop looping |
|
366 iCommandPendingList.Remove( index ); |
|
367 delete commandPtr; |
|
368 commandPtr = NULL; |
|
369 found = ETrue; |
|
370 } |
|
371 } |
|
372 } |
|
373 } |
|
374 |
|
375 // ----------------------------------------------------------------------------- |
|
376 // CMediatorServerCommandHandler::CancelCommands |
|
377 // |
|
378 // (other items were commented in a header). |
|
379 // ----------------------------------------------------------------------------- |
|
380 // |
|
381 void CMediatorServerCommandHandler::CancelCommands( MMediatorCommandObserver* aObserver, |
|
382 MMediatorCommandResponseObserver* aResponseObserver ) |
|
383 { |
|
384 LOG(_L("[Mediator Server]\t CMediatorServerCommandHandler::CancelCommand")); |
|
385 |
|
386 // loop through list of pending commands |
|
387 for ( TInt i = iCommandPendingList.Count()-1; i >= 0; i-- ) |
|
388 { |
|
389 |
|
390 CCommand* commandPtr = iCommandPendingList[i]; |
|
391 |
|
392 if ( commandPtr ) |
|
393 { |
|
394 |
|
395 TBool commandCanceled = EFalse; |
|
396 |
|
397 // This client has registered the command, and some other client has issued it |
|
398 // -> give an error response to the issuer |
|
399 if ( aObserver == commandPtr->Observer() ) |
|
400 { |
|
401 TRAP_IGNORE( commandPtr->ResponseObserver()->CommandResponseL( commandPtr->Domain(), |
|
402 commandPtr->Category(), |
|
403 commandPtr->Id(), |
|
404 KErrMediatorCommandRemoved, |
|
405 KNullDesC8 ) ); |
|
406 commandCanceled = ETrue; |
|
407 } |
|
408 |
|
409 // This client has issued the command |
|
410 // -> inform the client which registered the command that it is canceled |
|
411 if ( aResponseObserver == commandPtr->ResponseObserver() ) |
|
412 { |
|
413 TRAP_IGNORE( commandPtr->Observer()->CancelMediatorCommandL( commandPtr->Domain(), |
|
414 commandPtr->Category(), |
|
415 commandPtr->Id() ) ); |
|
416 commandCanceled = ETrue; |
|
417 } |
|
418 |
|
419 // pending command was registered and/or issued by this client, so now it can be removed |
|
420 // from the list |
|
421 if ( commandCanceled ) |
|
422 { |
|
423 iCommandPendingList.Remove( i ); |
|
424 delete commandPtr; |
|
425 } |
|
426 |
|
427 } |
|
428 |
|
429 } |
|
430 |
|
431 } |
|
432 |
|
433 |
|
434 // ----------------------------------------------------------------------------- |
|
435 // CMediatorServerCommandHandler::IssueResponse |
|
436 // |
|
437 // (other items were commented in a header). |
|
438 // ----------------------------------------------------------------------------- |
|
439 // |
|
440 TInt CMediatorServerCommandHandler::IssueResponse( |
|
441 TMediatorCategory aCategory, |
|
442 MediatorService::TCommand aCommand, |
|
443 const TDesC8& aData, |
|
444 TInt aStatus ) |
|
445 { |
|
446 LOG(_L("[Mediator Server]\t CMediatorServerCommandHandler::IssueResponse")); |
|
447 TBool found = EFalse; |
|
448 for ( TInt index = 0; index < iCommandPendingList.Count() && !found; index ++ ) |
|
449 { |
|
450 CCommand* commandPtr = iCommandPendingList[index]; |
|
451 if ( commandPtr ) |
|
452 { |
|
453 TUid domain = aCategory.iDomain; |
|
454 TUid category = aCategory.iCategory; |
|
455 TInt commandId = aCommand.iCommandId; |
|
456 if ( ( domain == commandPtr->Domain() ) |
|
457 && ( category == commandPtr->Category() ) |
|
458 && ( commandId == commandPtr->Id() ) ) |
|
459 { |
|
460 // We have found the correct command --> ignore the leave |
|
461 TRAP_IGNORE( commandPtr->ResponseObserver()->CommandResponseL( domain, |
|
462 category, |
|
463 commandId, |
|
464 aStatus, |
|
465 aData ) ); |
|
466 // Remove the command from list, free memory && stop looping |
|
467 iCommandPendingList.Remove( index ); |
|
468 delete commandPtr; |
|
469 commandPtr = NULL; |
|
470 found = ETrue; |
|
471 } |
|
472 } |
|
473 } |
|
474 |
|
475 if ( !found ) |
|
476 { |
|
477 ERROR_TRACE(Print(_L("[Mediator] CMediatorServerCommandHandler::IssueResponse: Command %d not found in category %d of domain %d\n"), aCommand.iCommandId, |
|
478 aCategory.iDomain.iUid ) ); |
|
479 } |
|
480 |
|
481 return found ? KErrNone : KErrMediatorCommandNotFound; |
|
482 |
|
483 } |
|
484 |
|
485 // ----------------------------------------------------------------------------- |
|
486 // CMediatorServerCommandHandler::IssueResponseL |
|
487 // |
|
488 // (other items were commented in a header). |
|
489 // ----------------------------------------------------------------------------- |
|
490 // |
|
491 void CMediatorServerCommandHandler::IssueResponseL( |
|
492 TMediatorCategory aCategory, |
|
493 MediatorService::TCommand aCommand, |
|
494 const TDesC8& aData, |
|
495 TInt aStatus ) |
|
496 { |
|
497 LOG(_L("[Mediator Server]\t CMediatorServerCommandHandler::IssueResponseL")); |
|
498 |
|
499 TInt err = IssueResponse( aCategory, aCommand, aData, aStatus ); |
|
500 |
|
501 if ( err != KErrNone ) |
|
502 { |
|
503 User::Leave( KErrMediatorCommandNotFound ); |
|
504 } |
|
505 } |
|
506 |
|
507 // ----------------------------------------------------------------------------- |
|
508 // CMediatorServerCommandHandler::TimerCallBack |
|
509 // |
|
510 // (other items were commented in a header). |
|
511 // ----------------------------------------------------------------------------- |
|
512 // |
|
513 void CMediatorServerCommandHandler::TimerCallBack( TUid aDomain, |
|
514 TUid aCategory, |
|
515 TInt aCommandId ) |
|
516 { |
|
517 LOG(_L("[Mediator Server]\t CMediatorServerCommandHandler::TimerCallBack")); |
|
518 TBool found = EFalse; |
|
519 for ( TInt index = 0; index < iCommandPendingList.Count() && !found; index ++ ) |
|
520 { |
|
521 CCommand* commandPtr = iCommandPendingList[index]; |
|
522 if ( commandPtr ) |
|
523 { |
|
524 if ( ( aDomain == commandPtr->Domain() ) |
|
525 && ( aCategory == commandPtr->Category() ) |
|
526 && ( aCommandId == commandPtr->Id() ) ) |
|
527 { |
|
528 // We have found the correct command --> ignore the leave |
|
529 // Send the response to initiator with timeout error |
|
530 TRAP_IGNORE( commandPtr->ResponseObserver()->CommandResponseL( aDomain, |
|
531 aCategory, |
|
532 aCommandId, |
|
533 KErrMediatorTimeout, |
|
534 KNullDesC8 ) ); |
|
535 // And clear the responder side also |
|
536 TRAP_IGNORE( commandPtr->Observer()->MediatorCommandTimeoutL( aDomain, |
|
537 aCategory, |
|
538 aCommandId ) ); |
|
539 // Remove the command from list, free memory && stop looping |
|
540 iCommandPendingList.Remove( index ); |
|
541 delete commandPtr; |
|
542 commandPtr = NULL; |
|
543 found = ETrue; |
|
544 } |
|
545 } |
|
546 } |
|
547 } |
|
548 |
|
549 |
|
550 |
|
551 // End of File |