KB_5K0077: ISEview – An iPhone/iPad app for Xiotech ISE Cortex

advertisement
KB_5K0077: ISEview – An iPhone/iPad app for Xiotech ISE Cortex Monitoring.
Description:
This knowledgebase article summarizes the ISEview application and in the process will hopefully give
you pointers on how to modify the application for your own purposes. Full sources are available with
the Xiotech Cortex SDK.
What is ISEview?
ISEview is a portable monitoring tool for Xiotech Emprise 5000 (ISE) storage arrays. It has been
designed specifically for the iPhone and iPad as two separate applications with a common look and
feel. The tool is designed to provide a quick status view of up to 16 different ISEs. The ISEs can be
local or remote…the only requirement is that they are accessible over whichever LAN connection the
iPhone or iPad has access to (either direct via WiFi, over the internet via 3G, or via VPN.)
ISEview allows monitoring of both status (environmental, states of FRU components, and component
info) as well as performance at both the overall ISE level and per controller. It also features an API
Browser that allows potential developers to view part of the Restful XML responses obtained from
Cortex on an ISE as well as a simple analysis screen that summarizes and ISE in an easy to read
fashion.
THE PROGRAM:
ISEview was designed in somewhat patchwork fashion over a 3 week period as the two developers
were also learning the SDK and Objective C at the same time. The following books provided good
insight during this process, with excellent examples that helped us along the way of gathering XML
data and building an in-memory XML tree for browsing purposes:


