Skip to content

Commit 71f75a0

Browse files
Merge pull request #210 from nextcloud/search
Unifield Search in async
2 parents 9e7b197 + 04415ec commit 71f75a0

1 file changed

Lines changed: 68 additions & 181 deletions

File tree

Sources/NextcloudKit/NextcloudKit+Search.swift

Lines changed: 68 additions & 181 deletions
Original file line numberDiff line numberDiff line change
@@ -1,162 +1,86 @@
11
// SPDX-FileCopyrightText: Nextcloud GmbH
2-
// SPDX-FileCopyrightText: 2022 Henrik Storch
3-
// SPDX-FileCopyrightText: 2023 Marino Faggiana
2+
// SPDX-FileCopyrightText: 2023/2026 Marino Faggiana
43
// SPDX-License-Identifier: GPL-3.0-or-later
54

65
import Foundation
76
import Alamofire
87
import SwiftyJSON
98

109
public extension NextcloudKit {
11-
/// Available NC >= 20
1210
/// Performs a unified search using multiple providers and returns results asynchronously.
1311
///
1412
/// - Parameters:
15-
/// - term: The search term to query.
1613
/// - timeout: The individual request timeout per provider.
17-
/// - timeoutProvider: The maximum time allowed for each provider before being cancelled.
1814
/// - account: The Nextcloud account performing the search.
1915
/// - options: Optional configuration for the request (headers, queue, etc.).
2016
/// - filter: A closure to filter which `NKSearchProvider` are enabled.
21-
/// - request: Callback to access and inspect the underlying `DataRequest?`.
2217
/// - taskHandler: Callback triggered when a `URLSessionTask` is created.
23-
/// - providers: Callback providing the list of providers that will be queried.
24-
/// - update: Called for every result update from a provider.
25-
/// - completion: Called when all providers are finished, returns the response and status.
26-
func unifiedSearch(term: String,
27-
timeout: TimeInterval = 30,
28-
timeoutProvider: TimeInterval = 60,
29-
account: String,
30-
options: NKRequestOptions = NKRequestOptions(),
31-
filter: @escaping (NKSearchProvider) -> Bool = { _ in true },
32-
request: @escaping (DataRequest?) -> Void,
33-
taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in },
34-
providers: @escaping (_ account: String, _ searchProviders: [NKSearchProvider]?) -> Void,
35-
update: @escaping (_ account: String, _ searchResult: NKSearchResult?, _ provider: NKSearchProvider, _ error: NKError) -> Void,
36-
completion: @escaping (_ account: String, _ responseData: AFDataResponse<Data>?, _ error: NKError) -> Void) {
18+
///
19+
/// - Returns: NKSearchProvider, NKError
20+
func unifiedSearchProviders(timeout: TimeInterval = 30,
21+
account: String,
22+
options: NKRequestOptions = NKRequestOptions(),
23+
filter: @escaping (NKSearchProvider) -> Bool = { _ in true },
24+
taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }
25+
) async -> (providers: [NKSearchProvider]?, error: NKError) {
3726
let endpoint = "ocs/v2.php/search/providers"
3827
guard let nkSession = nkCommonInstance.nksessions.session(forAccount: account),
3928
let url = nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint),
4029
let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else {
41-
return options.queue.async { completion(account, nil, .urlError) }
30+
return (nil, .urlError)
4231
}
4332

44-
let requestUnifiedSearch = nkSession.sessionData.request(url, method: .get, encoding: URLEncoding.default, headers: headers, interceptor: NKInterceptor(nkCommonInstance: nkCommonInstance)).validate(statusCode: 200..<300).onURLSessionTaskCreation { task in
45-
task.taskDescription = options.taskDescription
46-
taskHandler(task)
47-
}.responseData(queue: self.nkCommonInstance.backgroundQueue) { response in
48-
switch response.result {
49-
case .success(let jsonData):
50-
let json = JSON(jsonData)
51-
let providerData = json["ocs"]["data"]
52-
guard let allProvider = NKSearchProvider.factory(jsonArray: providerData) else {
53-
return completion(account, response, NKError(rootJson: json, fallbackStatusCode: response.response?.statusCode))
54-
}
55-
providers(account, allProvider)
33+
let request = nkSession.sessionData
34+
.request(url, headers: headers, interceptor: NKInterceptor(nkCommonInstance: nkCommonInstance))
35+
.validate(statusCode: 200..<300)
36+
.onURLSessionTaskCreation { task in
37+
task.taskDescription = options.taskDescription
38+
taskHandler(task)
39+
}
40+
let response = await request.serializingData().response
5641

57-
let filteredProviders = allProvider.filter(filter)
58-
let group = DispatchGroup()
42+
switch response.result {
43+
case .success(let jsonData):
44+
let json = JSON(jsonData)
45+
let providerData = json["ocs"]["data"]
46+
let providers = NKSearchProvider.factory(jsonArray: providerData)?.filter(filter)
5947

60-
for provider in filteredProviders {
61-
group.enter()
62-
let requestSearchProvider = self.searchProvider(provider.id, term: term, timeout: timeoutProvider, account: account, options: options) { account, partial, _, error in
63-
update(account, partial, provider, error)
64-
group.leave()
65-
}
66-
request(requestSearchProvider)
67-
}
48+
return(providers, .success)
49+
case .failure(let error):
50+
let nkError = NKError(error: error, afResponse: response, responseData: response.data)
6851

69-
group.notify(queue: options.queue) {
70-
completion(account, response, .success)
71-
}
72-
case .failure(let error):
73-
let error = NKError(error: error, afResponse: response, responseData: response.data)
74-
return completion(account, response, error)
75-
}
52+
return (nil, nkError)
7653
}
77-
request(requestUnifiedSearch)
7854
}
7955

