jueves, 18 de noviembre de 2010

Avoid uploading duplicated files in FTP using MSDOS script

I was uploading files using the script I made in a previous post, and all was working smoothly until I reach a directory full of images used for an address book application, there were about 2,000 files. The current version of the script made no comparison for file existence (actually it deletes the old file in the server before attempting to save the new one) or date comparison. In this case, and assuming those files change don’t change too often, I start thinking in a new process to remove the duplicated files and only upload the non existing files.

I made a new FTP script to read the remote files and then delete the existing files from the local PC. This process is a dumb process and only task is to delete all files from the list. It is not aware of date changes or new file versions. Saying these, if you use this script, always work with a backup of your files, never with your original files as they might be deleted.

The script usage is:

deleteDuplicatedFiles.cmd <Root directory to validate>

Current version of the script has no recursive processes implemented

The operation is as follows:

1. Set working variables and defaults

set user=<ftp user>
set pass=<ftp password>
set site=<ftp site>
set base=<remote path to validate>
set file=<FTP commands file>
set outputFile=<local file to store file listing>

2. Start creating the commands file

echo user %user% %pass% >> %file%
echo cd %base% >> %file%
echo ls >> %file%
echo bye >> %file%

3. Execute the commands´ file

ftp -n %site% < %file% > %outputFile%

4. Call to delete the preexisting files

call :deleteFiles "%workDir%" %outputFile%

5. The deleteFiles subroutine reads all lines from the output file and, assuming is a file name, it validate its existing using another subroutine

FOR /F "usebackq delims==" %%i IN (`type %localFile%`) do (
call :deleteSingleFile %%i
)

6. I’m using deleteSingleFile in case another validation is required or needed to implement, currently it only check the file existence and then delete the file

if exist %deleteFile% del %deleteFile%

Here you can find a copy of the script in case you want to try it: deleteDuplicatedFiles.zip

DISCLAIMER: this script is delivered as is and I take no responsibility of any deleted or damage file. Use this script cautiously and always work on a backup to preserve the original files.

jueves, 11 de noviembre de 2010

FTP Uploading files using MSDOS script

Recently I was working in a static Web Site and at the time I need to upload it to the server, I realize there were several files and directories to upload. As I’ve never been a big fan of FTP programs other than the command line provided in each OS, and, instead of searching if there were already a public solution, I started thinking in an automation script to upload the files.

The basic idea of an automation script is to read the contents of a directory, create a commands script for FTP and then execute the generated script. But it also needs to be recursive within internal directories.

As I was working with Windows, the script I created is for MS DOS

The way to call it is:

uploadfiles <Root directory to upload>

Where the given root directory is where the desired files to upload are stored.

Taking a little walkthrough of the script, it works like these:

1. Set working variables and defaults.

set user=<set FTP User>
set pass=<FTP Password>
set site=<site to connect>
set base=<target directory>
set file=<temporary commands file>

2. Next thing to do is to start creating the commands file, some of the used values have been set in step 1

echo user %user% %pass% >> %file%
echo cd %base% >> %file%
echo pwd >> %file%
echo bin >> %file%
echo hash >> %file%

3. The directory processing logic was implemented in a subroutine, we use it to process the root directory

call :processDir . "%workDir%" 0 %file% x

4. Once the directory was processed, we return just to call the FTP to use the commands file and upload all found files.

ftp -n %site% < %file%

The processDir subroutine is called recursively to process all found subdirectories. Once the script is finished, it is intended to work within a FTP session, once in the session if might change directories in remote site, but not in local PC, so the script need to handle all calls to local files as a relative path from where the script was called. This routine works as follows:

1. Local variables are set, from received parameters

set localPath=%~1
set localDir=%~2
set localID=%3
set localFile=%4
set quotes=1
set fullpath=%localPath%\%localDir%

2. For each processed directory, the script assumes that all need to be created