The iPhone Developer’s Cookbook: Building Applications with the iPhone SDK: Erica Sadun
Sams Teach Yourself iPhone Application Development in 24 hours: John Ray and Sean Johnson
Also, iPhone requirements are 3.1.3, iPad requires compiling with SDK 3.2.
The ISEview app is best thought of as having three ‘viewing’ levels. These are referred to as the Top
level, the MidView level, and the API Browser level. The following is an image of the Top Level.
The top level simply shows the various ISEs that the App has been configured to watch. The IP
addresses and ISE name are displayed here. This level is also where you use the normal ‘edit’/”“/”+”/’done’ logic to remove and add new ISEs to the list as shown below:
The logic is designed to show you immediately upon entering an IP address what the Serial number
and alternate IP address is.
This method also prevents you from entering a bad IP address and then having to delete it later when
you determine that it isn’t correct.
The MidView level is where the action is. Performance information is updated in this screen roughly
once every 2 seconds. The ISE and MRC performance for IOPS and MB/S are shown via both a
‘log10’ line display as well as raw data numbers. The log10 approach was taken to give a better feeling
for relative change no matter if you are doing high IO or low IO. This avoids the need for doing axis
scaling. Here also you will see the status of all the components update dynamically (updates are upon
entry and then every 120 seconds after that…the assumption being that component states just don’t
change that often). Below is the MidView.
Also, the MidView sports active buttons. Any of the MRC, Battery, Power Supply, or Datapac buttons
can be pressed to get more info. Below Datapac2 was pressed to see why it was yellow. In this case the
handle was left open and needs to be screwed down.
Pressing the About button will return the following:
And pressing the Analyze button will generate something like the following:
The analyze screen is intended to provide a high level view into the system (i.e. rather than just
jamming some qdepth and latency numbers it combines a lot of performance information from various
sources and tries to reduce that to concepts like ‘idle’, ‘busy’, ‘too busy’, and ‘balanced’. Some wiggle
room there for improvement and enhancements. This screen is also kept ‘text only’ so that it is easy to
copy and past the results into an email if you so choose.
NOTE: any screen can be copied ‘hot’ and then emailed on an iPhone or iPad by simply pressing the
‘square’ button at the bottom of the device (shown at the bottom of the picture above) and the top
‘power on/off’ button at the same time and then releasing them. The picture goes in your normal
pictures folders from where you can then email it. One of those undocumented iPhone features that
carried into the iPad.
Finally, the API Browser View was something of an afterthought, even though it was our first ‘view’
that we played with. Since all the data was gathered into memory by this logic, we decided to keep the
logic in the program code and allow users to browse through the restful interface ‘/all’ option. This
option contains much of the infrastructure, status, and performance data that you will need for
programming, although often it is easier on the system if you just ask for what you need (i.e. as the
MidView does to get performance information at a fast rate.)
Pressing the API Browser button will cause the button to ‘depress’ and after a few seconds you will be
taken to a table view screen that shows what level of the Restful hierarchy you are at (in this case you
are in controller/1 under the controllers section) along with a backpointer to take you up a level in the
hierarchy. There are also lines that end with a > to indicate that pressing on this line will take you
down the hierarchy. Very simple actually.
So, the displays are all neat, but the following section will delve into the guts of the iPhone/iPad
application code itself and that is even neater (now that we have it working). But, before jumping into
that, here is a snippet of documentation from the STAT5000 document that might help you to better
get a feel for how we ‘grab’ the correct information via Restful. If you have an ISE available right
now do the following:
1) From a web browser, enter either one of your ISEs IP addresses into the web browser address bar
followed by a /query (i.e. 10.10.20.30/query).
2) You will get something that looks like the following:
<?xml version="1.0" encoding="ISO-8859-1" ?>
- <array self="http://10.20.54.32/storage/arrays/1BC10089">
<status value="0" string="Operational" />
<globalid>1BC10089</globalid>
<id>20000014C367348C</id>
<name>ISE</name>
<serialnumber>1BC10089</serialnumber>
<model>ISE1400</model>
<vendor>XIOTECH</vendor>
...and so on…
3) Cut and paste the http string into the browser and press return. You will be requested for the
Adminstrator login and password. NOTE: the application has the administrator default password
hard coded into it.
4) Take other http:// lines that you see in the resulting page and try looking at those
Now things will make more sense when you look at what this code does (i.e. it is a big parsing,
analysis, and display engine for some of this data.)
Also, ISEview is designed without any complex schemas or data structures. You will see a pretty flat
structure when you look at the code and that was done entirely to keep things readable and ‘safe’. No
pointers, strings are all essentially copy safe, no complex conditional code that you have to reread a
number of times, etc. Also, this program is safe because it is ONLY monitoring an ISE…there is no
configuration code active in this example at all (i.e. no volume deletes). During the entire development
and subsequent testing of ISEview not a single ‘impact’ was ever seen on an ISE due to running this
program.
Oh yes, the iPad. Everything above applies to the iPad application. We just scale things up when
needed and take advantage of additional lines in other screens to see more of the data with less
scrolling. Future versions of the iPad will have more detail and the iPad has a faster processor so we
will take advantage of that as well. Here is a screen capture of the iPads midview:
As you can see…plenty of real-estate left.
Now for the code. Sources for ISEview are currently about 3400 lines of code. The code that is
provided with the SDK might vary a bit from this document’s contents but hopefully the gist will carry
forwards. The following section will go through the code from top to bottom and after that section will
be one final section that summarizes our first impressions of working with the iPhone SDK (at least
the positive ones).
The Code:
This section is presented in overview fashion, first summarizing the files themselves that
compose the code, then describing the general program flow for the entire application, and
then discussing the various areas of the code that had a higher learning curve (i.e. were harder
to design).
The files:
The ISEview code is composed of these source files:
Classes
TreeNode.h TreeNode.m XMLParser.h XMLParser.m db_build.h db_build.m MyAppDelegate.h MyAppDelegate.m UIimage+Resizing.h UIimage+Resizing.m MyListController.h MyListController.m ISEkclass.h ISEkclass.m iseCfgFromXmlQuery.h iseCfgFromXmlQuery.m iseCfgFromXmlQuery.xibMidView.h MidView.m MidView.xib TreeBrowser.h TreeBrowser.m analyzeView.h analyzeView.m analyzeView.xib aboutView.h aboutView.m aboutView.xib Other Sources
main.m ISEview_Prefix.pch -
Defines the TreeNode object which is extensively by the XML parser
Methods used for creating, searching, adding and deleting a TreeNode object
Defines the XMLParser stack array
Methods that parse XML and store the results in TreeNode and ISE structures
Defines the ISE structure used for storing information retrieved for an ISE
Contains the sharedInstance definition for the ISE structure
Defines the window and view controllers used by ISEview
Creates the initial window, table view controller, and navigation controller
Routines available for manipulating and resizing images
Routines available for manipulating and resizing images
Defines the routines for displaying and managing the top view
Contains the Methods used for displaying and managing the top view
Defines the object used to represent an ISE that is known to ISEview
Methods used to create and init and ISEkclass object
Defines the UI objects ad routines used to discover and ISE
Methods used for discovering an ISE on the network
The ISE discovery interface builder file
Responsible for displaying information about an ISE. It also instantiates an
NSTimer and NSoperationQueue to enable retrieval of data from the ISE
Methods to support managing the display of information for an ISE
Interface builder file for the MidView
Defines the TreeBroswerController (UITableViewController) for displaying the
API view of XML data
Methods for dealing with the API viewer table view
Defines objects associate with the AnalyzeView
Methods used for displaying the analyzeView
Interface builder file for analyze view
Defines objects used for the aboutView
Methods used in the aboutView UI
Interface builder file for the aboutView
nothing to see here so move along. It is important though
Prefix header for table view
And these files compose the rest of the application bundle:
Resources
gray2_btn.png blue2_btn.png red2_button.png green2_btn.png yellow2_btn.png 7_BLUE.png 6_ORANGE.png 5_GREEN.png 4_RED.png 3_YELLOW.png 2_GRAY.png green.jpg ISEs.plist info.plist Icon.png Default.png -
For uninitialized buttons background
not used
For button background displaying a failure state
For button background displaying an optimal state
For button background displaying a warning state
Blue X not currently used
Orange X not currently used
Green X used in top view
Red X not currently used
Yellow X not currently used
Gray X not currently used
Used for screen background in MidView
persistant data describing ISEs
properties list file required by apple
application icon
splashscreen
Resources-iPad
iseCfgFromXmlQuery.xib MidView.xib analyzeView.xib aboutView.xib -
interface builder ipad file
interface builder ipad file
interface builder ipad file
interface builder ipad file
Frameworks
UIKit.framework Foundation.framework CoreGraphics.framework -
Apple SDK
Apple SDK
Apple SDK
Products
ISEview.app iphone application
ISEview-iPad.app - ipad application
The Code Flow:
The following is a high level flow description for the ISEview code.
1) Init/Start
a. ISEview starts by creating a navigation controller and setting up the top table view
in MyAppDelegate.m.
b. From there initialization continues in MyListController.m where data is read from
the properties list file ISEs.plist and is used to populate ISEview’s ISE table.
ISEs.plist contains static data defining the ISE that ISEview is configured to
monitor.
i. It contains an array of dictionary items which describe an ISE in enough
detail such that it can be accessed over the network using the CorteX
Restful interface. This includes the IP address of both MRCs, the ISE
name, and the ISE serial number.
ii. Additional query URI are defined in the ISEs.plist file for convenience as
well.
2) Now the top level ISE table should be displayed. At this point the runloop monitors the
application for the users next action which may consist of discovering and adding another
ISE, deleting an ISE, or changing the order of the ISE in the table.
a. If the add ISE button (“+”) is pressed a modal view controller is created and
displayed. This dialogue allows the user to enter an ISEs hostname or IP address
and discover a new (or an already existing ISE). Upon successful discovery the
ISE will be added to the ISEs.plist file and display in the top table view.
b. If the Edit button is pressed, the normal iPhone logic for removing an entry is used
(i.e. each entry will show an ‘-‘ and then when selected a DELETE button will
appear that the user can then select. This logic is in [KARL]. This is also where
the user can change the order.
c. If just the entry itself is pressed the user is immediately moved to the Midview.
3) Midview - An ISE has been selected from the top view table which brings up midView.
midView performs a number functions which allow the display of the specific RESTful
data for the given ISE.
a. The first item it creates is an operation queue. The operation queue allows
RESTful queries to be performed in the background, but synchronously to avoid
the possibility of multiple XML parsing operations being performed
simultaneously. The parser used in ISEview is a singleton and there can be only
one XML object parsed at any given time.
b. Next, it starts an NSTimer which is used to control the periodic retrieval and
display of data for the ISE. Performance data is retrieved every two seconds and
the status data is fetched once a minute. Performance data drives the real-time
MRC and ISE IOPS and MB/S bars on the screen and is also used when analyzing
the ISE. Status data is used to display the colored button states and the content
when the status buttons are pressed. The first time thru both the performance and
the status data is fetched immediately.
c. As previously stated the performance data for an ISE is fetched every two seconds.
This is accomplished by placing the retrieval job on the NSoperation queue. This
job once executed will
i. fetch the XML from the ISE,
ii. parse it into usable data, and
iii. call a method to actually display the data.
d. The status data retrieved every 60 seconds is handled in the same way as the
performance data.
e. In parallel with the data retrieval, the runloop monitors user actions. Pressing one
of the MRC, PS, BAT, or Datapac buttons will bring up an alert view with the
relevant data associated with that object. The About, Analyze, and API Viewer
buttons are made available for selection and each have their own view (see file
list). When selected special care was taken to pause the operation queue and
prevent the display of data while that view is not visible. Since alert views sit on
top of the midView we do continue to allow performance data to update in the
image below the popup.
f. If the user selects to go back to the top view from the midView the operation
queue will be destroyed and the NStimer removed.
4) AboutView - About view simply displays the version number and information about the
ISEview program
5) AnalyzeView - The analyze view performs a high level analysis of the data retrieved for a
given ISE and displays it in an easy to read fashion.
6) APIbrowser - The API browser performs an 'all' query for a given ISE, gathers and parses
the results (building a tree structure in the process), and then allows the user to browse up
and down an automatically constructed table view of this data.
a. Invoking this operation can take a few seconds so special care was done in
handling actions after this button is pressed.
i. First, a query for 'all' is placed into the operation queue where it will be
handled in the order it was received. If there are other jobs still pending in
the queue they will be handled first.
ii. Then the API browser button is disabled so that it is impossible to press it
more than once.
b. Once the API browser job is run from the queue, it will fetch the data from the
ISE, parse it, and finally call a method to display it in a new table view.
c. Pressing back to go back into the top view after pressing API browser but before
the API browser view comes up will cause the operation to cancel.
The neat stuff:
In more detail, the above code flow can be broken down into the following main
sections…each of which took a bit of research and trial and error to get right:









