void Update()
        {
            _internetConnectionWatcher.Update();
            _mainThreadQueueRunner.PerformQueuedTasks();

            // check remote changes for subscribed
            if (Time.unscaledTime - _lastTimeCheckedForSubscribedItemsChanges > CHECK_REMOTE_UPDATES_INTERVAL_SECONDS)
            {
                DropboxSyncUtils.IsOnlineAsync((isOnline) => {
                    if (isOnline)
                    {
                        try {
                            CheckChangesForSubscribedItems();
                        }catch (Exception ex) {
                            Debug.LogException(ex);
                        }
                    }
                });

                _lastTimeCheckedForSubscribedItemsChanges = Time.unscaledTime;
            }
        }
        /// <summary>
        /// Asynchronously retrieves file from Dropbox as byte[]
        /// </summary>
        /// <param name="dropboxPath">Path to file on Dropbox or inside of Dropbox App folder (depending on accessToken type). Should start with "/". Example: /DropboxSyncExampleFolder/image.jpg</param>
        /// <param name="onResult">Result callback</param>
        /// <param name="onProgress">Callback function that receives progress as float from 0 to 1.</param>
        /// <param name="useCachedFirst">If True then first tries to get data from cache, if not cached then downloads.</param>
        /// <param name="useCachedIfOffline">If True and there's no Internet connection then retrieves file from cache if cached, otherwise produces error.</param>
        /// <param name="receiveUpdates">If true , then when there are remote updates on Dropbox, callback function onResult will be triggered again with updated version of the file.</param>
        public void GetFileAsBytes(string dropboxPath, Action <DropboxRequestResult <byte[]> > onResult, Action <float> onProgress = null, bool useCachedFirst = false,
                                   bool useCachedIfOffline = true, bool receiveUpdates = false)
        {
            if (DropboxSyncUtils.IsBadDropboxPath(dropboxPath))
            {
                onResult(DropboxRequestResult <byte[]> .Error(
                             new DBXError("Cant get file: bad path " + dropboxPath, DBXErrorType.BadRequest)
                             )
                         );
                return;
            }

            Action returnCachedResult = () => {
                var cachedFilePath = GetPathInCache(dropboxPath);

                if (File.Exists(cachedFilePath))
                {
                    var bytes = File.ReadAllBytes(cachedFilePath);
                    _mainThreadQueueRunner.QueueOnMainThread(() => {
                        onResult(new DropboxRequestResult <byte[]>(bytes));
                    });
                }
                else
                {
                    Log("cache doesnt have file");
                    _mainThreadQueueRunner.QueueOnMainThread(() => {
                        onResult(
                            DropboxRequestResult <byte[]> .Error(
                                new DBXError("File " + dropboxPath + " is removed on remote", DBXErrorType.RemotePathNotFound)
                                )
                            );
                    });
                }
            };

            Action subscribeToUpdatesAction = () => {
                SubscribeToFileChanges(dropboxPath, (fileChange) => {
                    UpdateFileFromRemote(dropboxPath, onSuccess: () => {
                        // return updated cached result
                        returnCachedResult();
                    }, onProgress: (progress) => {
                        if (onProgress != null)
                        {
                            _mainThreadQueueRunner.QueueOnMainThread(() => {
                                onProgress(progress);
                            });
                        }
                    }, onError: (error) => {
                        _mainThreadQueueRunner.QueueOnMainThread(() => {
                            onResult(DropboxRequestResult <byte[]> .Error(error));
                        });
                    });
                });
            };

            // maybe no need to do any remote requests
            if ((useCachedFirst) && IsFileCached(dropboxPath))
            {
                Log("GetFile: using cached version");
                returnCachedResult();

                if (receiveUpdates)
                {
                    subscribeToUpdatesAction();
                }
            }
            else
            {
                //Log("GetFile: check if online");
                // now check if we online

                DropboxSyncUtils.IsOnlineAsync((isOnline) => {
                    try {
                        if (isOnline)
                        {
                            Log("GetFile: internet available");
                            // check if have updates and load them
                            UpdateFileFromRemote(dropboxPath, onSuccess: () => {
                                Log("GetFile: state of dropbox file is " + dropboxPath + " is synced now");
                                // return updated cached result

                                returnCachedResult();


                                if (receiveUpdates)
                                {
                                    subscribeToUpdatesAction();
                                }
                            }, onProgress: (progress) => {
                                if (onProgress != null)
                                {
                                    _mainThreadQueueRunner.QueueOnMainThread(() => {
                                        onProgress(progress);
                                    });
                                }
                            }, onError: (error) => {
                                //Log("error");
                                _mainThreadQueueRunner.QueueOnMainThread(() => {
                                    onResult(DropboxRequestResult <byte[]> .Error(error));
                                });

                                if (receiveUpdates)
                                {
                                    subscribeToUpdatesAction();
                                }
                            });
                        }
                        else
                        {
                            Log("GetFile: internet not available");

                            if (useCachedIfOffline && IsFileCached(dropboxPath))
                            {
                                Log("GetFile: cannot check for updates - using cached version");

                                returnCachedResult();


                                if (receiveUpdates)
                                {
                                    subscribeToUpdatesAction();
                                }
                            }
                            else
                            {
                                if (receiveUpdates)
                                {
                                    // try again when internet recovers
                                    _internetConnectionWatcher.SubscribeToInternetConnectionRecoverOnce(() => {
                                        GetFileAsBytes(dropboxPath, onResult, onProgress, useCachedFirst, useCachedIfOffline, receiveUpdates);
                                    });

                                    subscribeToUpdatesAction();
                                }
                                else
                                {
                                    // error
                                    _mainThreadQueueRunner.QueueOnMainThread(() => {
                                        onResult(DropboxRequestResult <byte[]> .Error(
                                                     new DBXError("GetFile: No internet connection", DBXErrorType.NetworkProblem)
                                                     )
                                                 );
                                    });
                                }
                            }
                        }
                    }catch (Exception ex) {
                        Debug.LogException(ex);
                    }
                });
            }
        }
        void MakeDropboxRequest(string url, string jsonParameters,
                                Action <string> onResponse, Action <float> onProgress, Action <DBXError> onWebError)
        {
            Log("MakeDropboxRequest url: " + url);



            DropboxSyncUtils.IsOnlineAsync((isOnline) => {
                if (!isOnline)
                {
                    onWebError(new DBXError("No internet connection", DBXErrorType.NetworkProblem));
                    return;
                }

                try {
                    using (var client = new DBXWebClient()){
                        client.Headers.Set("Authorization", "Bearer " + DropboxAccessToken);
                        client.Headers.Set("Content-Type", "application/json");

                        client.DownloadProgressChanged += (s, e) => {
                            if (onProgress != null)
                            {
                                Log(string.Format("Downloaded {0} bytes out of {1}", e.BytesReceived, e.TotalBytesToReceive));
                                if (e.TotalBytesToReceive != -1)
                                {
                                    // if download size in known from server
                                    // _mainThreadQueueRunner.QueueOnMainThread(() => {
                                    onProgress((float)e.BytesReceived / e.TotalBytesToReceive);
                                    // });
                                }
                                else
                                {
                                    // return progress is going but unknown
                                    // _mainThreadQueueRunner.QueueOnMainThread(() => {
                                    onProgress(-1);
                                    // });
                                }
                            }
                        };

                        client.UploadDataCompleted += (s, e) => {
                            Log("MakeDropboxRequest -> UploadDataCompleted");
                            _activeWebClientsList.Remove(client);

                            if (e.Error != null)
                            {
                                Log("MakeDropboxRequest -> UploadDataCompleted -> Error");
                                //LogError("MakeDropboxRequest -> UploadDataCompleted -> Error "+e.Error.Message);

                                if (e.Error is WebException)
                                {
                                    Log("MakeDropboxRequest -> UploadDataCompleted -> Error -> WebException");
                                    var webex = e.Error as WebException;

                                    try{
                                        var stream      = webex.Response.GetResponseStream();
                                        var reader      = new StreamReader(stream);
                                        var responseStr = reader.ReadToEnd();

                                        var dict         = JSON.FromJson <Dictionary <string, object> >(responseStr);
                                        var errorSummary = dict["error_summary"].ToString();

                                        // _mainThreadQueueRunner.QueueOnMainThread(() => {
                                        onWebError(new DBXError(errorSummary, DBXError.DropboxAPIErrorSummaryToErrorType(errorSummary)));
                                        // });
                                    }catch (Exception ex) {
                                        Log("MakeDropboxRequest -> UploadDataCompleted -> Error -> WebException -> try get summary -> Exception: " + ex.Message);
                                        // _mainThreadQueueRunner.QueueOnMainThread(() => {
                                        onWebError(new DBXError(e.Error.Message, DBXErrorType.ParsingError));
                                        // });
                                    }
                                }
                                else
                                {
                                    // _mainThreadQueueRunner.QueueOnMainThread(() => {
                                    Log("MakeDropboxRequest -> UploadDataCompleted -> Error -> not WebException");
                                    if (e.Error != null)
                                    {
                                        onWebError(new DBXError(e.Error.Message + "\n" + e.Error.StackTrace, DBXErrorType.Unknown));
                                    }
                                    else
                                    {
                                        onWebError(new DBXError("Unknown error", DBXErrorType.Unknown));
                                    }
                                    // });
                                }
                            }
                            else if (e.Cancelled)
                            {
                                onWebError(new DBXError("User canceled request", DBXErrorType.UserCancelled));
                            }
                            else
                            {
                                // no error
                                var respStr = Encoding.UTF8.GetString(e.Result);
                                // _mainThreadQueueRunner.QueueOnMainThread(() => {
                                onResponse(respStr);
                                // });
                            }
                        };

                        var uri = new Uri(url);
                        Log("MakeDropboxRequest:client.UploadDataAsync");
                        client.UploadDataAsync(uri, "POST", Encoding.Default.GetBytes(jsonParameters));
                        _activeWebClientsList.Add(client);
                    }
                } catch (Exception ex) {
                    //onWebError(ex.Message);
                    //Log("caught exeption");
                    onWebError(new DBXError(ex.Message, DBXErrorType.Unknown));
                    //Log(ex.Response.ToString());
                }
            });
        }
        void MakeDropboxUploadRequest(string url, byte[] dataToUpload, string jsonParameters,
                                      Action <DBXFile> onResponse, Action <float> onProgress, Action <DBXError> onWebError)
        {
            DropboxSyncUtils.IsOnlineAsync((isOnline) => {
                if (!isOnline)
                {
                    onWebError(new DBXError("No internet connection", DBXErrorType.NetworkProblem));
                    return;
                }

                try {
                    using (var client = new DBXWebClient()){
                        client.Headers.Set("Authorization", "Bearer " + DropboxAccessToken);
                        client.Headers.Set("Dropbox-API-Arg", jsonParameters);
                        client.Headers.Set("Content-Type", "application/octet-stream");



                        client.UploadProgressChanged += (s, e) => {
                            Log(string.Format("Upload {0} bytes out of {1} ({2}%)", e.BytesSent, e.TotalBytesToSend, e.ProgressPercentage));

                            if (onProgress != null)
                            {
                                // _mainThreadQueueRunner.QueueOnMainThread(() => {
                                var uploadProgress = (float)e.BytesSent / e.TotalBytesToSend;

                                var SERVER_PROCESSING_PERCENTAG_VALUE = 0.99f;

                                if (uploadProgress > SERVER_PROCESSING_PERCENTAG_VALUE && e.ProgressPercentage < 100)
                                {
                                    // waiting for server to process uploaded file
                                    onProgress(SERVER_PROCESSING_PERCENTAG_VALUE);
                                }
                                else
                                {
                                    onProgress(uploadProgress);
                                }
                                // });
                            }
                        };

                        client.UploadDataCompleted += (s, e) => {
                            Log("MakeDropboxUploadRequest -> UploadDataCompleted");
                            _activeWebClientsList.Remove(client);

                            if (e.Error != null)
                            {
                                Log("MakeDropboxUploadRequest -> UploadDataCompleted -> with error");

                                if (e.Error is WebException)
                                {
                                    Log("MakeDropboxUploadRequest -> UploadDataCompleted -> with error -> WebException");
                                    var webex       = e.Error as WebException;
                                    var stream      = webex.Response.GetResponseStream();
                                    var reader      = new StreamReader(stream);
                                    var responseStr = reader.ReadToEnd();
                                    LogWarning(responseStr);

                                    try{
                                        var dict         = JSON.FromJson <Dictionary <string, object> >(responseStr);
                                        var errorSummary = dict["error_summary"].ToString();
                                        // _mainThreadQueueRunner.QueueOnMainThread(() => {
                                        onWebError(new DBXError(errorSummary, DBXErrorType.DropboxAPIError));
                                        // });
                                    }catch {
                                        // _mainThreadQueueRunner.QueueOnMainThread(() => {
                                        onWebError(new DBXError(e.Error.Message, DBXErrorType.ParsingError));
                                        // });
                                    }
                                }
                                else
                                {
                                    // _mainThreadQueueRunner.QueueOnMainThread(() => {
                                    Log("e.Error is something else");
                                    if (e.Error != null)
                                    {
                                        onWebError(new DBXError(e.Error.Message, DBXErrorType.Unknown));
                                    }
                                    else
                                    {
                                        onWebError(new DBXError("Unknown error", DBXErrorType.Unknown));
                                    }

                                    // });
                                }
                            }
                            else if (e.Cancelled)
                            {
                                Log("MakeDropboxUploadRequest -> canceled");
                                // _mainThreadQueueRunner.QueueOnMainThread(() => {
                                onWebError(new DBXError("Download was cancelled.", DBXErrorType.UserCancelled));
                                // });
                            }
                            else
                            {
                                Log("MakeDropboxUploadRequest -> no error");
                                //var respStr = Encoding.UTF8.GetString(e.Result);
                                var metadataJsonStr = Encoding.UTF8.GetString(e.Result);;
                                Log(metadataJsonStr);
                                var dict         = JSON.FromJson <Dictionary <string, object> >(metadataJsonStr);
                                var fileMetadata = DBXFile.FromDropboxDictionary(dict);

                                // _mainThreadQueueRunner.QueueOnMainThread(() => {
                                onResponse(fileMetadata);
                                // });
                            }
                        };

                        var uri = new Uri(url);

                        // don't use UploadFile (https://stackoverflow.com/questions/18539807/how-to-remove-multipart-form-databoundary-from-webclient-uploadfile)
                        client.UploadDataAsync(uri, "POST", dataToUpload);
                        _activeWebClientsList.Add(client);
                    }
                } catch (WebException ex) {
                    // _mainThreadQueueRunner.QueueOnMainThread(() => {
                    onWebError(new DBXError(ex.Message, DBXErrorType.Unknown));
                    // });
                }
            });
        }
        void MakeDropboxDownloadRequest(string url, string jsonParameters,
                                        Action <DBXFile, byte[]> onResponse, Action <float> onProgress, Action <DBXError> onWebError)
        {
            DropboxSyncUtils.IsOnlineAsync((isOnline) => {
                if (!isOnline)
                {
                    onWebError(new DBXError("No internet connection", DBXErrorType.NetworkProblem));
                    return;
                }

                try {
                    using (var client = new DBXWebClient()){
                        client.Headers.Set("Authorization", "Bearer " + DropboxAccessToken);
                        client.Headers.Set("Dropbox-API-Arg", jsonParameters);

                        client.DownloadProgressChanged += (s, e) => {
                            if (onProgress != null)
                            {
                                //Log(string.Format("Downloaded {0} bytes out of {1} ({2}%)", e.BytesReceived, e.TotalBytesToReceive, e.ProgressPercentage));
                                if (e.TotalBytesToReceive != -1)
                                {
                                    // if download size in known from server
                                    // _mainThreadQueueRunner.QueueOnMainThread(() => {
                                    onProgress((float)e.BytesReceived / e.TotalBytesToReceive);
                                    // });
                                }
                                else
                                {
                                    // return progress is going but unknown
                                    // _mainThreadQueueRunner.QueueOnMainThread(() => {
                                    onProgress(-1);
                                    // });
                                }
                            }
                        };



                        client.DownloadDataCompleted += (s, e) => {
                            _activeWebClientsList.Remove(client);

                            if (e.Error != null)
                            {
                                if (e.Error is WebException)
                                {
                                    var webex       = e.Error as WebException;
                                    var stream      = webex.Response.GetResponseStream();
                                    var reader      = new StreamReader(stream);
                                    var responseStr = reader.ReadToEnd();
                                    Log(responseStr);

                                    try{
                                        var dict         = JSON.FromJson <Dictionary <string, object> >(responseStr);
                                        var errorSummary = dict["error_summary"].ToString();
                                        // _mainThreadQueueRunner.QueueOnMainThread(() => {
                                        onWebError(new DBXError(errorSummary, DBXError.DropboxAPIErrorSummaryToErrorType(errorSummary)));
                                        // });
                                    }catch {
                                        // _mainThreadQueueRunner.QueueOnMainThread(() => {
                                        onWebError(new DBXError(e.Error.Message, DBXErrorType.ParsingError));
                                        // });
                                    }
                                }
                                else
                                {
                                    // _mainThreadQueueRunner.QueueOnMainThread(() => {
                                    Log("e.Error is something else");
                                    if (e.Error != null)
                                    {
                                        onWebError(new DBXError(e.Error.Message, DBXErrorType.Unknown));
                                    }
                                    else
                                    {
                                        onWebError(new DBXError("Unknown error", DBXErrorType.Unknown));
                                    }
                                    // });
                                }
                            }
                            else if (e.Cancelled)
                            {
                                // _mainThreadQueueRunner.QueueOnMainThread(() => {
                                onWebError(new DBXError("Download was cancelled.", DBXErrorType.UserCancelled));
                                // });
                            }
                            else
                            {
                                //var respStr = Encoding.UTF8.GetString(e.Result);
                                var metadataJsonStr = client.ResponseHeaders["Dropbox-API-Result"].ToString();
                                Log(metadataJsonStr);
                                var dict         = JSON.FromJson <Dictionary <string, object> >(metadataJsonStr);
                                var fileMetadata = DBXFile.FromDropboxDictionary(dict);

                                // _mainThreadQueueRunner.QueueOnMainThread(() => {
                                onResponse(fileMetadata, e.Result);
                                // });
                            }
                        };

                        var uri = new Uri(url);
                        client.DownloadDataAsync(uri);
                        _activeWebClientsList.Add(client);
                    }
                } catch (WebException ex) {
                    // _mainThreadQueueRunner.QueueOnMainThread(() => {
                    onWebError(new DBXError(ex.Message, DBXErrorType.Unknown));
                    // });
                }
            });
        }