How can you tell if the metadata for a "file" is a directory or file?
I'm using the new V2 dropbox API for Objective-C. I don't see any way to distinguish between a file and directory. I'm expecting a .isDirectory call similar to what was available in V1 of the API.
Is this API available in the Swift version of the API?
The following code lists all files and directories at the given path. (simplified, as it is ignoring "more files")
- (void)loadMetadataForPath:(NSString*)path
{
[[self.dbUserClient.filesRoutes listFolder:path ] setResponseBlock:^(DBFILESListFolderResult * _Nullable result, DBFILESListFolderError * _Nullable routeError, DBRequestError * _Nullable networkError)
{
NSArray<DBFILESMetadata*> *entries = result.entries;
NSString *cursor = result.cursor;
BOOL hasMore = result.hasMore.boolValue;
for (DBFILESMetadata *fileMetadata in entries)
{
// How do I test if this file is a directory?
// BOOL isDirectory = fileMetadata.isDirectory;
NSLog(#"Dropbox filename = %#", fileMetadata.name);
}
// Ingoring more files for simplicity
}];
}
You can use isKindOfClass to detect the type of the entry. There's an example in the readme in the printEntries method here.
Related
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.
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.
From my application i need to call a web service to get the list of books in a server. For this purpose the following WSDL link is provided http://demo.kitaboo.com/eBookServices/services/ListOfBooksService?wsdl
Using the WSDL2ObjC Tool available at http://code.google.com/p/wsdl2objc/downloads/list i've generated the equivalent objective-C code for the given WSDL url.
This is the link which i referred while implementing to call the web service.
#import "MyWebService.h"
MyWebServiceBinding *binding = [MyWebService MyWebServiceBinding];
binding.logXMLInOut = YES;
ns1_MyOperationRequest *request = [[ns1_MyOperationRequest new] autorelease];
request.attribute = #"attributeValue";
request.element = [[ns1_MyElement new] autorelease];
request.element.value = #"elementValue"];
MyWebServiceBindingResponse *response = [binding myOperationUsingParameters:request];
NSArray *responseHeaders = response.headers;
NSArray *responseBodyParts = response.bodyParts;
for(id header in responseHeaders) {
if([header isKindOfClass:[ns2_MyHeaderResponse class]]) {
ns2_MyHeaderResponse *headerResponse = (ns2_MyHeaderResponse*)header;
// ... Handle ns2_MyHeaderResponse ...
}
}
for(id bodyPart in responseBodyParts) {
if([bodyPart isKindOfClass:[ns2_MyBodyResponse class]]) {
ns2_MyBodyResponse *body = (ns2_MyBodyResponse*)bodyPart;
// ... Handle ns2_MyBodyResponse ...
}
}
I'm unable to interrelate the terms such as (ns1_MyOperationRequest, MyWebServiceBindingResponse, myOperationUsingParameters) that are present in the code.
Any idea on how to go about doing this?
EDIT for your updated question:
In your header file, add the ListOfBooksServiceSoapBindingResponseDelegate and also implement - (void) operation:(ListOfBooksServiceSoapBindingOperation *)operation completedWithResponse:(ListOfBooksServiceSoapBindingResponse *)response;
Check the instructions:
Once you obtain WSDL2ObjC, code generation is pretty simple.
Launch the app
Browse to a WSDL file or enter in a URL
Browse to an output directory
Click "Parse WSDL"
Source code files will be added to the output directory you've
specified. There will be one pair of .h/.m files for each namespace in
your WSDL.
In case you didn't notice, you must have downloaded a standalone WSDL2ObjC.app. The window looks like this:
Just enter your WSDL link in link and get the code
http://sudzc.com/
I'm working on a new feature for an existing iPhone application, and would like to create several new directories in the application's local "Documents" folder. I have successfully done this using the recommended method:
[NSFileManager createDirectoryAtPath:withIntermediateDirectories:attributes:error:]
When reading the documentation for this method, I was intrigued by return values listed in Apple's official documentation:
Return Value:
YES if the operation was successful or already exists, otherwise NO
Each time my application starts up, I would like to ensure that the directories are properly in place. I thought a clever way of doing this would be to call the createDirectory: method on each start and take advantage of the method's return value. If the directory was missing for some reason, it would be created. If the directory was already in place, the return value would still be YES. A NO return value could then be used as a flag for additional recovery/repair logic.
Unfortunately, I appear to be getting results inconsistent with Apple's documentation. The method is returning NO if the directory already exists - when Apple's docs say it should return YES in this case.
The following program demonstrates this behavior:
#import <Foundation/Foundation.h>
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSFileManager * fm = [NSFileManager defaultManager];
bool testDirectoryCreated = NO;
testDirectoryCreated = [[NSFileManager defaultManager]createDirectoryAtPath: [NSString stringWithFormat:#"%#/%#",[fm currentDirectoryPath],#"TestDirectory"]
withIntermediateDirectories: NO
attributes: nil
error: NULL];
NSLog(#"TestDirectory Created: %#\n", (testDirectoryCreated ? #"YES" : #"NO"));
testDirectoryCreated = [[NSFileManager defaultManager]createDirectoryAtPath: [NSString stringWithFormat:#"%#/%#",[fm currentDirectoryPath],#"TestDirectory"]
withIntermediateDirectories: NO
attributes: nil
error: NULL];
NSLog(#"TestDirectory Created: %#\n", (testDirectoryCreated ? #"YES" : #"NO"));
[pool drain];
return 0;
}
When the program executes, it will print YES on the first createDirectory: call, and NO on the second call - when "TestDirectory" already exists.
Is this an error in Apple's documentation, or am I missing something?
Also, any other ideas for just validating the integrity of my directory structure? Is there a simple "directory exists" method I can call?
Thanks,
Tom
If you'd like to get the convenience to also check if the directory exists with this method, you must pass TRUE to the withIntermediateDirectories: parameter.
This is stated in Apple's documentation
In addition, if you pass NO for this parameter, the directory must not exist at the time this call is made.
It does seem odd to me that the return value would be YES, if the directory already exists. I would have expected this return Value to only reflect success on creating the dir. Which would be consistent to your returns.
As to your other question, you may want to look at fileExistsAtPath: and fileExistsAtPath:isDirectory: under the NSFileManager.
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.