diff --git a/AlgoliaSearch-Client-Swift.podspec b/AlgoliaSearch-Client-Swift.podspec index 340568802..930b6c05b 100644 --- a/AlgoliaSearch-Client-Swift.podspec +++ b/AlgoliaSearch-Client-Swift.podspec @@ -1,5 +1,6 @@ Pod::Spec.new do |s| s.name = 'AlgoliaSearch-Client-Swift' + s.module_name = 'AlgoliaSearch' s.version = '1.2.0' s.license = 'MIT' s.summary = 'Algolia Search API Client for iOS & OS X written in Swift.' diff --git a/README.md b/README.md index 90e62ee5f..f119fb030 100644 --- a/README.md +++ b/README.md @@ -29,9 +29,10 @@ Table of Contents **Commands Reference** -1. [Add a new object](#add-a-new-object-in-the-index) +1. [Add a new object](#add-a-new-object-to-the-index) 1. [Update an object](#update-an-existing-object-in-the-index) 1. [Search](#search) +1. [Multiple queries](#multiple-queries) 1. [Get an object](#get-an-object) 1. [Delete an object](#delete-an-object) 1. [Delete by query](#delete-by-query) @@ -43,13 +44,12 @@ Table of Contents 1. [Batch writes](#batch-writes) 1. [Security / User API Keys](#security--user-api-keys) 1. [Copy or rename an index](#copy-or-rename-an-index) -1. [Backup / Retrieve all index content](#backup--retrieve-all-index-content) +1. [Backup / Retrieve all index content](#backup--retrieve-of-all-index-content) 1. [Logs](#logs) - Setup ------------- To setup your project, follow these steps: @@ -68,15 +68,10 @@ let client = AlgoliaSearch.Client(appID: "YourApplicationID", apiKey: "YourAPIKe - - - - - - Quick Start ------------- + In 30 seconds, this quick start tutorial will show you how to index and search objects. Without any prior configuration, you can start indexing [500 contacts](https://github.com/algolia/algoliasearch-client-csharp/blob/master/contacts.json) in the ```contacts``` index using the following code: @@ -158,13 +153,8 @@ index.search(Query(fullTextQuery: "jim"), block: { (JSON, error) -> Void in - - - - Documentation ================ - Check our [online documentation](http://www.algolia.com/doc/guides/swift): * [Initial Import](http://www.algolia.com/doc/guides/swift#InitialImport) * [Ranking & Relevance](http://www.algolia.com/doc/guides/swift#RankingRelevance) @@ -177,7 +167,6 @@ Check our [online documentation](http://www.algolia.com/doc/guides/swift): * [Security](http://www.algolia.com/doc/guides/swift#Security) * [REST API](http://www.algolia.com/doc/rest) - Tutorials ================ @@ -193,8 +182,6 @@ Commands Reference - - Add a new object to the Index ------------- @@ -322,8 +309,6 @@ let partialObject = ["price": operation] index.partialUpdateObject(partialObject, objectID: "myID", block: nil) ``` - - Search ------------- @@ -486,6 +471,10 @@ client.multipleQueries(queries, block: { (JSON, error) -> Void in }) ``` +You can specify a strategy to optimize your multiple queries: +- **none**: Execute the sequence of queries until the end. +- **stopIfEnoughMatches**: Execute the sequence of queries until the number of hits is reached by the sum of hits. + Get an object @@ -510,18 +499,12 @@ index.getObject("myID", attributesToRetrieve: ["firstname"], block: { (JSON, err You can also retrieve a set of objects: - ```swift index.getObjects(["myID1", "myID2"], block: { (JSON, error) -> { // do something }) ``` - - - - - Delete an object ------------- @@ -553,8 +536,8 @@ You can retrieve all settings using the `` function. The result will contain the * **attributesToIndex**: (array of strings) The list of fields you want to index.
If set to null, all textual and numerical attributes of your objects are indexed. Be sure to update it to get optimal results.
This parameter has two important uses: * *Limit the attributes to index*.
For example, if you store a binary image in base64, you want to store it and be able to retrieve it, but you don't want to search in the base64 string. * *Control part of the ranking*.
(see the ranking parameter for full explanation) Matches in attributes at the beginning of the list will be considered more important than matches in attributes further down the list. In one attribute, matching text at the beginning of the attribute will be considered more important than text after. You can disable this behavior if you add your attribute inside `unordered(AttributeName)`. For example, `attributesToIndex: ["title", "unordered(text)"]`. -**Notes**: All numerical attributes are automatically indexed as numerical filters. If you don't need filtering on some of your numerical attributes, please consider sending them as strings to speed up the indexing.
You can decide to have the same priority for two attributes by passing them in the same string using a comma as a separator. For example `title` and `alternative_title` have the same priority in this example, which is different than text priority: `attributesToIndex:["title,alternative_title", "text"]`. +* **numericAttributesToIndex**: (array of strings) All numerical attributes are automatically indexed as numerical filters. If you don't need filtering on some of your numerical attributes, you can specify this list to speed up the indexing.
If you only need to filter on a numeric value with the operator '=', you can speed up the indexing by specifying the attribute with `equalOnly(AttributeName)`. The other operators will be disabled. * **attributesForFaceting**: (array of strings) The list of fields you want to use for faceting. All strings in the attribute selected for faceting are extracted and added as a facet. If set to null, no attribute is used for faceting. * **attributeForDistinct**: The attribute name used for the `Distinct` feature. This feature is similar to the SQL "distinct" keyword. When enabled in queries with the `distinct=1` parameter, all hits containing a duplicate value for this attribute are removed from results. For example, if the chosen attribute is `show_name` and several hits have the same value for `show_name`, then only the best one is kept and others are removed. **Note**: This feature is disabled if the query string is empty and there aren't any `tagFilters`, `facetFilters`, nor `numericFilters` parameters. * **ranking**: (array of strings) Controls the way results are sorted.
We have nine available criteria: @@ -726,6 +709,17 @@ index.partialUpdateObjects([obj1, obj2], block: { (JSON, error) -> Void in +If you have one index per user, you may want to perform a batch operations across severals indexes. +We expose a method to perform this type of batch: + + +The attribute **action** can have these values: +- addObject +- updateObject +- partialUpdateObject +- partialUpdateObjectNoCreate +- deleteObject + Security / User API Keys ------------- @@ -778,13 +772,17 @@ index.addUserKey(["search"], block: { (JSON, error) -> Void in }) ``` -You can also create an API Key with advanced restrictions: +You can also create an API Key with advanced settings: * Add a validity period. The key will be valid for a specific period of time (in seconds). * Specify the maximum number of API calls allowed from an IP address per hour. Each time an API call is performed with this key, a check is performed. If the IP at the source of the call did more than this number of calls in the last hour, a 403 code is returned. Defaults to 0 (no rate limit). This parameter can be used to protect you from attempts at retrieving your entire index contents by massively querying the index. * Specify the maximum number of hits this API key can retrieve in one call. Defaults to 0 (unlimited). This parameter can be used to protect you from attempts at retrieving your entire index contents by massively querying the index. * Specify the list of targeted indices. You can target all indices starting with a prefix or ending with a suffix using the '*' character. For example, "dev_*" matches all indices starting with "dev_" and "*_dev" matches all indices ending with "_dev". Defaults to all indices if empty or blank. + * Specify the list of referers. You can target all referers starting with a prefix or ending with a suffix using the '*' character. For example, "algolia.com/*" matches all referers starting with "algolia.com/" and "*.algolia.com" matches all referers ending with ".algolia.com". Defaults to all referers if empty or blank. + * Specify the list of query parameters. You can force the query parameters for a query using the url string format (param1=X¶m2=Y...). + * Specify a description to describe where the key is used. + ```swift // Creates a new global API key that is valid for 300 seconds @@ -965,4 +963,3 @@ client.getLogsWithOffset(0, length: 100, block: { (JSON, error) -> Void in - diff --git a/Source/Client.swift b/Source/Client.swift index 093cba769..105ba4484 100644 --- a/Source/Client.swift +++ b/Source/Client.swift @@ -33,7 +33,7 @@ public class Client { setExtraHeader(apiKey, forKey: "X-Algolia-API-Key") } } - + /// Security tag header (see http://www.algolia.com/doc/guides/objc#SecurityUser for more details). public var tagFilters: String? { didSet { @@ -44,7 +44,7 @@ public class Client { } } } - + /// User-token header (see http://www.algolia.com/doc/guides/objc#SecurityUser for more details). public var userToken: String? { didSet { @@ -55,19 +55,19 @@ public class Client { } } } - + private let timeout: NSTimeInterval = 30 private let searchTimeout: NSTimeInterval = 5 private let incrementTimeout: NSTimeInterval = 10 - + public let appID: String - + let readQueryHostnames: [String] let writeQueryHostnames: [String] - + private let manager: Manager private var requestBuffer = RingBuffer(maxCapacity: 10) - + /// Algolia Search initialization. /// /// :param: appID the application ID you have in your admin interface @@ -80,43 +80,44 @@ public class Client { } else if count(apiKey) == 0 { NSException(name: "InvalidArgument", reason: "APIKey must be set", userInfo: nil).raise() } - + self.appID = appID self.apiKey = apiKey self.tagFilters = tagFilters self.userToken = userToken - + readQueryHostnames = [ "\(appID)-DSN.algolia.net", "\(appID)-1.algolianet.com", "\(appID)-2.algolianet.com", "\(appID)-3.algolianet.com" ] - + writeQueryHostnames = [ "\(appID).algolia.net", "\(appID)-1.algolianet.com", "\(appID)-2.algolianet.com", "\(appID)-3.algolianet.com" ] - - let version = NSBundle(identifier: "com.algolia.AlgoliaSearch")!.infoDictionary!["CFBundleShortVersionString"] as! String + + let version = NSBundle(forClass: self.dynamicType).infoDictionary!["CFBundleShortVersionString"] as! String + var HTTPHeaders = [ "X-Algolia-API-Key": self.apiKey, "X-Algolia-Application-Id": self.appID, "User-Agent": "Algolia for Swift \(version)" ] - + if let tagFilters = self.tagFilters { HTTPHeaders["X-Algolia-TagFilters"] = tagFilters } if let userToken = self.userToken { HTTPHeaders["X-Algolia-UserToken"] = userToken } - + manager = Manager(HTTPHeaders: HTTPHeaders) } - + /// Allow to set custom extra header. /// /// :param: value value of the header @@ -129,16 +130,16 @@ public class Client { manager.session.configuration.HTTPAdditionalHeaders = HTTPHeader } } - + // MARK: - Operations - + /// List all existing indexes. /// /// :return: JSON Object in the handler in the form: { "items": [ {"name": "contacts", "createdAt": "2013-01-18T15:33:13.556Z"}, {"name": "notes", "createdAt": "2013-01-18T15:33:13.556Z"}]} public func listIndexes(block: CompletionHandler) { performHTTPQuery("1/indexes", method: .GET, body: nil, hostnames: readQueryHostnames, block: block) } - + /// Delete an index. /// /// :param: indexName the name of index to delete @@ -147,7 +148,7 @@ public class Client { let path = "1/indexes/\(indexName.urlEncode())" performHTTPQuery(path, method: .DELETE, body: nil, hostnames: writeQueryHostnames, block: block) } - + /// Move an existing index. /// /// :param: srcIndexName the name of index to move. @@ -158,10 +159,10 @@ public class Client { "destination": dstIndexName, "operation": "move" ] - + performHTTPQuery(path, method: .POST, body: request, hostnames: writeQueryHostnames, block: block) } - + /// Copy an existing index. /// /// :param: srcIndexName the name of index to copy. @@ -172,15 +173,15 @@ public class Client { "destination": dstIndexName, "operation": "copy" ] - + performHTTPQuery(path, method: .POST, body: request, hostnames: writeQueryHostnames, block: block) } - + /// Return 10 last log entries. public func getLogs(block: CompletionHandler) { performHTTPQuery("1/logs", method: .GET, body: nil, hostnames: readQueryHostnames, block: block) } - + /// Return last logs entries. /// /// :param: offset Specify the first entry to retrieve (0-based, 0 is the most recent log entry). @@ -189,7 +190,7 @@ public class Client { let path = "1/logs?offset=\(offset)&length=\(length)" performHTTPQuery(path, method: .GET, body: nil, hostnames: readQueryHostnames, block: block) } - + /// Return last logs entries. /// /// :param: offset Specify the first entry to retrieve (0-based, 0 is the most recent log entry). @@ -198,31 +199,31 @@ public class Client { let path = "1/logs?offset=\(offset)&length=\(length)&type=\(type)" performHTTPQuery(path, method: .GET, body: nil, hostnames: readQueryHostnames, block: block) } - + /// Get the index object initialized (no server call needed for initialization). /// /// :param: indexName the name of index public func getIndex(indexName: String) -> Index { return Index(client: self, indexName: indexName) } - + /// List all existing user keys with their associated ACLs. public func listUserKeys(block: CompletionHandler) { performHTTPQuery("1/keys", method: .GET, body: nil, hostnames: readQueryHostnames, block: block) } - + /// Get ACL of a user key. public func getUserKeyACL(key: String, block: CompletionHandler) { let path = "1/keys/\(key)" performHTTPQuery(path, method: .GET, body: nil, hostnames: readQueryHostnames, block: block) } - + /// Delete an existing user key. public func deleteUserKey(key: String, block: CompletionHandler? = nil) { let path = "1/keys/\(key)" performHTTPQuery(path, method: .DELETE, body: nil, hostnames: writeQueryHostnames, block: block) } - + /// Create a new user key /// /// :param: acls The list of ACL for this key. The list can contains the following values (as String): search, addObject, deleteObject, deleteIndex, settings, editSettings @@ -230,7 +231,7 @@ public class Client { let request = ["acl": acls] performHTTPQuery("1/keys", method: .POST, body: request, hostnames: writeQueryHostnames, block: block) } - + /// Create a new user key /// /// :param: acls The list of ACL for this key. The list can contains the following values (as String): search, addObject, deleteObject, deleteIndex, settings, editSettings @@ -244,10 +245,10 @@ public class Client { "maxQueriesPerIPPerHour": maxQueries, "maxHitsPerQuery": maxHits, ] - + performHTTPQuery("1/keys", method: .POST, body: request, hostnames: writeQueryHostnames, block: block) } - + /// Create a new user key /// /// :param: acls The list of ACL for this key. The list can contains the following values (as String): search, addObject, deleteObject, deleteIndex, settings, editSettings @@ -263,10 +264,10 @@ public class Client { "maxQueriesPerIPPerHour": maxQueries, "maxHitsPerQuery": maxHits, ] - + performHTTPQuery("1/keys", method: .POST, body: request, hostnames: writeQueryHostnames, block: block) } - + /// Update a user key /// /// :param: key The key to update @@ -276,7 +277,7 @@ public class Client { let request = ["acl": acls] performHTTPQuery(path, method: .PUT, body: request, hostnames: writeQueryHostnames, block: block) } - + /// Update a user key /// /// :param: key The key to update @@ -292,10 +293,10 @@ public class Client { "maxQueriesPerIPPerHour": maxQueries, "maxHitsPerQuery": maxHits, ] - + performHTTPQuery(path, method: .PUT, body: request, hostnames: writeQueryHostnames, block: block) } - + /// Update a user key /// /// :param: key The key to update @@ -313,16 +314,16 @@ public class Client { "maxQueriesPerIPPerHour": maxQueries, "maxHitsPerQuery": maxHits, ] - + performHTTPQuery(path, method: .PUT, body: request, hostnames: writeQueryHostnames, block: block) } - + /// Query multiple indexes with one API call. /// /// :param: queries An array of queries with the associated index (Array of Dictionnary object ["indexName": "targettedIndex", "query": QueryObject]). public func multipleQueries(queries: [AnyObject], block: CompletionHandler? = nil) { let path = "1/indexes/*/queries" - + var convertedQueries = [[String: String]]() convertedQueries.reserveCapacity(queries.count) for query in queries { @@ -333,23 +334,23 @@ public class Client { ]) } } - + let request = ["requests": convertedQueries] performHTTPQuery(path, method: .POST, body: request, hostnames: readQueryHostnames, block: block) } - + // MARK: - Network - + /// Perform an HTTP Query. func performHTTPQuery(path: String, method: HTTPMethod, body: [String: AnyObject]?, hostnames: [String], isSearchQuery: Bool = false, index: Int = 0, block: CompletionHandler? = nil) { assert(index < hostnames.count, "\(index) < \(hostnames.count) !") - + var currentTimeout = (isSearchQuery) ? searchTimeout : timeout if index > 1 { currentTimeout += incrementTimeout } manager.session.configuration.timeoutIntervalForRequest = currentTimeout - + let request = manager.request(method, "https://\(hostnames[index])/\(path)", parameters: body) { (response, data, error) -> Void in if let statusCode = response?.statusCode { if let block = block { @@ -379,10 +380,10 @@ public class Client { } } } - + requestBuffer.append(request) } - + /// Cancel a queries. Only the last ten queries can be cancelled. func cancelQueries(method: HTTPMethod, path: String) { for request in requestBuffer { @@ -393,4 +394,4 @@ public class Client { } } } -} \ No newline at end of file +}