echo mkdir %localDir% >> %localFile%
echo cd %localDir% >> %localFile%
echo pwd >> %localFile%

3. The is finds all the directories of the current one and call itself to process them

FOR /F "usebackq delims==" %%i IN (`dir /b/ad`) do (
...
if "!localquote!"=="0" call :processDir %fullpath% %%i 1 %localFile% %5x
if "!localquote!"=="1" call :processDir "%fullpath%" "%%i" 1 %localFile% %5x
)

4. Next step is to process all files form current directory

FOR /F "usebackq delims==" %%i IN (`dir /b/a-d`) do (
...
if "!localquote!"=="0" (
echo ls %%i >> %localFile%
echo dele %%i >> %localFile%
echo put %fullpath%\%%i >> %localFile%
)

if "!localquote!"=="1" (
echo ls "%%i" >> %localFile%
echo dele "%%i" >> %localFile%
echo put "%fullpath%\%%i" >> %localFile%
)
)

The process is always trying to detect is the files are received with quotes or spaces within its name to make the proper handle, but, I haven’t fully test it for such cases.

Testing the script, let’s say we have the script and the desired directory to upload (web) in d:\temp, we execute it like this:

D:\Temp>uploadFiles.cmd Web

The screen output, in this case, is:

Processing .\Web\Administration
Processing .\Web\bin
Processing .\Web\Controls
Processing .\Web\HtmlTemp
Processing .\Web\Images
Processing .\Web\JS
Processing .\Web
Processing FTP

Having configured the script with these values:

set user=myUser
set pass=myPassword
set site=ftp.mySite.com
set base=/www/
set file=\command.ftp

The created commands file is:

user myUser myPassword
cd /www/
pwd
bin
hash
mkdir Administration
cd Administration
pwd
ls CountryAccess.aspx
dele CountryAccess.aspx
put .\Web\Administration\CountryAccess.aspx
ls CountryAccess.aspx.cs
dele CountryAccess.aspx.cs
put .\Web\Administration\CountryAccess.aspx.cs
ls CountryAccess.aspx.designer.cs
dele CountryAccess.aspx.designer.cs
put .\Web\Administration\CountryAccess.aspx.designer.cs
...
pwd
mkdir JS
cd JS
pwd
ls flash_detect.js
dele flash_detect.js
put .\Web\JS\flash_detect.js
ls GeneralFunctions.js
dele GeneralFunctions.js
put .\Web\JS\GeneralFunctions.js
cd ..
pwd
ls Web.config
dele Web.config
put .\Web\Web.config
cd ..
pwd

Here you can find a copy of the script in case you want to test it yourself: uploadFiles.zip

viernes, 27 de agosto de 2010

Daily accomplishments

I was reading “No B.S. Ruthless Management of People and Profits: The Ultimate, No Holds Barred, Kick Butt, Take No Prisoners Guide to Really Getting Rich” by Dan Kennedy.

It makes reference to a study made to top executives.
According with the referenced study, the executives, by their own account only averages 40 minutes or less of actual accomplishment.
In my work as a Project Manager, we found an average developer to be productive for 80% of their time, in an 8 hours shift, it will be about 6:30 hours; plans where made with this in mind.
Could that be that when responsibilities are increased and more people need to be managed by us, our own accomplishments decrease?

viernes, 13 de agosto de 2010

iPhone's Numeric Pad keyboard with a DOT

I was developing an application for iPhone which handl
es numeric fields to register information. To my disappointment, even in iOS 4, there is no default keyboard for a Number pad including a dot “.”; there is a Number pad with numbers from 0 to 9 and the backspace, in the other hand we have the Numbers and punctuations, which includes numbers, dot, parenthesis, and other symbols. T
he problem with the former is the absence of a dot and the later is that we can easily switch to the alphabetical keyboard.

But what happen with the Number pad including a dot? The Number pad keyboard includes a blank space


