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
65import Foundation
76import Alamofire
87import SwiftyJSON
98
109public 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