/// <summary>
        /// Creates folder using path specified
        /// </summary>
        /// <param name="dropboxFolderPath">Path of folder to create</param>
        /// <param name="onResult">Result callback that contains metadata of the created folder</param>
        public void CreateFolder(string dropboxFolderPath, Action <DropboxRequestResult <DBXFolder> > onResult)
        {
            var path = DropboxSyncUtils.NormalizePath(dropboxFolderPath);

            var prms = new DropboxCreateFolderRequestParams();

            prms.path = path;

            MakeDropboxRequest(CREATE_FOLDER_ENDPOINT, prms, (jsonStr) => {
                DBXFolder folderMetadata = null;

                try {
                    var root       = JSON.FromJson <Dictionary <string, object> >(jsonStr);
                    folderMetadata = DBXFolder.FromDropboxDictionary(root["metadata"] as Dictionary <string, object>);
                }catch (Exception ex) {
                    _mainThreadQueueRunner.QueueOnMainThread(() => {
                        onResult(DropboxRequestResult <DBXFolder> .Error(new DBXError(ex.Message, DBXErrorType.ParsingError)));
                    });
                    return;
                }

                _mainThreadQueueRunner.QueueOnMainThread(() => {
                    onResult(new DropboxRequestResult <DBXFolder>(folderMetadata));
                });
            }, onProgress: (progress) => {}, onWebError: (error) => {
                if (error.ErrorDescription.Contains("path/conflict/folder"))
                {
                    error.ErrorType = DBXErrorType.RemotePathAlreadyExists;
                }
                _mainThreadQueueRunner.QueueOnMainThread(() => {
                    onResult(DropboxRequestResult <DBXFolder> .Error(error));
                });
            });
        }
Example #2
0
        /// <summary>
        /// Unsubscribe specific callback from changes on specified Dropbox path
        /// </summary>
        /// <param name="dropboxPath">Path from which to unsubscribe</param>
        /// <param name="onChange">Callback reference</param>
        public void UnsubscribeFromChangesOnPath(string dropboxPath, Action <List <DBXFileChange> > onChange)
        {
            dropboxPath = DropboxSyncUtils.NormalizePath(dropboxPath);

            var item = OnChangeCallbacksDict.Where(p => p.Key.path == dropboxPath).Select(p => p.Key).FirstOrDefault();

            if (item != null)
            {
                OnChangeCallbacksDict[item].Remove(onChange);
            }
        }
Example #3
0
        /// <summary>
        /// Unsubscribes all subscribers from changes on specified Dropbox path
        /// </summary>
        /// <param name="dropboxPath">Path from which to unsubscribe</param>
        public void UnsubscribeAllFromChangesOnPath(string dropboxPath)
        {
            dropboxPath = DropboxSyncUtils.NormalizePath(dropboxPath);

            var removeKeys = OnChangeCallbacksDict.Where(p => p.Key.path == dropboxPath).Select(p => p.Key).ToList();

            foreach (var k in removeKeys)
            {
                OnChangeCallbacksDict.Remove(k);
            }
        }
        /// <summary>
        /// Retrieves structure of dropbox folders and files inside specified folder.
        /// </summary>
        /// <param name="dropboxFolderPath">Dropbox folder path</param>
        /// <param name="onResult">Callback function that receives result containing DBXFolder with all child nodes inside.</param>
        /// <param name="onProgress">Callback fnction that receives float from 0 to 1 intdicating the progress.</param>
        public void GetFolderStructure(string dropboxFolderPath, Action <DropboxRequestResult <DBXFolder> > onResult,
                                       Action <float> onProgress = null)
        {
            var path = DropboxSyncUtils.NormalizePath(dropboxFolderPath);

            _GetFolderItemsFlat(path, onResult: (items) => {
                DBXFolder rootFolder = null;

                // get root folder
                if (path == "/")
                {
                    rootFolder = new DBXFolder {
                        id = "", path = "/", name = "", items = new List <DBXItem>()
                    };
                }
                else
                {
                    rootFolder = items.Where(x => x.path == path).First() as DBXFolder;
                }
                // squash flat results
                rootFolder = BuildStructureFromFlat(rootFolder, items);

                _mainThreadQueueRunner.QueueOnMainThread(() => {
                    onResult(new DropboxRequestResult <DBXFolder>(rootFolder));
                });
            },
                                onProgress: (progress) => {
                if (onProgress != null)
                {
                    _mainThreadQueueRunner.QueueOnMainThread(() => {
                        onProgress(progress);
                    });
                }
            },
                                onError: (errorStr) => {
                _mainThreadQueueRunner.QueueOnMainThread(() => {
                    onResult(DropboxRequestResult <DBXFolder> .Error(errorStr));
                });
            }, recursive: true);
        }
        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;
            }
        }
        DBXFolder BuildStructureFromFlat(DBXFolder rootFolder, List <DBXItem> pool)
        {
            foreach (var poolItem in pool)
            {
                // if item is immediate child of rootFolder
                if (DropboxSyncUtils.IsPathImmediateChildOfFolder(rootFolder.path, poolItem.path))
                {
                    // add poolItem to folder children
                    if (poolItem.type == DBXItemType.Folder)
                    {
                        //Debug.Log("Build structure recursive");
                        rootFolder.items.Add(BuildStructureFromFlat(poolItem as DBXFolder, pool));
                    }
                    else
                    {
                        rootFolder.items.Add(poolItem);
                    }
                    //Debug.Log("Added child "+poolItem.path);
                }
            }

            return(rootFolder);
        }