After a little search a found a couple of examples of including the dot in the kwyboard, using the blank space.

First, lets say we have a text field defined and connected to the interface

IBOutlet UITextField * textMoney;

And wich keyboard type is the Number Pad

self.textMoney.keyboardType = UIKeyboardTypeNumberPad;

In order to include the Dot, we need to stat receiving notifications to act propperly when the keyboard shows:

/**

View will appear

**/

- (void)viewWillAppear:(BOOL)animtated

{

if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 3.2)

{

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardDidShowNotification object:nil];

}

else

{

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(keyboardWillShow:) name:UIKeyboardWillShowNotification object:nil];

}

}


We include the notification, according with the detected iOS.

/**

View will disappear

**/

- (void)viewWillDisappear:(BOOL)animtated

{

[[NSNotificationCenter defaultCenter] removeObserver:self];

}


The dismiss te notification once the view is not showed.

Now, all interesting process begin with when we received the notification in the configured function, in this case keyboardWillShow

/**

The Keyboard will be shown

**/

- (void)keyboardWillShow:(NSNotification *)notification

{

if ([textNotes isFirstResponder])

{

// Skip for Notes keyboard

return;

}

// Locate fist view

UIWindow *tempWindow = [[[UIApplication sharedApplication] windows] objectAtIndex:1];

UIView *keyboard;

// Define Button

UIButton * utilityButton = [UIButton buttonWithType:UIButtonTypeCustom];

utilityButton.frame = CGRectMake(0, 163, 105, 53);


// Set Text and font

[utilityButton.titleLabel setFont:[UIFont systemFontOfSize:35]];

[utilityButton setTitle:[Dot or Comma according with localized settings] forState:UIControlStateNormal];

// Set Solors

[utilityButton setTitleColor:[UIColor colorWithRed:77.0f/255.0f green:84.0f/255.0f blue:98.0f/255.0f alpha:1.0] forState:UIControlStateNormal];

[utilityButton setTitleColor:[UIColor whiteColor] forState:UIControlStateHighlighted];

// Background for the pressed state

[utilityButton setBackgroundImage:[UIImage imageNamed:@"background.png"] forState:UIControlStateHighlighted];

// Add call to functionality

[utilityButton addTarget:self action:@selector(addDecimalPointToField) forControlEvents:UIControlEventTouchUpInside];

// Behavior when orientation is changed

[utilityButton setAutoresizingMask:(UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleRightMargin | UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleHeight)];


// Locate keyboard view and add button

for(int i=0; i<[tempWindow.subviews count]; i++)

{

keyboard = [tempWindow.subviews objectAtIndex:i];

if ([[[UIDevice currentDevice] systemVersion] floatValue] >= 3.2)

{

if([[keyboard description] hasPrefix:@"<UIPeripheralHost"] == YES)

{

[keyboard addSubview:utilityButton];

}

}

else

{

if([[keyboard description] hasPrefix:@"<UIKeyboard"] == YES)

{

[keyboard addSubview:utilityButton];

}

}

}

}


Again, we need to make a distinction for the iOS.

One little extra t
hing, to add the dot to the current field with the focus, we use a trick, copy the dot to memory and then call the paste function of the current field, this is done in addDecimalPointToField.

/**

Send Decimal Point event to field with focus

**/

- (void)addDecimalPointToField

{

UIView * objectToEdit = nil;

// Locate field with the focus

for (UIView * localView in self.scrollView.subviews)

{

if ([localView isFirstResponder])

{

objectToEdit = localView;

}

}

// Try to insert dot

if (objectToEdit != nil)

{

// Detect if the Dot has been already inserted

NSString * localText = [(UITextField *)objectToEdit text];

NSRange separatorPosition = [localText rangeOfString:[Dot or Comma according with localized settings]];


if (separatorPosition.location == NSNotFound)

{

// Dot wasn't found, add it

[objectToEdit insertText:[NSString stringWithFormat:[Dot or Comma according with localized settings]]];

}

}

}