80-
/// Asynchronously performs a unified search and returns the final search response.
81-
///
82-
/// - Parameters:
83-
/// - term: The string to search for.
84-
/// - timeout: Per-provider timeout in seconds.
85-
/// - timeoutProvider: Overall timeout for a provider.
86-
/// - account: The account used to authenticate the request.
87-
/// - options: Optional parameters for the search.
88-
/// - filter: Closure to filter the search providers.
89-
/// - request: Callback with the underlying `DataRequest?`.
90-
/// - taskHandler: Monitors the task creation.
91-
/// - providers: Callback that reports which providers are used.
92-
/// - update: Callback triggered as results come in from providers.
93-
/// - Returns: Final completion with account, raw response data, and NKError.
94-
func unifiedSearchAsync(term: String,
95-
timeout: TimeInterval = 30,
96-
timeoutProvider: TimeInterval = 60,
97-
account: String,
98-
options: NKRequestOptions = NKRequestOptions(),
99-
filter: @escaping (NKSearchProvider) -> Bool = { _ in true },
100-
request: @escaping (DataRequest?) -> Void,
101-
taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in },
102-
providers: @escaping (_ account: String, _ searchProviders: [NKSearchProvider]?) -> Void,
103-
update: @escaping (_ account: String, _ searchResult: NKSearchResult?, _ provider: NKSearchProvider, _ error: NKError) -> Void
104-
) async -> (
105-
account: String,
106-
responseData: AFDataResponse<Data>?,
107-
error: NKError
108-
) {
109-
await withCheckedContinuation { continuation in
110-
unifiedSearch(term: term,
111-
timeout: timeout,
112-
timeoutProvider: timeoutProvider,
113-
account: account,
114-
options: options,
115-
filter: filter,
116-
request: request,
117-
taskHandler: taskHandler,
118-
providers: providers,
119-
update: update) { account, responseData, error in
120-
continuation.resume(returning: (
121-
account: account,
122-
responseData: responseData,
123-
error: error
124-
))
125-
}
126-
}
127-
}
128-
129-
/// Available NC >= 20
13056
/// Performs a search using a specified provider with pagination and timeout support.
13157
///
13258
/// - Parameters:
133-
/// - id: The identifier of the search provider to use.
59+
/// - providerId: The identifier of the search provider to use.
13460
/// - term: The search term.
13561
/// - limit: Optional maximum number of results to return.
13662
/// - cursor: Optional pagination cursor for subsequent requests.
13763
/// - timeout: The timeout interval for the search request.
13864
/// - account: The Nextcloud account performing the search.
13965
/// - options: Optional request configuration such as headers and queue.
14066
/// - taskHandler: Callback to observe the underlying URLSessionTask.
141-
/// - completion: Completion handler returning the account, search results, raw response, and NKError.
14267
///
143-
/// - Returns: The underlying DataRequest object if the request was started, otherwise nil.
144-
func searchProvider(_ id: String,
145-
term: String,
146-
limit: Int? = nil,
147-
cursor: Int? = nil,
148-
timeout: TimeInterval = 60,
149-
account: String,
150-
options: NKRequestOptions = NKRequestOptions(),
151-
taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in },
152-
completion: @escaping (_ account: String, NKSearchResult?, _ responseData: AFDataResponse<Data>?, _ error: NKError) -> Void) -> DataRequest? {
68+
/// - Returns: NKSearchResult, NKError
69+
func unifiedSearch(providerId: String,
70+
term: String,
71+
limit: Int? = nil,
72+
cursor: Int? = nil,
73+
timeout: TimeInterval = 60,
74+
account: String,
75+
options: NKRequestOptions = NKRequestOptions(),
76+
taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in })
77+
async -> (searchResult: NKSearchResult?, error: NKError) {
15378
guard let term = term.urlEncoded,
15479
let nkSession = nkCommonInstance.nksessions.session(forAccount: account),
15580
let headers = nkCommonInstance.getStandardHeaders(account: account, options: options) else {
156-
completion(account, nil, nil, .urlError)
157-
return nil
81+
return(nil, .urlError)
15882
}
159-
var endpoint = "ocs/v2.php/search/providers/\(id)/search?term=\(term)"
83+
var endpoint = "ocs/v2.php/search/providers/\(providerId)/search?term=\(term)"
16084
if let limit = limit {
16185
endpoint += "&limit=\(limit)"
16286
}
@@ -165,83 +89,37 @@ public extension NextcloudKit {
16589
}
16690
guard let url = self.nkCommonInstance.createStandardUrl(serverUrl: nkSession.urlBase, endpoint: endpoint)
16791
else {
168-
completion(account, nil, nil, .urlError)
169-
return nil
92+
return(nil, .urlError)
17093
}
17194
var urlRequest: URLRequest
17295

17396
do {
17497
try urlRequest = URLRequest(url: url, method: .get, headers: headers)
17598
urlRequest.timeoutInterval = timeout
17699
} catch {
177-
completion(account, nil, nil, NKError(error: error))
178-
return nil
100+
return(nil, NKError(error: error))
179101
}
180102

181-
let requestSearchProvider = nkSession.sessionData.request(urlRequest, interceptor: NKInterceptor(nkCommonInstance: nkCommonInstance)).validate(statusCode: 200..<300).onURLSessionTaskCreation { task in
182-
task.taskDescription = options.taskDescription
183-
taskHandler(task)
184-
}.responseData(queue: self.nkCommonInstance.backgroundQueue) { response in
185-
switch response.result {
186-
case .success(let jsonData):
187-
let json = JSON(jsonData)
188-
let searchData = json["ocs"]["data"]
189-
guard let searchResult = NKSearchResult(json: searchData, id: id) else {
190-
return completion(account, nil, response, NKError(rootJson: json, fallbackStatusCode: response.response?.statusCode))
191-
}
192-
completion(account, searchResult, response, .success)
193-
case .failure(let error):
194-
let error = NKError(error: error, afResponse: response, responseData: response.data)
195-
return completion(account, nil, response, error)
103+
let request = nkSession.sessionData
104+
.request(urlRequest, interceptor: NKInterceptor(nkCommonInstance: nkCommonInstance))
105+
.validate(statusCode: 200..<300)
106+
.onURLSessionTaskCreation { task in
107+
task.taskDescription = options.taskDescription
108+
taskHandler(task)
196109
}
197-
}
110+
let response = await request.serializingData().response
198111

199-
return requestSearchProvider
200-
}
112+
switch response.result {
113+
case .success(let jsonData):
114+
let json = JSON(jsonData)
115+
let searchData = json["ocs"]["data"]
116+
let searchResult = NKSearchResult(json: searchData, id: providerId)
201117

202-
/// Asynchronously performs a search request using the specified provider.
203-
///
204-
/// - Parameters:
205-
/// - id: The identifier of the search provider to use.
206-
/// - term: The search query string.
207-
/// - limit: Optional limit for number of results.
208-
/// - cursor: Optional pagination cursor.
209-
/// - timeout: The timeout for the request.
210-
/// - account: The Nextcloud account performing the request.
211-
/// - options: Optional configuration options for the request.
212-
/// - taskHandler: Callback to observe the created task.
213-
///
214-
/// - Returns: A tuple containing the account, search result, response data, and error.
215-
func searchProviderAsync(_ id: String,
216-
term: String,
217-
limit: Int? = nil,
218-
cursor: Int? = nil,
219-
timeout: TimeInterval = 60,
220-
account: String,
221-
options: NKRequestOptions = NKRequestOptions(),
222-
taskHandler: @escaping (_ task: URLSessionTask) -> Void = { _ in }
223-
) async -> (
224-
account: String,
225-
searchResult: NKSearchResult?,
226-
responseData: AFDataResponse<Data>?,
227-
error: NKError
228-
) {
229-
await withCheckedContinuation { continuation in
230-
_ = searchProvider(id,
231-
term: term,
232-
limit: limit,
233-
cursor: cursor,
234-
timeout: timeout,
235-
account: account,
236-
options: options,
237-
taskHandler: taskHandler) { account, result, responseData, error in
238-
continuation.resume(returning: (
239-
account: account,
240-
searchResult: result,
241-
responseData: responseData,
242-
error: error
243-
))
244-
}
118+
return (searchResult, .success)
119+
case .failure(let error):
120+
let nkError = NKError(error: error, afResponse: response, responseData: response.data)
121+
122+
return (nil, nkError)
245123
}
246124
}
247125
}
@@ -309,6 +187,7 @@ public class NKSearchProvider: NSObject {
309187
public let id, name: String
310188
public let order: Int
311189

190+
// Initialize from JSON
312191
init?(json: JSON) {
313192
guard let id = json["id"].string,
314193
let name = json["name"].string,
@@ -319,6 +198,14 @@ public class NKSearchProvider: NSObject {
319198
self.order = order
320199
}
321200

201+
// Classic initializer
202+
public init(id: String, name: String, order: Int) {
203+
self.id = id
204+
self.name = name
205+
self.order = order
206+
super.init()
207+
}
208+
322209
static func factory(jsonArray: JSON) -> [NKSearchProvider]? {
323210
guard let allProvider = jsonArray.array else { return nil }
324211
return allProvider.compactMap(NKSearchProvider.init)

0 commit comments

Comments
 (0)