Can I download update files from server and replace files in supporting folder? - iphone

I am doing iPhone app dev. Usually, I put image(photo) files and some other configuration files in to supporting folder.
Can I update those files in supporting folder from server? If can, how to do it?
If not, can I pre-store the files which are need to upload into Document Folder and download newer files in document folder to replace older files?

we had the same problem in our current app. our final approach is this:
1) the assets we want to deliver with the app are stored inside a folder under Resources.
2) Plus there is an XML file telling us, which asset belongs where and how old it is (timestamp).
3) When the app starts for the first time, we copy all files (except the xml) from Rescources to the app's Cache folder:
// get the app's cache folder
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
_cachesDirectory = [paths objectAtIndex:0];
_assetDirectory = [NSString stringWithFormat:#"%#/assets", _cachesDirectory];
// copy everything
_fmngr = [NSFileManager defaultManager];
- (void) copyAllFilesFromInitialFolder:(NSString*)path
{
NSArray *files = [_fmngr contentsOfDirectoryAtPath:path error:nil];
for (NSString *file in files)
{
if ([file isEqualToString:[NSString stringWithFormat:#"%#.xml", kDefaultXMLFileName]])
{
continue;
}
NSString *fpath = [NSString stringWithFormat:#"%#/%#", path, file];
BOOL isDir;
if ([_fmngr fileExistsAtPath:fpath isDirectory:&isDir])
{
if (isDir)
{
[self copyAllFilesFromInitialFolder:fpath];
}
else
{
NSString *relPath = [fpath substringFromIndex:[_initialLangDeviceContentPath length]+1];
NSString *fileName = [self convertFilePathToName:relPath];
NSString *tpath = [NSString stringWithFormat:#"%#/%#", _assetDirectory, fileName];
NSError *error;
BOOL success = [_fmngr copyItemAtPath:fpath toPath:tpath error:&error];
if (!success || error)
{
NSLog(#"INFO: copy %# to CACHES failed ... file may already be there.", file);
}
}
}
}
}
4) At the next start of the application, we check online if there are newer files on our update server. The XML from step 2) also resides on the server and has to be updated when JPGs/PNGs/... are updated/replaced on the server.
If nothing changed our PHP script returns a 304 "not modfied" - otherwise it'll output an updated version of the XML.
the PHP script looks something like this:
$doc = new DOMDocument();
#$doc->load($filename);
$xpath = new DOMXPath($doc);
$assets = $xpath->query('//asset');
if ($assets->length > 0)
{
foreach ($assets as $asset)
{
$assetPath = $asset->getAttribute('path');
if (file_exists($assetPath))
{
$atime = filemtime($assetPath);
$asset->setAttribute('modified', $atime);
}
else
{
// file not found - link broken
$asset->setAttribute('modified', '0');
}
}
}
$output = $doc->saveXML();
header('Content-type: text/xml');
echo $output;
The app downloads and parses the generated XML, comparing all modified values.
When there is an asset with a newer modified timestamp it is deleted locally and redownloaded. only after this check is completed the app starts - and you got the new assets on the device.
Hope this helps. We had some problems with the last modified attribute of the files we deliver with the app. When including the files into the app bundle and copying them at runtime, the files last modified is always the time of the first start of the app. sometimes you already updated some files on the server - but because the app thinks the files on the device are newer (because they were copied just now) they are not re-dowloaded from the server :(
so you can't work with the real file's attributes but have to include the actual file-date inside the XML file.

Related

How to import an .sqlite3/.sqlite file to ios application?

I was having an excel file.
I have converted that file to .csv format and import that file to base and converted it into .sqlite file.
So the question is that:
Is there any way to import it into an ios app and manipulate the data.
Is there any way to use it like core data or import that file into core data.
Kindly refer any good tutorial preferably video tutorial or some other good one.
You can use it directly with FMDB library: https://github.com/ccgus/fmdb
Another option is to import that file into core data, but it is a little tricky. You can do it if you follow these steps:
Create empty SQLite database in your application and run your app in simulator.
Open simulator directory on your computer and locate SQLite database file.
Look inside it with SQLite command line tool or something like "SQLite Data Browser" GUI tool (http://sqlitebrowser.sourceforge.net/).
Import your data to this database file without changing structure and data in core data meta tables.
Finally you have SQLite database file ready to be used with core data. So you put it into your app bundle.
On first application launch you should copy your SQLite database file to appropriate directory (you know where you should put your file - you already found it in simulator app directory) before configuring core data stack.
It sounds a bit complicated but it works ;)
Nice article about shipping pre-populated data for core data: http://www.objc.io/issue-4/importing-large-data-sets-into-core-data.html
Update
Please note the updated response.
Is there any way to import it (SQLite) into an ios app and manipulate the data?
You can import a sqlite file into Xcode, by simply adding it as a resource using Add New File... However you would have limited ability to use it jointly with Core Data (unless it was created with Core Data). One can review the objc.io article referenced earlier that covers how to deal with prepopulated data in an Xcode project. Here is the pertinent section of that article.
NSFileManager* fileManager = [NSFileManager defaultManager];
NSError *error;
if([fileManager fileExistsAtPath:self.storeURL.path]) {
NSURL *storeDirectory = [self.storeURL URLByDeletingLastPathComponent];
NSDirectoryEnumerator *enumerator = [fileManager enumeratorAtURL:storeDirectory
includingPropertiesForKeys:nil
options:0
errorHandler:NULL];
NSString *storeName = [self.storeURL.lastPathComponent stringByDeletingPathExtension];
for (NSURL *url in enumerator) {
if (![url.lastPathComponent hasPrefix:storeName]) continue;
[fileManager removeItemAtURL:url error:&error];
}
// handle error
}
NSString* bundleDbPath = [[NSBundle mainBundle] pathForResource:#"seed" ofType:#"sqlite"];
[fileManager copyItemAtPath:bundleDbPath toPath:self.storeURL.path error:&error];
NSDictionary *infoDictionary = [NSBundle mainBundle].infoDictionary;
NSString* bundleVersion = [infoDictionary objectForKey:(NSString *)kCFBundleVersionKey];
NSString *seedVersion = [[NSUserDefaults standardUserDefaults] objectForKey:#"SeedVersion"];
if (![seedVersion isEqualToString:bundleVersion]) {
// Copy the seed database
}
// ... after the import succeeded
[[NSUserDefaults standardUserDefaults] setObject:bundleVersion forKey:#"SeedVersion"];
Assuming one wanted to import a CSV file rather than an Excel or SQLite... Since this is a common question, here is a simple parser that one can use to incorporate CSV data into an Xcode project.
func parseCSV (contentsOfURL: NSURL, encoding: NSStringEncoding, error: NSErrorPointer) -> [(name:String, detail:String, price: String)]? {
// Load the CSV file and parse it
let delimiter = ","
var items:[(name:String, detail:String, price: String)]?
if let content = String(contentsOfURL: contentsOfURL, encoding: encoding, error: error) {
items = []
let lines:[String] = content.componentsSeparatedByCharactersInSet(NSCharacterSet.newlineCharacterSet()) as [String]
for line in lines {
var values:[String] = []
if line != "" {
// For a line with double quotes
// we use NSScanner to perform the parsing
if line.rangeOfString("\"") != nil {
var textToScan:String = line
var value:NSString?
var textScanner:NSScanner = NSScanner(string: textToScan)
while textScanner.string != "" {
if (textScanner.string as NSString).substringToIndex(1) == "\"" {
textScanner.scanLocation += 1
textScanner.scanUpToString("\"", intoString: &value)
textScanner.scanLocation += 1
} else {
textScanner.scanUpToString(delimiter, intoString: &value)
}
// Store the value into the values array
values.append(value as! String)
// Retrieve the unscanned remainder of the string
if textScanner.scanLocation < count(textScanner.string) {
textToScan = (textScanner.string as NSString).substringFromIndex(textScanner.scanLocation + 1)
} else {
textToScan = ""
}
textScanner = NSScanner(string: textToScan)
}
// For a line without double quotes, we can simply separate the string
// by using the delimiter (e.g. comma)
} else {
values = line.componentsSeparatedByString(delimiter)
}
// Put the values into the tuple and add it to the items array
let item = (name: values[0], detail: values[1], price: values[2])
items?.append(item)
}
}
}
return items
}
(Source article)
Another option is to use the Core Data Editor tool originally mentioned in the Ray W. list of tools. This GUI editor tries to make handling CSV data imports easier.
Is there any way to use it like core data or import that file into core data?
So a SQLite database is not the same as Core Data (which is an object graph persistence...). I was about to go into my diatribe here, but Apple's Core Data FAQ says it better than I could...:
How do I use my existing SQLite database with Core Data?
You don’t. Although Core Data supports SQLite as one of its persistent
store types, the database format is private. You cannot create a
SQLite database using native SQLite API and use it directly with Core
Data (nor should you manipulate an existing Core Data SQLite store
using native SQLite API). If you have an existing SQLite database, you
need to import it into a Core Data store (see Efficiently Importing
Data).
So that's the official answer. Anything else offered is just a way to work around the fact that one is not supposed to do this.
However, given that you also have a CSV file you do have some other options. In the past I've built a file reader to examine the contents of a CSV file using a stream reader. Here is the gist of that, however my file likely had some other formatting so this probably needs tweaking. You can also look at using any object that reads the contents of a file. For example; a much simpler technique comes to mind:
Use the initWithContentsOfFile on the NSString class
Gives you a string with the CSV in memory
Iterate the string for each line
Loop through the line using commas and do something with each piece of data
NSString *fileContents = [NSString stringWithContentsOfFile:#"myfile.txt"];
NSArray *lines = [fileContents componentsSeparatedByString:#"\n"];
//loop and split each line in lines array into useful data
Let's say you really want to use SQLite in iOS, warnings notwithstanding... You can add the sqlite3 library to your project. Full details are available on how to use SQLite instead of Core Data. One of the many online tutorials is at AppCoda
The basics are covered (sample project):
Saving...
- (IBAction)saveInfo:(id)sender {
// Prepare the query string.
NSString *query = [NSString stringWithFormat:#"insert into peopleInfo values(null, '%#', '%#', %d)", self.txtFirstname.text, self.txtLastname.text, [self.txtAge.text intValue]];
// Execute the query.
[self.dbManager executeQuery:query];
// If the query was successfully executed then pop the view controller.
if (self.dbManager.affectedRows != 0) {
NSLog(#"Query was executed successfully. Affected rows = %d", self.dbManager.affectedRows);
// Pop the view controller.
[self.navigationController popViewControllerAnimated:YES];
}
else{
NSLog(#"Could not execute the query.");
}
}
Editing...
-(void)loadInfoToEdit{
// Create the query.
NSString *query = [NSString stringWithFormat:#"select * from peopleInfo where peopleInfoID=%d", self.recordIDToEdit];
// Load the relevant data.
NSArray *results = [[NSArray alloc] initWithArray:[self.dbManager loadDataFromDB:query]];
// Set the loaded data to the textfields.
self.txtFirstname.text = [[results objectAtIndex:0] objectAtIndex:[self.dbManager.arrColumnNames indexOfObject:#"firstname"]];
self.txtLastname.text = [[results objectAtIndex:0] objectAtIndex:[self.dbManager.arrColumnNames indexOfObject:#"lastname"]];
self.txtAge.text = [[results objectAtIndex:0] objectAtIndex:[self.dbManager.arrColumnNames indexOfObject:#"age"]];
}
Deleting...
-(void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath{
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the selected record.
// Find the record ID.
int recordIDToDelete = [[[self.arrPeopleInfo objectAtIndex:indexPath.row] objectAtIndex:0] intValue];
// Prepare the query.
NSString *query = [NSString stringWithFormat:#"delete from peopleInfo where peopleInfoID=%d", recordIDToDelete];
// Execute the query.
[self.dbManager executeQuery:query];
// Reload the table view.
[self loadData];
}
}
Re: Kindly refer any good tutorial preferably video tutorial or some
other good one.
The following tutorial should fill your need. There are quite a few tutorials on this topic you can check out www.lynda.com for a detailed walk through on building an iOS app with SQLite (some cost involved for full access however search Youtube as they post sample movies covering these topics all the time).
http://www.youtube.com/watch?v=bC3F8a4F_KE (see 1:17 in video)
If you have an .sql file, you just import it to your project by going to File - Add Files.
Also, keep in mind that if you leave your .sql file in your bundle, it will be read only.
So, unless you want it to be read only, you should make new group and put your .sql there.

iOS read CSV file by using CHCSVParser

Here is my code, and the csv file is
"Hello,Baby",Testing
ByeBye,Testing
When I print out the rowArray, I found that the result contain "
"Hello,Baby"
How can I remove it
- (IBAction)importToDatabase:(id)sender {
NSString *csvFilePath = [NSString stringWithFormat:#"%#/Documents/%#",NSHomeDirectory(),#"test1.csv"];
if([[NSFileManager defaultManager] fileExistsAtPath:csvFilePath])
NSLog(#"Exist");
else
NSLog(#"NOT EXIST");
NSArray *rowArray = [NSArray arrayWithContentsOfCSVFile:csvFilePath];
NSLog(#"%#",[[rowArray objectAtIndex:0]objectAtIndex:0]);
}
You can make use of the function stringByReplacingOccurrencesOfString:withString: to replace the " with a blank string (see the documentation). If have access to the code of the method arrayWithContentsOfCSVFile: you can did it in there, if not you will have to iterate throw the values of your array to remove it.

How to convert text file to xml in xcode

I have a big text file that I want to convert into Xcode. I added the text file in the main bundle (drag and drop) into my project . I can see the text file viewDidLoad.
But I like to convert it to XML file. For instance my file looks like :
asasasasasas
wewewewewewe
qwqwqwqwqwqw
xyz_ 22 aaaaaaaaaaa
fgfgfgfgfgfgfg
ererererererer
abc_ 12 bbbbbbbbbb
jkjkjkjkjkjkjk
lalallalalalal
In the above mentioned, I want to eliminate the first 3 lines, to start from xyz_ 22 as (parent), jkjkjkj as child lalalala as a child.
I need only the idea how to implement this ... I'll write the code :)
mycode:
- (IBAction)readUsingObjectiveC:(id)pId {
NSString * zStr = [NSString stringWithContentsOfFile:#"/Users/dd007/Desktop/abc.txt" encoding:NSASCIIStringEncoding error:NULL];
NSLog(#"readUsingObjectiveC zStr=\n%#",zStr);
// now to extract the data line by line
NSArray * zAryOfLines = [zStr componentsSeparatedByString:#"\n"];
if([zAryOfLines count] == 0)
{
NSLog(#"readUsingObjectiveC zAryOfLines count = 0");
return;
}
//for (int i=0; i<([zAryOfLines count]-30); ++i)
for ( int i=30; i<zAryOfLines ; i++)
{
if([[zAryOfLines objectAtIndex:i] isEqualToString:#"xyz_ "])
{
NSLog(#"<msg1>%#<msg1/>\n",[zAryOfLines objectAtIndex:i]);
NSLog(#"<msg2>%#<msg2/>\n",[zAryOfLines objectAtIndex:i+1]);
NSLog(#"<msg3>%#<msg3/>\n",[zAryOfLines objectAtIndex:i+2]);
[zArrayOfLines writeToFIle:#"/.....documents/..save.xml" automatically:YES encodingNSASCIIStringEncoding error:NULL];
}
}
I am getting convert into xml format but i like to save the file in .xml .. but i am getting error could any one tell me where i am doing mistake ??????
You can't convert a file to xml using XCode, you have two options:
You can create a python or ruby script that parses your file and makes a XML file, and then use a xml parser,
Or you can create a class that parses your plain file with the rules you want.
I think you want to transform a .txt to a .xml so for make this i read the .txt to a nsstring i cut this into a NSArray with componentsSeparatedByString:#"\n" method of NSString (take care of \r character) and after just don't take line you don't want and create a new NSString for add tag XML to your line if you now where place the good tag or check it with the contains of your line in the NSArray for finish just saved in new file with extension .xml.
If you need help for write code like i describe say it.
There is no option for converting the text to xml directly in iOS.
But you can do it by passing the data manually.
You can use either libXml framework or GDataXml for doing this.
For libXml xml generation sample code go to this link and download Chapter 10.zip
Please check this tutorial for Read and Write XML Documents with GDataXML
Also TCMXMLWriter is an opensource xmlgenerator :
I think a code like this work for your example :
NSString *contentFile = [[NSString alloc] initWithContentsOfFile:pathFile encoding:NSUTF8StringEncoding error:nil];
NSArray *lineFile = [contentFile componentsSeparatedByString:#"\n"];
NSMutableString *xmlFile = [[NSMutableString alloc] init];
For(int i = 3; i < lineFile.count; i++)//i = 3 for don't take the 3 first line
{
if ([((NSString *)[lineFile objectAtIndex:i]) rangedOfString:#"test"].location != NSNotFound)
{
xmlFile = [NSString stringWithFormat:#"%#<nameTag>%#</nameTag>", xmlFile, (NSString *)[lineFile objectAtIndex:i]];
}
else if ...
}
And save he nsstring in new file. Possible in loop to make by number like if i is multiple of 3 of 4 etc...

SQLite, iPhone and versioning

I want to include an updated SQLite database with a new version of an app. My app copies the database file into the Documents directory on startup. What is the best way to do this kind of versioning (besides using Core Data)?
I'm assuming that either a special 'version' table in the SQLite file or a small text file with the version number is the way to go, but I'd like to get other peoples opinions.
No need for a specialized table. SQLite has a pragma for this, called user_version. SQLite doesn't use this value for anything, it's left entirely to the application.
To read the version:
#pragma user_version;
To set the version:
#pragma user_version=1;
The way I do this is by looking at filestamps. If the modification date of the SQLite DB file in the .app bundle is more recent than the one in the local documents directory, then I copy the one from the .app bundle over... Here's the code I use.
sqlite3 *dbh; // Underlying database handle
NSString *name; // Database name (this is the basename part, without the extension)
NSString *pathBundle; // Path to SQLite DB in the .app folder
NSString *pathLocal; // Path to SQLite DB in the documents folder on the device
- (BOOL)automaticallyCopyDatabase { // Automatically copy DB from .app bundle to device document folder if needed
ES_CHECK(!dbh, NO, #"Can't autoCopy an already open DB")
ES_CHECK(name!=nil, NO, #"No DB name specified")
ES_CHECK(pathBundle!=nil, NO, #"No .app bundle path found, this is a cache DB")
ES_CHECK(pathLocal!=nil, NO, #"No local document path found, this is a read-only DB")
NSFileManager *fileManager = [NSFileManager defaultManager];
NSDictionary *localAttr = [fileManager fileAttributesAtPath:pathLocal traverseLink:YES];
BOOL needsCopy = NO;
if (localAttr == nil) {
needsCopy = YES;
} else {
NSDate *localDate;
NSDate *appDBDate;
if (localDate = [localAttr objectForKey:NSFileModificationDate]) {
ES_CHECK([fileManager fileExistsAtPath:pathBundle], NO, #"Internal error: file '%#' does not exist in .app bundle", pathBundle)
NSDictionary *appDBAttr = [fileManager fileAttributesAtPath:pathBundle traverseLink:YES];
ES_CHECK(appDBAttr!=nil, NO, #"Internal error: can't get attributes for '%#'", pathBundle)
appDBDate = [appDBAttr objectForKey:NSFileModificationDate];
ES_CHECK(appDBDate!=nil, NO, #"Internal error: can't get last modification date for '%#'", pathBundle)
needsCopy = [appDBDate compare:localDate] == NSOrderedDescending;
} else {
needsCopy = YES;
}
}
if (needsCopy) {
NSError *error;
BOOL success;
if (localAttr != nil) {
success = [fileManager removeItemAtPath:pathLocal error:&error];
ES_CHECK(success, NO, #"Can't delete file '%#'" ,pathLocal)
}
success = [fileManager copyItemAtPath:pathBundle toPath:pathLocal error:&error];
ES_CHECK(success, NO, #"Can't copy database '%#' to '%#': %#", pathBundle, pathLocal, [error localizedDescription])
ES_TRACE(#"Copied DB '%#' to '%#'", pathBundle, pathLocal)
return success;
}
return YES;
}
The ES_CHECK things are just macros that expand to nothing in release mode, and raise an exception in debug mode... They look like this:
#if ES_DEBUG
#define ES_ASSERT(cond) assert(cond);
#define ES_LOG(msg...) NSLog(msg);
#define ES_TRACE(msg...) NSLog(msg);
#else
#define ES_ASSERT(cond)
#define ES_LOG(msg...)
#define ES_TRACE(msg...)
#endif
#define ES_CHECK(cond, ret, msg...) if (!(cond)) { ES_LOG(msg) ES_ASSERT(cond) return (ret); } // Check with specified return value (when condition fails)
After trying a few techniques, I ended up adding a table to my database for meta-information and putting in a timestamp column. Each time I update my app, I check the timestamp of the bundle database against the timestamp of the copied database (i.e. in the Documents directory). It means I have to remember to change the timestamp value when I update, but it's simple and it works.
Using file timestamps didn't work, as there's a possibility of the user downloading the app in the App Review time window, and ending up with a copied database with a newer timestamp than the one in the bundle.

Open strings file from recource folder

I have a language file downloaded from a web service located in my resources folder of the project. The file name is "no.properties" and I need to load the file into a string to make further use of it..
The content of my file is displayed like this:
CMD_EXIT = Avslutt
CMD_PAUSE = Pause
CMD_SCAN = Skann
ect.....
Here is my code of loading the file into a string:
NSString* path = [[NSBundle mainBundle] pathForResource:[defaults objectForKey:#"language"]
ofType:#"properties"];
NSString* content = [NSString stringWithContentsOfFile:path
encoding:NSUTF8StringEncoding
error:NULL];
The problem is that the "content" string is null even though the file contains a lot of data. Anyone know what might be the problem?
note: I´ve checked that the value received from userdefaults equals "no". I have also tried to rename the file to "no.txt", but still the "content" string is null.
First, you could try to pass a NSError object to stringWithContentsOfFile:::.
You should receive a Cocoa error number, that (hopefully) tells you more about the problem.
NSError* err = nil;
NSString* string = [NSString stringWithContentsOfFile:path encoding:NSUTF16BigEndianStringEncoding error:&err];
As you seem to work with language files, I suspect that you are specifying the wrong encoding when reading in the strings.