Developer Area

root/apps/iphone/my.tel/trunk/Classes/RecordViewController.m @ 615

Revision 615, 19.0 kB (checked in by henri, 4 years ago)

Simplified handling of RecordCells?.

Line 
1//
2//  RecordViewController.m
3//  My.tel
4//
5//  Created by Henri Asseily on 11/18/08.
6//  Copyright 2008 Telnic Ltd.. All rights reserved.
7//
8
9#import "RecordViewController.h"
10
11#define kRowHeight 52.0
12
13static NSDictionary *imageMappings;
14
15@interface RecordViewController (PrivateMethods)
16- (void)setDidPreload:(BOOL)preload;
17@end;
18
19@implementation RecordViewController
20
21@synthesize skeysDict;
22@synthesize liDict;
23
24@synthesize apiId;
25@synthesize recordsArray;
26@synthesize uiArray;
27
28@synthesize editController;
29@synthesize delegate;
30
31#pragma mark -
32#pragma mark Designated initializer
33
34+ (RecordViewController *)controllerWithDelegate:(id <TelControllerDelegate>)aDelegate preload:(BOOL)preload {
35        RecordViewController *theC = [[[RecordViewController alloc] initWithNibName:@"TabNaptr" bundle:nil] autorelease];
36        theC.delegate = aDelegate;
37        theC.skeysDict = [NSMutableDictionary dictionaryWithContentsOfFile:
38                                          [[NSBundle mainBundle] pathForResource:@"ServiceTypes" ofType:@"plist"]];
39        theC.liDict = [NSMutableDictionary dictionaryWithContentsOfFile:
40                                   [[NSBundle mainBundle] pathForResource:@"LocationIndicators" ofType:@"plist"]];
41        theC.recordsArray = [NSMutableArray arrayWithCapacity:20];
42        theC.uiArray = [NSMutableArray arrayWithCapacity:20];
43
44        if (preload) {
45                [theC getRecords];
46        }
47        [theC setDidPreload:preload];
48        return theC;   
49}
50
51- (void)setDidPreload:(BOOL)preload {
52        didPreload = preload;
53}
54
55#pragma mark ------ Class variables access
56
57+ (NSDictionary *)imageMappings {
58        if (!imageMappings) {
59                imageMappings = [[NSDictionary dictionaryWithObjectsAndKeys:
60                                                  @"unknown.png", @"unknown",
61                                                  @"phone.png", @"voice",
62                                                  @"fax.png", @"fax",
63                                                  @"blackberry.png", @"mobile",
64                                                  @"mobile.png", @"mobileOther",
65                                                  @"sms.png", @"sms-tel",
66                                                  @"sms.png", @"mms-tel",
67                                                  @"sms.png", @"ems-tel",
68                                                  @"email.png", @"email",
69                                                  @"email.png", @"sms-email",
70                                                  @"email.png", @"mms-email",
71                                                  @"email.png", @"ems-email",
72                                                  @"http.png", @"web",
73                                                  @"http.png", @"web-ssl",
74                                                  @"voip.png", @"skype-voice",
75                                                  @"voip.png", @"aol-voice",
76                                                  @"voip.png", @"google-voice",
77                                                  @"voip.png", @"msn-voice",
78                                                  @"voip.png", @"yahoo-voice",
79                                                  @"voip.png", @"sip-voice",
80                                                  @"voip.png", @"h323-voice",
81                                                  @"im.png", @"icq",
82                                                  @"im.png", @"skype-im",
83                                                  @"im.png", @"aol-im",
84                                                  @"im.png", @"google-im",
85                                                  @"im.png", @"msn-im",
86                                                  @"im.png", @"yahoo-im",
87                                                  @"im.png", @"xmpp",
88                                                  @"ftp.png", @"ftp",
89                                                  @"username.png", @"user",
90                                                  @"info.png", @"note",
91                                                  @"goto.png", @"ntn",
92                nil] retain];
93        }
94        return imageMappings;
95}
96               
97#pragma mark ------ Standard View Controller Methods
98
99- (void)viewDidLoad {
100    [super viewDidLoad];
101        self.title = @"Contact Items";
102        self.navigationItem.rightBarButtonItem = self.editButtonItem;
103        self.tableView.allowsSelectionDuringEditing = YES;      // so that the insert cell can be selected
104        self.tableView.rowHeight = recordTableHeight = kRowHeight;
105        [[self class] imageMappings];
106}
107
108- (void)viewWillAppear:(BOOL)animated {
109        [super viewWillAppear:animated];
110        // Don't get the data if we preloaded
111        if (didPreload) {
112                didPreload = NO;
113        } else {
114                [self getRecords];
115        }
116}
117
118- (void)dealloc {
119        self.liDict = nil;
120        self.skeysDict = nil;
121        self.apiId = nil;
122        self.recordsArray = nil;
123        self.uiArray = nil;
124        self.editController = nil;
125        [imageMappings release];
126    [super dealloc];
127}
128
129#pragma mark ------ TableView Delegate and DataSource Methods
130
131- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
132    return 1;
133}
134
135- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
136        return @"All records";
137}
138
139- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
140        return [uiArray count];
141}
142
143- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
144        // UI Item keys:
145        // apiId (string), terminal (bool), enabled (bool), imageName (string),
146        // private (bool), service (string), label (string), value (string)
147       
148    static NSString *CellIdentifier = @"RecordCellIdentifier";
149   
150        RecordCell *cell;
151       
152        cell = (RecordCell *)[tableView dequeueReusableCellWithIdentifier:CellIdentifier];
153        if (cell == nil) {
154                cell = [[[RecordCell alloc] initWithStyle:UITableViewCellStyleValue2 reuseIdentifier:CellIdentifier] autorelease];
155                cell.canBeDisabled = FALSE;             // When not handling profiles, don't allow enable/disable
156   }
157        NSMutableDictionary *uiItem = [[uiArray objectAtIndex:indexPath.row] retain];
158        [cell.labelService setText:[NSString stringWithFormat:@"%@", [uiItem objectForKey:@"service"]]];
159        [cell.labelLabel setText:[NSString stringWithFormat:@"%@", [uiItem objectForKey:@"longLabel"]]];
160        [cell.labelValue setText:[NSString stringWithFormat:@"%@", [uiItem objectForKey:@"value"]]];
161       
162        // Set the icon image
163        [cell.labelImage setImage:[UIImage imageNamed:(NSString *)[uiItem objectForKey:@"imageName"]]];
164        //[cell.labelImage setBounds:CGRectMake(33.0, 5.0, 30.0, 30.0)];
165
166        // TODO: Overlay a small padlock image if the record is private
167
168        [uiItem release];
169
170        return cell;
171}
172
173- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(NSIndexPath *)indexPath {
174        if ([tableView cellForRowAtIndexPath:indexPath].editingStyle == UITableViewCellEditingStyleDelete) {
175                // Do not go into the record edit view when the table is in editing mode
176                return nil;
177        }
178        return indexPath;
179}
180
181 - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
182         [tableView deselectRowAtIndexPath:indexPath animated:YES];
183         if ([tableView cellForRowAtIndexPath:indexPath].editingStyle == UITableViewCellEditingStyleInsert) {
184                 // we're inserting a row
185                 [tableView.dataSource tableView:tableView
186                                          commitEditingStyle:UITableViewCellEditingStyleInsert forRowAtIndexPath:indexPath];
187         } else {
188                 NSDictionary *theRecord = [self selectRecordInSetUsingId:[[uiArray objectAtIndex:indexPath.row]
189                                                                                                                                   objectForKey:@"apiId"]];
190                 [self displayEditingForRecord:theRecord];
191                 
192         }
193 }
194
195- (void)setEditing:(BOOL)editing animated:(BOOL)animated {
196        [super setEditing:editing animated:animated];
197        if (editing) {
198                dataDidChange = NO;     // we won't hit the network unless the user changed something
199                self.navigationItem.leftBarButtonItem = [[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemAdd
200                                                                                                                                                                                          target:self
201                                                                                                                                                                                           action:@selector(addRecord)] autorelease];
202        } else {
203                self.navigationItem.leftBarButtonItem = nil;
204                if (dataDidChange) {
205                        dataDidChange = NO;
206                        buttonEditCount = 3;
207                        UIActivityIndicatorView *aiV = [[UIActivityIndicatorView alloc] initWithActivityIndicatorStyle:UIActivityIndicatorViewStyleWhite];
208                        [aiV startAnimating];
209                        UIBarButtonItem *savingButton = [[[UIBarButtonItem alloc] initWithCustomView:aiV] autorelease];
210                        [aiV release];
211                        self.navigationItem.rightBarButtonItem = savingButton;
212                        [self orderRecords];
213                } else {
214                        // No data changed, let's just fix the edit button
215                        [self updateEditButtonAndDoNothing:nil];
216                }
217
218        }
219}
220
221- (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath {
222        if (self.editing) {
223                return UITableViewCellEditingStyleDelete;
224        } else {
225                return UITableViewCellEditingStyleNone;
226        }
227}
228
229- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
230       
231        if (editingStyle == UITableViewCellEditingStyleDelete) {
232                dataDidChange = YES;
233                if (self.editing) {
234                        // delete immediately, do not cache deletion because we can't tell a single row swipe from multiple deletes
235                        [self deleteRecords:[uiArray objectAtIndex:indexPath.row]];
236                }
237                [uiArray removeObjectAtIndex:indexPath.row];
238                [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
239        }
240}
241
242- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
243        // Disable swipe-to-delete: don't allow row edit if table not in edit mode
244        return (tableView.editing);
245}
246
247- (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath {
248    return YES;
249}
250
251- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath
252          toIndexPath:(NSIndexPath *)toIndexPath {
253        dataDidChange = YES;
254        NSDictionary *recordToMove = [[uiArray objectAtIndex:fromIndexPath.row] retain];
255    [uiArray removeObjectAtIndex:fromIndexPath.row];
256    [uiArray insertObject:recordToMove atIndex:toIndexPath.row];
257    [recordToMove release];     
258}
259
260//- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
261//      // If there's no label, shorten the row by the label point size + 2
262//      if ([[[uiArray objectAtIndex:indexPath.row] objectForKey:@"longLabel"] length] == 0) {
263//              return (recordTableHeight - 14);
264//      }
265//      return recordTableHeight;
266//}
267
268#pragma mark ------ Open Record Edit view
269
270- (void)addRecord {
271        [self displayEditingForRecord:nil];
272}
273
274- (void)displayEditingForRecord:(NSDictionary *)aRec {
275        if (!editController) {
276                editController = [[RecordEditController controllerWithServiceKeys:skeysDict lihKeys:liDict] retain];
277                editController.delegate = self;
278        }
279        editController.record = aRec;
280        [self.navigationController pushViewController:editController animated:YES];
281}
282
283#pragma mark ------ UI Methods and Json Delegates
284
285- (NSDictionary *)selectRecordInSetUsingId:(NSString *)anId {
286        // selects the correct full record in the set from an id
287        for (NSDictionary *theRec in recordsArray) {
288                if ([[theRec valueForKey:@"apiId"] integerValue] == [anId integerValue]) {
289                        return theRec;
290                }
291        }
292        return nil;
293}
294
295- (void)updateEditButtonAndDoNothing:(NSDictionary *)parsedJson {
296        if (parsedJson) {
297                [[MyTelConnect sharedInstance] doNothing:parsedJson];
298        }
299        self.navigationItem.rightBarButtonItem = self.editButtonItem;
300}
301
302- (void)updateUITableWithJson:(NSDictionary *)parsedJson {
303#ifdef DEBUG
304        NSLog(@"%@", [parsedJson descriptionInStringsFileFormat]);
305#endif
306       
307        [self.recordsArray removeAllObjects];
308        [self.uiArray removeAllObjects];
309        if ([[parsedJson valueForKey:@"success"] integerValue] == 1) {
310                [self.recordsArray setArray:(NSArray *)[parsedJson valueForKey:@"recordList"]];
311                for (NSDictionary *aRec in self.recordsArray) {
312                        [self.uiArray addObject:[self uiItemFromJsonItem:aRec]];
313                }
314                [self.delegate dataDidChangeInController:self];
315                [self.tableView reloadData];
316        } else {
317                [self.tableView reloadData];
318                [Record throwJsonErrorAlert:parsedJson];
319        }
320       
321}
322
323- (NSMutableDictionary *)uiItemFromJsonItem:(NSDictionary *)jsonItem {
324        // Method that creates a dictionary to display in a cell row
325        // Sample jsonItem:
326        //  {apiId: 23, terminal: true,
327        //      serviceKeys: ["voice", "fax"], value: "+34.34343443",
328        //      groups: [1, 3],
329        //      profiles: [12, 23], global: false,
330        //      locations: ["x-home", "x-mobile"], editable: true,
331        //  longLabel: "Some text to describe my home number"}
332#ifdef DEBUG
333        NSLog(@"Naptr Rec in JSON: %@", [jsonItem descriptionInStringsFileFormat]);
334#endif
335       
336        NSMutableDictionary *uiItem;
337        if (!jsonItem) {
338                return NULL;
339        }
340        uiItem = [NSMutableDictionary dictionaryWithCapacity:7];
341       
342        // set apiId
343        [uiItem setObject:(NSString *)[jsonItem objectForKey:@"apiId"] forKey:@"apiId"];
344       
345        // set naptr value
346        if ([[jsonItem objectForKey:@"value"] isKindOfClass:[NSNull class]])
347                [uiItem setObject:@"" forKey:@"value"];
348        else
349                [uiItem setObject:(NSString *)[jsonItem objectForKey:@"value"] forKey:@"value"];
350
351        // set label
352        if ([[jsonItem objectForKey:@"longLabel"] isKindOfClass:[NSNull class]])
353                [uiItem setObject:@"" forKey:@"longLabel"];
354        else
355                [uiItem setObject:(NSString *)[jsonItem objectForKey:@"longLabel"] forKey:@"longLabel"];
356       
357        // set public/private
358        if ([(NSArray *)[jsonItem objectForKey:@"groups"] count] > 0)
359                [uiItem setValue:@"1" forKey:@"private"];
360        else
361                [uiItem setValue:@"0" forKey:@"private"];       
362       
363        // test nonterminal and return
364        if ([jsonItem objectForKey:@"terminal"] && [[jsonItem objectForKey:@"terminal"] integerValue] == 0) { //nonterminal
365                [uiItem setValue:@"0" forKey:@"terminal"];
366                [uiItem setObject:[self.skeysDict objectForKey:@"ntn"] forKey:@"service"];
367                [uiItem setObject:(NSString *)[[RecordViewController imageMappings] objectForKey:@"ntn"] forKey:@"imageName"];
368                return uiItem;
369        }
370        [uiItem setValue:@"1" forKey:@"terminal"];
371               
372        // set service + LI string, and image as well
373        NSMutableString *sPart = [NSMutableString stringWithString:@""];
374        NSMutableString *liPart = [NSMutableString stringWithString:@""];
375        BOOL couldbeMobilePhone = NO;
376        if ([jsonItem objectForKey:@"serviceKeys"]) {
377                NSUInteger i, count = [[jsonItem objectForKey:@"serviceKeys"] count];
378                for (i = 0; i < count; i++) {
379                        NSString *anS = [[jsonItem objectForKey:@"serviceKeys"] objectAtIndex:i];
380                        if (i == 0) {
381                                // set the icon image to the first service key
382                                if ([imageMappings objectForKey:anS]) {
383                                        [uiItem setObject:[[RecordViewController imageMappings] objectForKey:anS] forKey:@"imageName"];
384                                } else {
385                                        [uiItem setObject:[[RecordViewController imageMappings] objectForKey:@"unknown"] forKey:@"imageName"];
386                                }
387                                // Check for mobile phones (yes mobile phones SHOULD have been their own
388                                // type, but blame the IETF for this ridiculous state of things)
389                                if ([anS isEqualToString:@"voice"])
390                                        couldbeMobilePhone = YES; // Could be a mobile. see below
391                        } else {
392                                [sPart appendString:@" & "];
393                        }
394                        if ([self.skeysDict objectForKey:anS]) {
395                                [sPart appendString:[self.skeysDict objectForKey:anS]];
396                        } else {
397                                [sPart appendString:anS];
398                        }
399                }
400        }
401        if ([jsonItem objectForKey:@"locations"]) {
402                NSUInteger i, count = [[jsonItem objectForKey:@"locations"] count];
403                for (i = 0; i < count; i++) {
404                        if (i > 0)
405                                [liPart appendString:@" & "];
406                        NSString *anLI = (NSString *)[[jsonItem objectForKey:@"locations"] objectAtIndex:i];
407                        [liPart appendString:[self.liDict objectForKey:anLI]];
408                        if ([anLI isEqualToString:@"x-mobile"] && couldbeMobilePhone) {
409                                [uiItem setObject:[[RecordViewController imageMappings] objectForKey:@"mobile"] forKey:@"imageName"];
410                        }
411                }
412        }
413        [uiItem setObject:[NSString stringWithFormat:@"%@ %@", liPart, sPart] forKey:@"service"];
414       
415        return uiItem;
416}
417
418#pragma mark ------ Data management
419
420- (void)getRecords {
421        //inputRecord = {
422        //domainName: "stan.cartman.tel",
423        //profileId: 23,  /** use -1 to get all Records of the domain */
424        //includeNonTerminals: true
425        //};
426
427//      successResult = {
428//      success: true,
429//      domainId: 12,
430//      recordList: [{apiId: 23, terminal: true,
431//      serviceKeys: ["voice", "fax"], value: "+34.34343443",
432//      label: "my home number", groups: [1, 3],
433//      profiles: [12, 23], global: false,
434//      locations: ["x-home", "x-mobile"], editable: true},
435//      longLabel: "Some text to describe my home number",
436//      {apiId: 12, terminal: true, serviceKeys: ["web"],,
437//      value: "www.telnic.org", label: "my homepage",
438//      groups: [3], profiles: [], global: true,
439//      locations: ["x-home"], editable: true},
440//      {apiId: 12, terminal: false, value: "stan.cartman.tel.",
441//      groups: [3], profiles: [], global: true}],
442//      actionMessages: ["request successful",
443//                                       "2nd message here",
444//                                       "3rd message here"]
445//      };
446//     
447        Record *conn = [[[Record alloc] init] autorelease];
448        [conn setTheDelegate:self];
449        [conn setActionSel:@selector(updateUITableWithJson:)];
450        [conn setConnectionUrl:[conn urlFromAction:@"getrecordlist"]];
451       
452        NSMutableDictionary *requestData = [[NSMutableDictionary dictionaryWithCapacity:3] retain];
453        [requestData setObject:[self.delegate domain] forKey:@"domainName"];
454        [requestData setObject:@"-1" forKey:@"profileId"];
455        [requestData setObject:@"true" forKey:@"includeNonTerminals"];
456       
457        [conn setPayload:requestData];
458        [conn performLookup:TRUE];
459        [requestData release];
460}
461
462- (void)storeRecord:(NSDictionary *)aRec {
463//      inputRecord = {
464//      domainName: "cartman.tel",
465//      apiId: 0,
466//      serviceKeys: ["voice", "fax"],
467//      value: "+34.34343443",
468//      label: "my home number",
469//      groups: [1, 2, 3],
470//      profiles: [12, 23, 47],
471//      locations: ["x-home", "x-mobile"]
472//      };
473//     
474//      successResult = {
475//      success: "true",
476//      actionMessages: ["record stored successfully",
477//                                       "2nd message here",
478//                                       "3dmessage here"],
479//      apiId: 17
480//      };
481       
482        Record *conn = [[[Record alloc] init] autorelease];
483        [conn setTheDelegate:self];
484        [conn setActionSel:@selector(getRecords)];
485        [conn setConnectionUrl:[conn urlFromAction:@"storerecord"]];
486       
487        NSMutableDictionary *requestData = [[NSMutableDictionary dictionaryWithDictionary:aRec] retain];
488       
489        [conn setPayload:requestData];
490        [conn performLookup:TRUE];
491        [requestData release];
492}
493
494- (void)deleteRecords:(NSDictionary *)aRec {
495//      inputRecords = {
496//      domainName: "cartman.tel",
497//      apiIds: [23, 2, 22]
498//      };
499//     
500//      successResult = {
501//      success: true,
502//      actionMessages: ["records deleted"
503//                                       "2nd message here",
504//                                       "3rd message here"]
505//      };
506
507        if (!aRec)
508                return;
509        NSArray *idsToDelete;
510        idsToDelete = [NSArray arrayWithObject:[aRec objectForKey:@"apiId"]];
511        if ([idsToDelete count] == 0)   // Nothing to delete
512                return;
513        [idsToDelete retain];
514        Record *conn = [[[Record alloc] init] autorelease];
515        [conn setTheDelegate:[MyTelConnect sharedInstance]];
516        [conn setActionSel:@selector(doNothing:)];
517        [conn setConnectionUrl:[conn urlFromAction:@"deleterecords"]];
518       
519        NSMutableDictionary *requestData = [[NSMutableDictionary dictionaryWithCapacity:2] retain];
520        [requestData setObject:[self.delegate domain] forKey:@"domainName"];
521        [requestData setObject:idsToDelete forKey:@"apiIds"];
522       
523        [conn setPayload:requestData];
524        NSDictionary *parsedJson = [conn performLookup:FALSE];
525        [requestData release];
526        [idsToDelete release];
527        if ([[parsedJson valueForKey:@"success"] integerValue] == 0) {
528                [Record throwJsonErrorAlert:parsedJson];
529        }
530        [self getRecords];
531}
532
533
534- (void)orderRecords {
535//      inputRecords = {
536//      domainName: "cartman.tel",
537//      includeNonTerminals: true
538//      apiIds: [23, 31, 1, 45, 2, 22]
539//      };
540//     
541//      successResult = {
542//      success: true,
543//      actionMessages: ["records reordered"
544//                                       "2nd message here",
545//                                       "3rd message here"]
546//      };
547        if ([uiArray count] == 0) {
548                buttonEditCount--;
549                [self performSelector:@selector(updateEditButtonAndDoNothing:) withObject:nil];
550                return;
551        }
552        Record *conn = [[[Record alloc] init] autorelease];
553        [conn setTheDelegate:self];
554        [conn setActionSel:@selector(updateEditButtonAndDoNothing:)];
555        [conn setConnectionUrl:[conn urlFromAction:@"orderrecords"]];
556       
557        NSMutableDictionary *requestData = [[NSMutableDictionary dictionaryWithCapacity:3] retain];
558        [requestData setObject:[self.delegate domain] forKey:@"domainName"];
559        [requestData setObject:@"true" forKey:@"includeNonTerminals"];
560       
561        NSMutableArray *apiIdArray = [NSMutableArray arrayWithCapacity:[uiArray count]];
562        for (NSDictionary *theRow in uiArray) {
563                [apiIdArray addObject:[theRow objectForKey:@"apiId"]];
564        }
565        [requestData setObject:apiIdArray forKey:@"apiIds"];
566
567        [conn setPayload:requestData];
568        [conn performLookup:TRUE];
569        [requestData release];
570        buttonEditCount--;
571}
572
573#pragma mark -
574#pragma mark TelControllerDelegate methods
575
576- (void)dataDidChangeInController:(UIViewController *)controller {
577        if ([controller isMemberOfClass:[RecordEditController class]])
578                [self getRecords];
579}
580
581- (NSString *)domain {
582        // request the domain from the calling controller
583        return [self.delegate domain];
584}
585
586@end
587
Note: See TracBrowser for help on using the browser.
Telnic
Search This Site
Partners
Neustar
ICANN
Main site | WHOIS | Sell .tel | FAQ | Archived Site | About Telnic | Contact Us