Gathering URL data (how we get all that Restful XML data)
XML Parsing (how we decided to parse the XML data…yes there are multiple ways)
XML tree node logic (how we keep info around to view hierarchically)
Data Structure Construction (how we keep info around to analyze)
Timer logic (how to do things in the background)
Alert View (how to show data quickly upon a button press)
Add/remove/sort ISE list logic
o Dynamic query while adding an ISE (validate on the fly)
Custom Button Creation/Usage (i.e. making things look nice)
Dynamic query while adding an ISE (validate on the fly)
The following sections will discuss these in more detail.
Gathering URL data
This is what iPhones and iPads are made for. Unfortunately there are several methods that can
be used and of course some are better than others. The logic we ended up using was the
NSURLRequest method as shown in the following code snippet:
[UIApplication sharedApplication].networkActivityIndicatorVisible = YES;
NSError *error;
NSURLResponse *response;
NSURLRequest *request = [NSURLRequest requestWithURL:url
cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:12];
NSData *data = [NSURLConnection sendSynchronousRequest:request
returningResponse:&response error:&error];
if(error) {
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
[pool drain];
return nil;
}
NSXMLParser *parser = [[NSXMLParser alloc] initWithData:data];
[UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
The advantage to this logic is that we could both use it in a background mode, but also specify
timeouts to better handle error conditions. This logic is all in file XMLParser.m.
XML Parsing
Of course, if gathering URL data is high on an iPhones list of things to do, then close behind
is XML parsing. Luckily there are only 2 ‘common’ ways of doing this. A SAX (Simple Api
for Xml) parser or a DOM (Document Object Module) parser. We decided on a hybrid
approach, using SAX parser (NSXMLParser), mostly because it is included as the default with
the iPhone SDK, but building our own tree structure and using that in some cases (i.e. to
browse the API structures).
SAX parsers are indirect callback parsers (i.e. the parser walks through the entire XML file
and ‘calls’ routines (that you write) whenever it starts an element, has data that is part of an
element, or sees the end of an element…i.e hitting <element>data</element> would generate
three separate calls.
DOM parsers just stick all the XML in a buffer/tree and let you grope through it picking out
what you want after the fact.
The logic for XML Parsing is in the module named XMLParser.m and much of the logic
resides in the three callback methods (didStartElement, foundCharacters, and didEndElement)
which in turn create the node tree and stuff our data structures. Most of our logic then simply
uses the data that is built up from these data structures, although we did include an example of
a DOM search in our code as well. This is in the logic that is used to look at the query that
comes back in response when you are entering a new ISE’s IP address. There we know we
just want the ip addresses and the ISE name and SN so we ‘grope’ for those in the returned
data buffer. This is covered in more detail later on in this document.
XML TreeNode Logic
Got all that XML data and want to let people ‘view’ it? The treenode logic comes next.
- (TreeNode *) init
{
if (self = [super init])
{
self.key = nil;
self.leafvalue = nil;
self.parent = nil;
self.children = nil;
}
return self;
}
+ (TreeNode *) treeNode
{
return [[[self alloc] init] autorelease];
}
The logic used is similar to other treenode implementations, although it is based heavily on
code examples from Erica Sadun’s book “The iPhone Developers’ Cookbook”.
Data Structure Construction (how we keep info around to analyze)
Examples are included in the code for both normal C data structure usage as well as using
shared instances to update an objects NSString and NSObject structures. We chose the C data
structure usage simply because it was MUCH less typing and mapped easily into our existing
data structure logic. Example
if(ise[cur_ise].parse_level!=LVL_DISKS && ise[cur_ise].parse_level!=LVL_POOL) {
if([elementName isEqualToString:@"medium"]) {
elementName=@"medium: Datapac";
ise[cur_ise].parse_level=LVL_DP;
NSString *wh_cn=[attributeDict objectForKey:@"self"];
NSRange sl = [wh_cn rangeOfString:@"/" options: (NSBackwardsSearch)];
if(sl.location!=NSNotFound) {
NSString *wh_sub=[wh_cn substringFromIndex:sl.location+1];
elementName = [elementName stringByAppendingString:@" #"];
elementName = [elementName stringByAppendingString:wh_sub];
SAFECOPY(ise[0].curr_attrib,[elementName UTF8String]);
on_dp=( [wh_sub isEqualToString:@"2"] );
}
}
}
Timer logic (how to do things in the background)
Setup the queue and start the timer
NSOperationQueue *_updqueue = [NSOperationQueue new];
self.updqueue = _updqueue;
[_updqueue release];
[updqueue setMaxConcurrentOperationCount:1];
self.updtimer = [NSTimer scheduledTimerWithTimeInterval: 1.0
target:self
selector:@selector(updateMidView:)
userInfo:nil repeats: YES];
.
.
.
Then submit jobs to the queue based on modulo timerCount intervals
// fast timer performance for the perf data
if ((timerCount % 2) == 0) {
// fetch performance data
NSString *perfurl = [whichIse perfurl];
NSInvocationOperation *operation = [[NSInvocationOperation alloc]
initWithTarget:self
selector:@selector(loadXMLPerfDataFromQueue:)
object:perfurl];
[updqueue addOperation:operation];
[operation release];
}
Alert View (how to show data quickly upon a button press)
else if ([button.currentTitle isEqual:@"DP1"] || [button.currentTitle isEqual:@"DP2"]) {
i=( [button.currentTitle isEqual:@"DP2"] );
alestr = [[NSString alloc] initWithFormat:
@"%@\n%@: %@\n%@: %@\n%@: %@\n%@: %@\n%@: %@ GB\n%@: %@\n%@: %d degF\n",
[NSString stringWithUTF8String:ise[cur_ise].dp[i].sts],
@"detail", [NSString stringWithUTF8String:ise[cur_ise].dp[i].detail],
@"serial#", [NSString stringWithUTF8String:ise[cur_ise].dp[i].sn],
@"model", [NSString stringWithUTF8String:ise[cur_ise].dp[i].model],
@"part#", [NSString stringWithUTF8String:ise[cur_ise].dp[i].partnum],
@"capacity", [NSString stringWithUTF8String:ise[cur_ise].dp[i].capacity],
@"health", [NSString stringWithUTF8String:ise[cur_ise].dp[i].health],
@"temperature", (((atoi(ise[cur_ise].dp[i].temp))*9)/5)+32 ];
altitle = [[NSString alloc] initWithFormat:@"DataPac #%d",i+1];
}
UIAlertView *av = [[[UIAlertView alloc] initWithTitle:altitle
message:alestr delegate:nil
cancelButtonTitle:@"OK" otherButtonTitles:nil] autorelease];
[av show];
[alestr release];
[altitle release];
Custom Button Creation/Usage (i.e. making things look nice)
Create the button using interface builder and lay a custom png file down as the background
such that it looks pretty. If you want, you can change the background png as follows.
NSArray *buttonVals = [NSArray arrayWithObjects: @"green2_btn.png", @"yellow2_btn.png",
@"red2_btn.png", @"gray2_btn.png", nil];
int statcolor = 0;
NSMutableString *color = nil;
//mrc1
statcolor = [self chkStatDetails:ise[cur_ise].cn[0].detail];
color = [buttonVals objectAtIndex:statcolor];
[mrc1stat setBackgroundImage:[UIImage imageNamed:color] forState:UIControlStateNormal];
Display/Add/remove/sort ISE list logic
The logic for displaying/adding/removing/re-ordering the ISE in the Top table view is all
implemented in the standard table view controller logic in MyListController.m. Upon
initialization the ISEs.plist file which contains persistant information regarding ISE is read
and used to display the Top view data.
1) Display (in MyListController.m)
NSString *labelInfo = [NSString stringWithFormat:
@"%@ - %@",
[isekclass displayname],
[isekclass serialnumber]];
[[cell textLabel] setText:labelInfo];
NSString *detailText = [NSString stringWithFormat:
@"%@ / %@",
[isekclass mrc1ip],
[isekclass mrc2ip]];
2) Add (See below for adding ISE but the code in MyListController.m is as follows)
// Creates a new nav controller with an instance of MyDetailController as
// its root view controller, and runs it as a modal view controller. By
// default, that causes the detail view to be animated as sliding up from
// the bottom of the screen. And because the detail controller is the root
// view controller, there's no back button.
//
- (void)add
{
iseCfgFromXmlQuery *controller = [[iseCfgFromXmlQuery alloc] init];
controller.view;
UINavigationController *newNavController = [[UINavigationController alloc]
initWithRootViewController:controller];
[[self navigationController] presentModalViewController:newNavController
animated:YES];
[controller release];
}
3) Remove
// Override inherited method to enable/disable Edit button
//
- (void)setEditing:(BOOL)editing
animated:(BOOL)animated
{
[self save];
[super setEditing:editing
animated:animated];
UIBarButtonItem *editButton = [[self navigationItem] rightBarButtonItem];
[editButton setEnabled:!editing];
}
4) Sort (allowing re-order of table cells)
The re-order is dependant on the successful save to complete at program termination in
order to maintain the new order persistantly
// Invoked when the user drags one of the table view's cells. Mirror the
// change in the user interface by updating the array of displayed objects.
//
- (void) tableView:(UITableView *)tableView
moveRowAtIndexPath:(NSIndexPath *)sourceIndexPath
toIndexPath:(NSIndexPath *)targetIndexPath
{
NSUInteger sourceIndex = [sourceIndexPath row];
NSUInteger targetIndex = [targetIndexPath row];
if (sourceIndex != targetIndex)
{
[[self displayedObjects] exchangeObjectAtIndex:sourceIndex
withObjectAtIndex:targetIndex];
}
}
Dynamic query while adding an ISE (validate on the fly)
These routines manage the discovery and management of ISE systems which are known to
ISEview. The data is kept around or maintains persistence via the use of the ISEs.plist
properties file. The logic for discovering an ISE is one of the more simple functions thanks to
the parsing capabilities of the XMLparser.m. The following logic is employed:
1. Build the query URL and call the XMLparser to sort it out (in iseCfgFromXmlQuery.m)
// fetch data was pressed
-(IBAction) setOutput:(id)sender {
// build the query url
NSString *queryurl = [[NSString alloc] initWithFormat:@"http://%@/query", userInput.text];
// Call the xml parser to fetch and parse the query url
TreeNode *root = [[XMLParser sharedInstance] parseXMLFromURL:[NSURL URLWithString:queryurl]];
2. Use the DOM style tree in memory to find the SN and MRC 1 and 2 IP addresses
// find the serialnumber
TreeNode *sn = [root objectForKey: @"serialnumber"];
if(sn.leafvalue!=nil) { sn.leafvalue=[sn.leafvalue
stringByTrimmingCharactersInSet:[NSCharacterSet
whitespaceAndNewlineCharacterSet]]; }
// find the name
TreeNode *isename = [root objectForKey: @"name"];
if(isename.leafvalue!=nil) { isename.leafvalue=[isename.leafvalue
stringByTrimmingCharactersInSet:
[NSCharacterSet whitespaceAndNewlineCharacterSet]]; }
// find controller1 ip address
TreeNode *cn1 = [root objectForKey:@"controller1"];
if(cn1.leafvalue!=nil) { cn1.leafvalue=[cn1.leafvalue
stringByTrimmingCharactersInSet:[NSCharacterSet
whitespaceAndNewlineCharacterSet]]; }
TreeNode *ip1 = [cn1 objectForKey:@"ipaddress"];
if(ip1.leafvalue!=nil) { ip1.leafvalue=[ip1.leafvalue
stringByTrimmingCharactersInSet:[NSCharacterSet
whitespaceAndNewlineCharacterSet]]; }
// find controller2 ip address
TreeNode *cn2 = [root objectForKey:@"controller2"];
if(cn2.leafvalue!=nil) { cn2.leafvalue=[cn2.leafvalue
stringByTrimmingCharactersInSet:[NSCharacterSet
whitespaceAndNewlineCharacterSet]]; }
TreeNode *ip2 = [cn2 objectForKey:@"ipaddress"];
if(ip2.leafvalue!=nil) { ip2.leafvalue=[ip2.leafvalue
stringByTrimmingCharactersInSet:[NSCharacterSet
whitespaceAndNewlineCharacterSet]]; }
3. Update the display using the newly discovered ISE information
// update the displayed values
mrc1ipOutlet.text = ip1.leafvalue;
mrc2ipOutlet.text = ip2.leafvalue;
snOutlet.text = sn.leafvalue;
4. And save the data to the end of the ISEs.plist file
Get the path to the ISEs.plist file
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
NSString *path = [documentsDirectory stringByAppendingPathComponent:@"ISEs.plist"];
Read it into an array
NSMutableArray *iseDicts = [NSMutableArray arrayWithContentsOfFile:path];
Create the new entry
NSMutableDictionary *newdictionary = [NSDictionary dictionaryWithObjectsAndKeys:
(NSString *)isename.leafvalue, @"displayname",
(NSString *)sn.leafvalue, @"serialnumber",
(NSString *)ip1.leafvalue, @"mrc1ip",
(NSString *)ip2.leafvalue, @"mrc2ip",
(NSString *)uid, @"userid",
(NSString *)pw, @"password",
(NSString *)queryurl, @"queryurl",
(NSString *)perfurl, @"perfurl",
(NSString *)statusurl, @"statusurl",
(NSString *)driveurl, @"driveurl", nil];
Put them together and write them out
// add ise to the array of ises
if ([iseDicts count] < 16) {
[iseDicts addObject:newdictionary];
NSString *plist = [iseDicts description];
NSError *error = nil;
[plist writeToFile:path
atomically:YES
encoding:NSUTF8StringEncoding
error:&error];
if (error) {
NSLog(@"error writing to plist file");
}
}
Download