using System; using System.Collections.Generic; using System.Net; using System.Net.Http; using System.Reactive.Linq; using System.Threading.Tasks; using Firebase.Database.Http; using Firebase.Database.Streaming; using Newtonsoft.Json; namespace Firebase.Database.Query { /// /// Represents a firebase query. /// public abstract class FirebaseQuery : IFirebaseQuery, IDisposable { protected readonly FirebaseQuery Parent; private HttpClient client; protected TimeSpan DEFAULT_HTTP_CLIENT_TIMEOUT = new TimeSpan(0, 0, 180); /// /// Initializes a new instance of the class. /// /// The parent of this query. /// The owning client. protected FirebaseQuery(FirebaseQuery parent, FirebaseClient client) { Client = client; Parent = parent; } /// /// Disposes this instance. /// public void Dispose() { client?.Dispose(); } /// /// Gets the client. /// public FirebaseClient Client { get; } /// /// Queries the firebase server once returning collection of items. /// /// Optional timeout value. /// Type of elements. /// Collection of holding the entities returned by server. public async Task>> OnceAsync(TimeSpan? timeout = null) { var url = string.Empty; try { url = await BuildUrlAsync().ConfigureAwait(false); } catch (Exception ex) { throw new FirebaseException("Couldn't build the url", string.Empty, string.Empty, HttpStatusCode.OK, ex); } return await GetClient(timeout).GetObjectCollectionAsync(url, Client.Options.JsonSerializerSettings) .ConfigureAwait(false); } /// /// Starts observing this query watching for changes real time sent by the server. /// /// Type of elements. /// Optional custom root element of received json items. /// Observable stream of . public IObservable> AsObservable( EventHandler> exceptionHandler = null, string elementRoot = "") { return Observable.Create>(observer => { var sub = new FirebaseSubscription(observer, this, elementRoot, new FirebaseCache()); sub.ExceptionThrown += exceptionHandler; return sub.Run(); }); } /// /// Builds the actual URL of this query. /// /// The . public async Task BuildUrlAsync() { // if token factory is present on the parent then use it to generate auth token if (Client.Options.AuthTokenAsyncFactory != null) { var token = await Client.Options.AuthTokenAsyncFactory().ConfigureAwait(false); return this.WithAuth(token).BuildUrl(null); } return BuildUrl(null); } /*public async Task>> OnceAsync(Type dataType, TimeSpan? timeout = null) { var url = string.Empty; try { url = await this.BuildUrlAsync().ConfigureAwait(false); } catch (Exception ex) { throw new FirebaseException("Couldn't build the url", string.Empty, string.Empty, HttpStatusCode.OK, ex); } return await this.GetClient(timeout).GetObjectCollectionAsync(url, Client.Options.JsonSerializerSettings, dataType) .ConfigureAwait(false); }*/ /// /// Assumes given query is pointing to a single object of type and retrieves it. /// /// Optional timeout value. /// Type of elements. /// Single object of type . public async Task OnceSingleAsync(TimeSpan? timeout = null) { var responseData = string.Empty; var statusCode = HttpStatusCode.OK; var url = string.Empty; try { url = await BuildUrlAsync().ConfigureAwait(false); } catch (Exception ex) { throw new FirebaseException("Couldn't build the url", string.Empty, responseData, statusCode, ex); } try { var response = await GetClient(timeout).GetAsync(url).ConfigureAwait(false); statusCode = response.StatusCode; responseData = await response.Content.ReadAsStringAsync().ConfigureAwait(false); response.EnsureSuccessStatusCode(); response.Dispose(); return JsonConvert.DeserializeObject(responseData, Client.Options.JsonSerializerSettings); } catch (Exception ex) { throw new FirebaseException(url, string.Empty, responseData, statusCode, ex); } } /// /// Posts given object to repository. /// /// The object. /// Specifies whether the key should be generated offline instead of online. /// Optional timeout value. /// Type of /// Resulting firebase object with populated key. public async Task> PostAsync(string data, bool generateKeyOffline = true, TimeSpan? timeout = null) { // post generates a new key server-side, while put can be used with an already generated local key if (generateKeyOffline) { var key = FirebaseKeyGenerator.Next(); await new ChildQuery(this, () => key, Client).PutAsync(data).ConfigureAwait(false); return new FirebaseObject(key, data); } var c = GetClient(timeout); var sendData = await SendAsync(c, data, HttpMethod.Post).ConfigureAwait(false); var result = JsonConvert.DeserializeObject(sendData, Client.Options.JsonSerializerSettings); return new FirebaseObject(result.Name, data); } /// /// Patches data at given location instead of overwriting them. /// /// The object. /// Optional timeout value. /// Type of /// The . public async Task PatchAsync(string data, TimeSpan? timeout = null) { var c = GetClient(timeout); await this.Silent().SendAsync(c, data, new HttpMethod("PATCH")).ConfigureAwait(false); } /// /// Sets or overwrites data at given location. /// /// The object. /// Optional timeout value. /// Type of /// The . public async Task PutAsync(string data, TimeSpan? timeout = null) { var c = GetClient(timeout); await this.Silent().SendAsync(c, data, HttpMethod.Put).ConfigureAwait(false); } /// /// Deletes data from given location. /// /// Optional timeout value. /// The . public async Task DeleteAsync(TimeSpan? timeout = null) { var c = GetClient(timeout); var url = string.Empty; var responseData = string.Empty; var statusCode = HttpStatusCode.OK; try { url = await BuildUrlAsync().ConfigureAwait(false); } catch (Exception ex) { throw new FirebaseException("Couldn't build the url", string.Empty, responseData, statusCode, ex); } try { var result = await c.DeleteAsync(url).ConfigureAwait(false); statusCode = result.StatusCode; responseData = await result.Content.ReadAsStringAsync().ConfigureAwait(false); result.EnsureSuccessStatusCode(); } catch (Exception ex) { throw new FirebaseException(url, string.Empty, responseData, statusCode, ex); } } /// /// Build the url segment of this child. /// /// The child of this query. /// The . protected abstract string BuildUrlSegment(FirebaseQuery child); private string BuildUrl(FirebaseQuery child) { var url = BuildUrlSegment(child); if (Parent != null) url = Parent.BuildUrl(this) + url; return url; } private HttpClient GetClient(TimeSpan? timeout = null) { if (client == null) client = new HttpClient(); if (!timeout.HasValue) client.Timeout = DEFAULT_HTTP_CLIENT_TIMEOUT; else client.Timeout = timeout.Value; return client; } private async Task SendAsync(HttpClient client, string data, HttpMethod method) { var responseData = string.Empty; var statusCode = HttpStatusCode.OK; var requestData = data; var url = string.Empty; try { url = await BuildUrlAsync().ConfigureAwait(false); } catch (Exception ex) { throw new FirebaseException("Couldn't build the url", requestData, responseData, statusCode, ex); } var message = new HttpRequestMessage(method, url) { Content = new StringContent(requestData) }; try { var result = await client.SendAsync(message).ConfigureAwait(false); statusCode = result.StatusCode; responseData = await result.Content.ReadAsStringAsync().ConfigureAwait(false); result.EnsureSuccessStatusCode(); return responseData; } catch (Exception ex) { throw new FirebaseException(url, requestData, responseData, statusCode, ex); } } } }