I have change part of the code with [According with the localization options, add a Dot or a Comma], this in order to let you decide what punctuation symbol to use.

The result will be something like:


I'm not really sure it this will be accepted by Apple to be release to iTunes Store, I haven't use it for a commercial application, at least, my AdHoc application implements it.

Hope this help you.

lunes, 2 de agosto de 2010

Migrating Contacts over phones

Recently I was helping in a cell phone migration; contacts were the most important thing to migrate, but the devices where not directly compatible as the devices where a Nokia and a Samsung.

After a couple of Google’s searches, I got to the idea that there were no application so make my life easier, so I got to the task to investigate what the vendor’s applications were capable of.

I install in a Windows PC both suites, Nokia PC Suite and Samsung PC Studio, after I connect and synchronize both devices I got the option of exporting and importing contacts.

From the Nokia PC Suite, I exported all contacts; as a result I got a single file including all the 100+ contacts. I then turn to Samsung’s suite and try to import from the file, but it only imports the first one.

I don’t know if it has something to do with the vCard standard, but, most of the products I´ve tried do the same when reading multiple cards within a file.

So, next thing to do was to create as many files as contacts to import, wait a minute, this is more than 100 files!

What if I want to make a migration of a device with thousand of contacts?

To make my life easier, I start creating a DOS shell script to separate the file in as many contacts as is has. Its operation will be easy:
1. Read source file line by line
2. When detects the tag “BEGIN:VCARD” create a new file
3. Writes all read lines to the created file

Whit this simple process I got all the files I need and next step was to imported in Samsung’s suite. Fortunately, it support to drag-n-drop many files at a time, so, just in a moment the Agenda was introduced in the new phone.

Here is the code to create the vCards.

It reads the input file in the same path. File with contacts need to be named as “contacts.txt”


@echo off

set fileNumber=0

setLocal EnableDelayedExpansion
for /f "tokens=* delims==" %%a in ('type contacts.txt') do (
if "BEGIN:VCARD" == "%%a" (
set /a fileNumber+=1
echo BEGIN:VCARD > !fileNumber!.vcf
)
if NOT "BEGIN:VCARD" == "%%a" (
echo %%a >> !fileNumber!.vcf
)
)

goto :eof


Here you can find a copy of the script in case you want to test it yourself: readCards.zip

jueves, 8 de julio de 2010

WebServices solution for iPhone - Second Stage

The FileDownloadController class

Once I had the FileDownload class, it came to me the problem of using it many times and from many places. I could create as many FileDownload objects as needed, but this will create many variables and lines of code. My solution was to create a controller class (FileDownloadController), which I implement with a Singleton Pattern.

The concept is:
  • The FileDownloadController class will create and manage all needed FileDownload objects
  • Whenever an object needs to download a file, it should get the FileDownloadController instance and then asks for the download
  • Once the download is complete, or in case of error, a callback will be received by the requesting object

In order to maintain certain acceptable performance, because the download process works in the background and consumes resources, there should be a limited number of concurrent downloads, this number is set when the controller is created. In my tests, using an iPhone 3G, an acceptable number is 10.

Because of the limited number of download slots, we might face the problem of having more requests than slots, for this we have 2 possible solutions:
  1. Store requests in a queue and attend them once a slot is available
  2. Cancel the oldest download and use it for the requested

As one of the purposes of these classes were to download images, and those been requested from a scrollable table, the selected solution was number 2.

To attend this new requirement, an intermediary class was created to hold the FileDownload object and the time when the process was called. The interface of this class is:

@interface FileDownloadControllerItem : NSObject

{

int jobID;

FileDownload * downloadObject;

NSDate * startTime;

}


@property (nonatomic, retain) FileDownload * downloadObject;

@property (nonatomic, assign) int jobID;