Example #7
0
 public DBXFolder(string p)
 {
     type = DBXItemType.Folder;
     path = DropboxSyncUtils.NormalizePath(p);
 }
Example #8
0
 public DBXFile(string p)
 {
     type = DBXItemType.File;
     path = DropboxSyncUtils.NormalizePath(p);
 }
        // GETTING FILE

        /// <summary>
        /// Asynchronously retrieves file from Dropbox and tries to produce object of specified type T.
        /// </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 GetFile <T>(string dropboxPath, Action <DropboxRequestResult <T> > onResult, Action <float> onProgress = null, bool useCachedFirst = false,
                                bool useCachedIfOffline = true, bool receiveUpdates = false) where T : class
        {
            Action <DropboxRequestResult <byte[]> > onResultMiddle = null;

            if (typeof(T) == typeof(string))
            {
                //Log("GetFile: text type");

                // TEXT DATA
                onResultMiddle = (res) => {
                    if (res.error != null || res.data == null)
                    {
                        _mainThreadQueueRunner.QueueOnMainThread(() => {
                            onResult(DropboxRequestResult <T> .Error(res.error));
                        });
                    }
                    else
                    {
                        _mainThreadQueueRunner.QueueOnMainThread(() => {
                            onResult(new DropboxRequestResult <T>(DropboxSyncUtils.GetAutoDetectedEncodingStringFromBytes(res.data) as T));
                        });
                    }
                };
            }
            else if (typeof(T) == typeof(JsonObject) || typeof(T) == typeof(JsonArray))
            {
                //Log("GetFile: JSON type");

                // JSON OBJECT/ARRAY
                onResultMiddle = (res) => {
                    if (res.error != null)
                    {
                        _mainThreadQueueRunner.QueueOnMainThread(() => {
                            onResult(DropboxRequestResult <T> .Error(res.error));
                        });
                    }
                    else
                    {
                        _mainThreadQueueRunner.QueueOnMainThread(() => {
                            onResult(new DropboxRequestResult <T>(JSON.FromJson <T>(
                                                                      DropboxSyncUtils.GetAutoDetectedEncodingStringFromBytes(res.data)
                                                                      )));
                        });
                    }
                };
            }
            else if (typeof(T) == typeof(Texture2D))
            {
                //Log("GetFile: Texture2D type");
                // IMAGE DATA
                onResultMiddle = (res) => {
                    if (res.error != null)
                    {
                        _mainThreadQueueRunner.QueueOnMainThread(() => {
                            onResult(DropboxRequestResult <T> .Error(res.error));
                        });
                    }
                    else
                    {
                        _mainThreadQueueRunner.QueueOnMainThread(() => {
                            onResult(new DropboxRequestResult <T>(DropboxSyncUtils.LoadImageToTexture2D(res.data) as T));
                        });
                    }
                };
            }
            else
            {
                _mainThreadQueueRunner.QueueOnMainThread(() => {
                    onResult(DropboxRequestResult <T> .Error(
                                 new DBXError(string.Format("Dont have a mapping byte[] -> {0}. Type {0} is not supported.", typeof(T).ToString()),
                                              DBXErrorType.NotSupported
                                              )
                                 )
                             );
                });
                return;
            }

            GetFileAsBytes(dropboxPath, onResultMiddle, onProgress, useCachedFirst, useCachedIfOffline, receiveUpdates);
        }
        /// <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));
                    // });
                }
            });
        }
        void FileGetRemoteChanges(string dropboxFilePath, Action <DBXFileChange> onResult,
                                  Action <DBXError> onError, bool saveChangesInfoLocally = false)
        {
            var localFilePath   = GetPathInCache(dropboxFilePath);
            var localFileExists = File.Exists(localFilePath);

            var metadataFilePath = GetMetadataFilePath(dropboxFilePath);
            var localMetadata    = GetLocalMetadataForFile(dropboxFilePath);

            // request for metadata to get remote content hash
            //Log("Getting metadata");
            GetMetadata <DBXFile>(dropboxFilePath, onResult: (res) => {
                DBXFileChange result = null;

                if (res.error != null)
                {
                    Log("Failed to get remote metadata for file " + dropboxFilePath);
                    if (res.error.ErrorType == DBXErrorType.RemotePathNotFound)
                    {
                        Log("file not found - file was deleted or moved");
                        // file was deleted or moved

                        // if we knew about this file before
                        if (localMetadata != null)
                        {
                            // if we didnt know that it was removed
                            if (!localMetadata.deletedOnRemote)
                            {
                                result = new DBXFileChange(DBXFile.DeletedOnRemote(dropboxFilePath), DBXFileChangeType.Deleted);
                            }
                            else
                            {
                                // no change
                                result = new DBXFileChange(localMetadata, DBXFileChangeType.None);
                            }
                        }
                        else
                        {
                            onError(res.error);
                        }
                    }
                    else
                    {
                        onError(res.error);
                        return;
                    }
                }
                else
                {
                    Log("Got remote metadata for file " + dropboxFilePath);
                    var remoteMedatadata = res.data;

                    if (localMetadata != null && !localMetadata.deletedOnRemote)
                    {
                        Log("local metadata file exists and we knew this file existed on remote");
                        Log("check if remote content has changed");
                        // get local content hash
                        // var local_content_hash = localMetadata.contentHash;
                        string local_content_hash = null;
                        if (localFileExists)
                        {
                            local_content_hash = DropboxSyncUtils.GetDropboxContentHashForFile(localFilePath);
                        }
                        else
                        {
                            local_content_hash = localMetadata.contentHash;
                        }

                        var remote_content_hash = remoteMedatadata.contentHash;

                        if (local_content_hash != remote_content_hash)
                        {
                            Log("remote content hash has changed - file was modified");
                            result = new DBXFileChange(remoteMedatadata, DBXFileChangeType.Modified);
                        }
                        else
                        {
                            Log("remote content did not change");
                            result = new DBXFileChange(remoteMedatadata, DBXFileChangeType.None);
                        }
                    }
                    else
                    {
                        // metadata file doesnt exist
                        Log("local metadata file doesnt exist - consider as new file added");
                        // TODO: check maybe file itself exists and right version, then just create metadata file - no need to redownload file itself
                        result = new DBXFileChange(remoteMedatadata, DBXFileChangeType.Added);
                    }
                }

                // if no error
                if (result != null)
                {
                    if (saveChangesInfoLocally)
                    {
                        SaveFileMetadata(result.file);
                    }

                    onResult(result);
                }
            });
        }
        void _GetFolderItemsFlat(string folderPath, Action <List <DBXItem> > onResult, Action <float> onProgress,
                                 Action <DBXError> onError, bool recursive = false, string requestCursor = null, List <DBXItem> currentResults = null)
        {
            folderPath = DropboxSyncUtils.NormalizePath(folderPath);

            if (folderPath == "/")
            {
                folderPath = "";                 // dropbox error fix
            }

            string url;
            DropboxRequestParams prms;

            if (requestCursor == null)
            {
                // first request
                currentResults = new List <DBXItem>();
                url            = LIST_FOLDER_ENDPOINT;
                prms           = new DropboxListFolderRequestParams {
                    path = folderPath, recursive = recursive
                };
            }
            else
            {
                // have cursor to continue list
                url  = LIST_FOLDER_CONTINUE_ENDPOINT;
                prms = new DropboxContinueWithCursorRequestParams(requestCursor);
            }

            MakeDropboxRequest(url, prms, onResponse: (jsonStr) => {
                //Log("Got reponse: "+jsonStr);

                Dictionary <string, object> root = null;
                try {
                    root = JSON.FromJson <Dictionary <string, object> >(jsonStr);
                }catch (Exception ex) {
                    onError(new DBXError(ex.Message, DBXErrorType.ParsingError));
                    return;
                }

                var entries = root["entries"] as List <object>;
                foreach (Dictionary <string, object> entry in entries)
                {
                    if (entry[".tag"].ToString() == "file")
                    {
                        currentResults.Add(DBXFile.FromDropboxDictionary(entry));
                    }
                    else if (entry[".tag"].ToString() == "folder")
                    {
                        currentResults.Add(DBXFolder.FromDropboxDictionary(entry));
                    }
                    else
                    {
                        onError(new DBXError("Unknown entry tag " + entry[".tag".ToString()], DBXErrorType.Unknown));
                        return;
                    }
                }

                if ((bool)root["has_more"])
                {
                    // recursion
                    _GetFolderItemsFlat(folderPath, onResult, onProgress, onError, recursive: recursive,
                                        requestCursor: root["cursor"].ToString(),
                                        currentResults: currentResults);
                }
                else
                {
                    // done
                    onResult(currentResults);
                }
            }, onProgress: onProgress,
                               onWebError: (webErrorStr) => {
                //LogError("Got web err: "+webErrorStr);
                onError(webErrorStr);
            });
        }