@property (nonatomic, retain) NSDate * startTime;


-(FileDownloadControllerItem *)initwithDelegate:(id)delegate;

-(void)downloadFile:(NSInteger)fileID fileName:(NSString *)name callBack:(id<FileDownloadProtocol>)delegate;

-(void)stopDownload;


@end


  • jobID it holds the Download ID discussed in the previous post of this series.
  • downloadObject holds the FileDownload object that will be used to process the download
  • startTime holds the time when the download was called and will be used to identify the older download when a new download is requested and there are no available slots

Its implementation is pretty straightforward and in most cases are direct calls to the FileDownload object.

-(FileDownloadControllerItem *)initwithDelegate:(id)delegate;


Create objects and set the delegate for callbacks

-(void)downloadFile:(NSInteger)fileID fileName:(NSString *)name callBack:(id<FileDownloadProtocol>)delegate;


Is called when a file needs to be download, it set the self variables and call the FileDownload object to start the process. This class is not validating any available slot; it assumes that validation was made previously.

-(void)stopDownload;


Just call to cancel the current download.

About the implementation

/**

Initializtion given a delegate

**/

-(FileDownloadControllerItem *)initwithDelegate:(id)delegate

{

[self init];


self.jobID = FREESLOT;

self.downloadObject = [[FileDownload alloc] init];

self.startTime = [NSDate dateWithTimeIntervalSince1970:0];

return self;

}


/**

Download a file

**/

-(void)downloadFile:(NSInteger)fileID fileName:(NSString *)name callBack:(id)delegate;

{

self.jobID = fileID;

self.startTime = [NSDate dateWithTimeIntervalSinceNow:0];

[self.downloadObject setJobID:fileID];

[self.downloadObject startDownloadFile:name callBack:delegate];

}


/**

Cancel current download

**/

-(void)stopDownload

{

[self.downloadObject stopDownload];

}


Most of the logic is implemented in the Controller class, which interface is as follows:

@interface FileDownloadController : NSObject

{

int currentDownloads;

BOOL maintainQueue;

int totalJobs;

NSMutableArray * downloadArray;

NSMutableDictionary * callBackObjects;

}


@property (nonatomic, assign) int totalJobs;

@property (nonatomic, assign) BOOL maintainQueue;


+ (FileDownloadController *)getController;

+ (FileDownloadController *)getController:(NSInteger)jobs useQueue:(BOOL)queue;


- (FileDownloadController *)initWithJobs:(NSInteger)jobs;

- (FileDownloadController *)initWithJobs:(NSInteger)jobs useQueue:(BOOL)queue;


- (BOOL)downloadFile:(NSInteger)fileID fileName:(NSString *)name callBack:(id<FileDownloadProtocol>)delegate;


- (BOOL)findJobInQueue:(NSInteger)job;


- (void)cancelDownloadFile:(NSInteger)fileID;

- (void)stopAllDownloads;


@end


  • currentDownloads is used to keep track of the actual concurrent downloads
  • maintainQueue is not used, yet, but it intention is to identify if a download queue is used or if the older download might be cancelled
  • totalJobs maximum number of concurrent downloads
  • downloadArray this array holds all the download slots
  • callBackObjects is used to hold the download ids and the callback objects; this is needed because the FileDownload will call a callback within the controller, then the controller will call the requesting object

The available functions are:

+ (FileDownloadController *)getController;


This is used to retrieve a controller with default values, internally it calls initWithJobs with 10 jogs and not using the queue

+ (FileDownloadController *)getController:(NSInteger)jobs useQueue:(BOOL)queue;


Is used to create a controller with specific values, it also calls initWithJobs with the given parameters

- (FileDownloadController *)initWithJobs:(NSInteger)jobs;


Simple constructor which initialize the controller with the given jobs and not using the queue

- (FileDownloadController *)initWithJobs:(NSInteger)jobs useQueue:(BOOL)queue;


Initialize the controller with the given jobs and queue use, this function creates the required download slots

- (BOOL)downloadFile:(NSInteger)fileID fileName:(NSString *)name callBack:(id<FileDownloadProtocol>)delegate;


Called to download a file

- (BOOL)findJobInQueue:(NSInteger)job;


As each download required an ID, this function tells us if an ID is already in the download queue, this is, if it was found in any download slot

- (void)cancelDownloadFile:(NSInteger)fileID;


Call to cancel a given download

- (void)stopAllDownloads;


Called to stop all downloads, mainly called from the destructor

There are other internal functions used by the class, the implementation is:

/**

Get Singleton object

**/

+ (FileDownloadController *)getController

{

if (controller == nil)

{

controller = [[FileDownloadController alloc] initWithJobs:10 useQueue:NO];

}

return controller;

}


/**

Get Singleton object

**/

+ (FileDownloadController *)getController:(NSInteger)jobs useQueue:(BOOL)queue

{

if (controller == nil)

{

controller = [[FileDownloadController alloc] initWithJobs:jobs useQueue:queue];

}

return controller;

}


Both functions call the constructors.

/**

Initialization with job number

**/

-(FileDownloadController *)initWithJobs:(NSInteger)jobs

{

return [self initWithJobs:jobs useQueue:NO];

}


/**

Initialization with jobs and Queue handling

**/

-(FileDownloadController *)initWithJobs:(NSInteger)jobs useQueue:(BOOL)queue

{

[self init];

totalJobs = jobs;

maintainQueue = queue;

currentDownloads = 0;


downloadArray = [[NSMutableArray alloc] initWithCapacity:self.totalJobs];

for (int i = 0; i < self.totalJobs; ++i)

{

[downloadArray addObject:[[FileDownloadControllerItem alloc] initwithDelegate:self]];

}

callBackObjects = [[NSMutableDictionary alloc] init];

return self;

}


Within the constructor, the download slots are created and object’s variables initialized.

The downloadArray holds the FileDownloadControllerItem created for each slot.
The callBackObjects will hold the relation of each download ID and the object related for the callback

/**

Download files

**/

-(BOOL)downloadFile:(NSInteger)fileID fileName:(NSString *)name callBack:(id<FileDownloadProtocol>)delegate

{

// Validate file is not already in download queue, cero is not valid id

// Find a free download slot

// If none, clear one

// Configure slot to download file

BOOL error;

FileDownloadControllerItem * downloadItem;

error = [self findJobInQueue:fileID];

if (!error)

{

[self storeSession:fileID callBack:delegate];

downloadItem = [self getFreeSlot:NO];

if (downloadItem == nil)

{

downloadItem = [self getFreeSlot:YES];

}

[self increaseDownloadCounter];

[downloadItem downloadFile:fileID fileName:name callBack:self];

}

return downloadItem != nil;

}


To start a download process, first thing to do is validate it is not already been downloaded. Then we store the session in the dictionary and look for a free slot ([[self getFreeSlot:NO]), if there is none, we get one canceling the oldest ([[self getFreeSlot:YES]). Finally we increase the download counter and start the download ([downloadItem downloadFile:fileID fileName:name callBack:self]).

/**

Find if job is already in progress

**/

-(BOOL)findJobInQueue:(NSInteger)job

{

BOOL error = NO;

FileDownloadControllerItem * downloadItem;

for (int i = 0; i < [downloadArray count]; ++i)

{

downloadItem = (FileDownloadControllerItem *)[downloadArray objectAtIndex:i];

if (downloadItem.jobID == job)

{

error = YES;

}

}

return error;

}


To find if a job is already been downloaded, we loop through all the slots and compare the IDs

/**

Cancel docwnload given its fileID

**/

-(void)cancelDownloadFile:(NSInteger)fileID

{

[self cancelDownload:[self getSlot:fileID]];

}


To cancel a download, just get the slot a a given ID and call to cancel it using cancelDownload

/**

Call to cancel download activities

**/

-(void)cancelDownload:(FileDownloadControllerItem *)downloadItem

{

if (downloadItem != nil)

{

[self deleteSession:downloadItem.jobID];

[self decreaseDownloadCounter];

[downloadItem.downloadObject stopDownload];

downloadItem.jobID = FREESLOT;

}

}


cancelDownload cancel the downloadItem, decrease counters and clear sessions for the canceled download.

/**

Stop downloads

**/

-(void)stopAllDownloads

{

for (int i = 0; i < [downloadArray count]; ++i)

{

FileDownloadControllerItem * downloadItem = (FileDownloadControllerItem *)[downloadArray objectAtIndex:i];

[self deleteSession:downloadItem.jobID];

[self decreaseDownloadCounter];


[downloadItem stopDownload];

downloadItem.jobID = FREESLOT;

}

}


To stop all downloads, loop the slots and call each to be cancelled.

/**

Get a worker given a jobid

**/

-(FileDownloadControllerItem *)getSlot:(NSInteger)job

{

FileDownloadControllerItem * downloadItem = nil;

for (int i = 0; i < [downloadArray count]; ++i)

{

downloadItem = (FileDownloadControllerItem *)[downloadArray objectAtIndex:i];

if (downloadItem.jobID == job)

{

return downloadItem;

}

}

return downloadItem;

}


This function finds a downloadItem given the ID and returns it

/**

Get a free worker

**/

-(FileDownloadControllerItem *)getFreeSlot:(BOOL)force

{

FileDownloadControllerItem * downloadItem = nil;

FileDownloadControllerItem * downloadItemReturn = nil;

NSDate * minDate = [NSDate dateWithTimeIntervalSinceNow:0];

for (int i = 0; i < [downloadArray count]; ++i)

{

downloadItem = (FileDownloadControllerItem *)[downloadArray objectAtIndex:i];

if ( ( downloadItemReturn == nil && downloadItem.jobID == FREESLOT ) || force)

{

if (force)

{

if ([downloadItem.startTime timeIntervalSince1970] < [minDate timeIntervalSince1970])

{

minDate = [NSDate dateWithTimeIntervalSince1970:[downloadItem.startTime timeIntervalSince1970]];

downloadItemReturn = downloadItem;

}

}

else

{

downloadItemReturn = downloadItem;

}

}

}


if (downloadItem != nil)

{

[self cancelDownload:downloadItemReturn];

}

return downloadItemReturn;

}


getFreeSlot is used to retrieve an available downloadItem, in normal execution, it loop through all the slots looking for an empty one. When forcing the availability, it finds the oldest, which will be cancelled and then returned as a free slot.

The controller implements the FileDownloadProtocol and need to implement its functionality, this is:

- (void) downloadError:(NSError *)error downloadID:(NSInteger)jobID;

- (void) downloadSucceed:(NSMutableData *)data downloadID:(NSInteger)jobID;


In both cases, it deletes the session created for the download and return the information to the object which request the download using a callback.

With all this work done, when an object requires to download a file, it need to do some like these:

downloadController = [FileDownloadController getController];

[downloadController downloadFile:fileID fileName:url callBack:self];


Implementing the FileDownloadProtocol we should have:

#pragma mark -

#pragma mark FileDownloadProtocol


/**

Notify an error

**/

- (void) downloadError:(NSError *)error downloadID:(NSInteger)jobID

{

// Notify error to screen

}


/**

Notify download complete

**/

- (void)downloadSucceed:(NSMutableData *)data downloadID:(NSInteger)jobID

{

// Do something with the data, remember the jobID is the ID sent in downloadFile

}


Using this class, a file download is easiest than previous. Now just remember to release the downloadController object before exiting the application.

in a next post I'll complete this series with the Third Stage: calling WebServices.