IEnumerator FileFetchAllVersions()
    {
        JCloudDocumentOperation operation = JCloudDocument.FileFetchAllVersions("testfile.txt");

        while (!operation.finished)
        {
            yield return(null);
        }

        if (operation.success && operation.result != null)
        {
            JCloudDocumentVersions versions = (JCloudDocumentVersions)operation.result;
            documentResultString = "cloud document test file versions :";
            int offset = 1;
            foreach (JCloudDocumentVersionMetadata metadata in versions.versionsMetadata)
            {
                documentResultString += " " + offset + ". " + metadata.modificationDate + (metadata.isCurrent ? " (current)" : "");
                offset++;
            }
            documentResultString += " (hash : " + versions.versionsHash + ")" + (operation.error != null ? (" ; error : " + operation.error) : "");
        }
        else
        {
            documentResultString = "cloud document test file versions : failure" + (operation.error != null ? (" ; error : " + operation.error) : "");
        }
    }
    IEnumerator FileReadConflictVersionBytes()
    {
        JCloudDocumentOperation operation;

        operation = JCloudDocument.FileFetchAllVersions("testfile.txt");
        while (!operation.finished)
        {
            yield return(null);
        }

        if (operation.success && operation.result != null)
        {
            JCloudDocumentVersions        versions = (JCloudDocumentVersions)operation.result;
            JCloudDocumentVersionMetadata?conflictVersionMetadata = null;
            foreach (JCloudDocumentVersionMetadata metadata in versions.versionsMetadata)
            {
                if (metadata.isCurrent == false)
                {
                    conflictVersionMetadata = metadata;
                    break;
                }
            }

            if (conflictVersionMetadata != null)
            {
                operation = JCloudDocument.FileReadVersionBytes("testfile.txt", conflictVersionMetadata.Value.uniqueIdentifier);
                while (!operation.finished)
                {
                    documentResultString = "cloud document read conflict version bytes progress : " + operation.progress;
                    yield return(null);
                }
                System.Text.UTF8Encoding encoder = new System.Text.UTF8Encoding();
                if (operation.success)
                {
                    documentResultString = "cloud document did read conflict version bytes ; read this : " + ((operation.result == null) ? "(null)" : encoder.GetString(operation.result as byte[])) + (operation.error != null ? (" ; error : " + operation.error) : "");
                }
                else
                {
                    documentResultString = "cloud document read conflict version bytes : failure" + (operation.error != null ? (" ; error : " + operation.error) : "");
                }
            }
            else
            {
                documentResultString = "cloud document read conflict version bytes : failure" + (operation.error != null ? (" ; error : " + operation.error) : "") + " (found no conflict version)";
            }
        }
        else
        {
            documentResultString = "cloud document read conflict version bytes : failure" + (operation.error != null ? (" ; error : " + operation.error) : "");
        }
    }
    IEnumerator FilePickCurrentVersion()
    {
        JCloudDocumentOperation operation;

        operation = JCloudDocument.FileFetchAllVersions("testfile.txt");
        while (!operation.finished)
        {
            yield return(null);
        }

        if (operation.success && operation.result != null)
        {
            JCloudDocumentVersions        versions = (JCloudDocumentVersions)operation.result;
            JCloudDocumentVersionMetadata?currentVersionMetadata = null;
            foreach (JCloudDocumentVersionMetadata metadata in versions.versionsMetadata)
            {
                if (metadata.isCurrent == true)
                {
                    currentVersionMetadata = metadata;
                    break;
                }
            }

            if (currentVersionMetadata != null)
            {
                operation = JCloudDocument.FilePickVersion("testfile.txt", currentVersionMetadata.Value.uniqueIdentifier, versions.versionsHash);
                while (!operation.finished)
                {
                    yield return(null);
                }

                if (operation.success)
                {
                    documentResultString = "cloud document did pick current version : " + (((bool)operation.result == true) ? "success" : "failure") + (operation.error != null ? (" ; error : " + operation.error) : "");
                }
                else
                {
                    documentResultString = "cloud document did pick current version : failure" + (operation.error != null ? (" ; error : " + operation.error) : "");
                }
            }
            else
            {
                documentResultString = "cloud document did pick current version : failure" + (operation.error != null ? (" ; error : " + operation.error) : "") + " (found no current version)";
            }
        }
        else
        {
            documentResultString = "cloud document did pick current version : failure" + (operation.error != null ? (" ; error : " + operation.error) : "");
        }
    }
    private IEnumerator DownloadFromCloudByCheckingConflicts(System.Action <UserCloudDownloadDelegateEventArgs> onResul = null)
    {
#if UNITY_IPHONE || UNITY_EDITOR
        JCloudDocumentOperation operation;

        // Let's check file for conflict first
        operation = JCloudDocument.FileHasConflictVersions(FILE_NAME_CLOUD);

        while (operation.finished == false)
        {
            yield return(null);            // Wait for 1 frame
        }
        // Look for error -- if any, handle & stop coroutine here -- ignore cloud unvailable as it simply means conflicts are not handled
        if (operation.error.HasValue && operation.error.Value != JCloudDocumentError.CloudUnavailable)
        {
            HandleDocumentError(operation.error.Value, onResul);
            yield break;
        }

        // Did looking for conflict trigger a result?
        if (((bool?)operation.result).HasValue && (bool)operation.result == true)
        {
            // We have conflicts -- sort them out
            operation = JCloudDocument.FileFetchAllVersions(FILE_NAME_CLOUD);
            while (operation.finished == false)
            {
                yield return(null);
            }

            // Look for error -- if any, handle & stop coroutine here
            if (operation.error.HasValue)
            {
                HandleDocumentError(operation.error.Value, onResul);
                yield break;
            }

            // Get conflict versions
            JCloudDocumentVersions versions = (JCloudDocumentVersions)operation.result;

            // We will now attempt to pick the "best" version (e.g. most progress) -- you could offer UI
            UserCloud temporalUser;
            int       lastLevelTemporalUser = -1;
            byte[]    bestVersionIdentifier = null;
            foreach (JCloudDocumentVersionMetadata metadata in versions.versionsMetadata)
            {
                // Read version bytes
                operation = JCloudDocument.FileReadVersionBytes(FILE_NAME_CLOUD, metadata.uniqueIdentifier);
                while (operation.finished == false)
                {
                    yield return(null);
                }

                // Look for error -- if any, handle & stop coroutine here
                if (operation.error.HasValue)
                {
                    HandleDocumentError(operation.error.Value, onResul);
                    yield break;
                }

                // Pick best version
                temporalUser = UserCloud.Deserialize((byte[])operation.result);

                if (temporalUser == null)
                {
                    continue;
                }

                if (temporalUser.LastFinishedLvl > lastLevelTemporalUser)
                {
                    lastLevelTemporalUser = temporalUser.LastFinishedLvl;
                    bestVersionIdentifier = metadata.uniqueIdentifier;
                }
            }

            // At that point we should have the best version
            if (bestVersionIdentifier != null)
            {
                // Pick it as current version
                operation = JCloudDocument.FilePickVersion(FILE_NAME_CLOUD, bestVersionIdentifier, versions.versionsHash);
                while (operation.finished == false)
                {
                    yield return(null);
                }

                // Look for error -- if any, handle & stop coroutine here
                if (operation.error.HasValue)
                {
                    HandleDocumentError(operation.error.Value, onResul);
                    yield break;
                }
            }
        }

        // At that point conflicts have been cleared -- wait for file reading
        operation = JCloudDocument.FileReadAllBytes(FILE_NAME_CLOUD);
        while (operation.finished == false)
        {
            yield return(null);            // Wait for 1 frame
        }
        // Look for error -- if any, handle & stop coroutine here
        if (operation.error.HasValue)
        {
            HandleDocumentError(operation.error.Value, onResul);
            yield break;
        }

        byte[] bytes = operation.result as byte[];
        if (bytes != null)
        {
            //Save iCloud info in Memory. Then, merge information with local user data
            Debug.Log("- LoadUserFromCloud OK");
            CurrentCloudUser = UserCloud.Deserialize(bytes);
            this.MergeUsers(onResul);
        }
#endif

#if UNITY_ANDROID && !disableGoogleCloud
        byte[] googleCloudDataBytes = null;

        if (CanUseGoogleCloud())
        {
            while (!PlayGameServices.isSignedIn() && googleCloudTryingToLogin)
            {
                yield return(null);                // Wait for n frames until googleCloudTryingToLogin was setted to FALSE
            }

            if (!PlayGameServices.isSignedIn() && !googleCloudTryingToLogin)
            {
                HandleDocumentError(JCloudDocumentError.CloudUnavailable, onResul);
                yield break;
            }

            Debug.Log("[UserManagerCloud] DownloadFromCloudByCheckingConflicts(): Downloading user data from cloud...");
            PlayGameServices.loadCloudDataForKey(0, false);

            // Wait a maximum of 6 seconds to retrieve google cloud data
            float timeLeftToWait = 6f;
            float lastTime       = Time.realtimeSinceStartup;
            while (googleCloudData == null && timeLeftToWait > 0f)
            {
                timeLeftToWait -= Time.realtimeSinceStartup - lastTime;
                lastTime        = Time.realtimeSinceStartup;

                yield return(null);                // Wait for n frames until googleCloudData was setted in loadCloudDataForKeyFailedEvent or loadCloudDataForKeySucceededEvent
            }

            if (googleCloudData == "ERROR" || googleCloudData == null)
            {
                Debug.Log("ANDROID CLOUD SERVICES RETURNED ERROR DOWNLOADING");
                HandleDocumentError(JCloudDocumentError.DocumentNotFound, onResul);
                yield break;
            }

            Debug.Log("[UserManagerCloud] There is google cloud data : " + googleCloudData.Length);

            try
            {
                googleCloudDataBytes = System.Convert.FromBase64String(googleCloudData);
                Debug.Log("[UserManagerCloud] Cloud data size: " + googleCloudDataBytes.Length);
            }
            catch (Exception)
            {
                Debug.LogWarning("[UserManagerCloud] Failed to decode android cloud data: " + googleCloudData);
            }
        }
        CurrentCloudUser = UserCloud.Deserialize(googleCloudDataBytes);
        MergeUsers(onResul);

        //Free memory
        googleCloudData = null;
#endif
        yield break;
    }
	// Fetch conflict versions metadata + current live version metadata at path
	protected static IEnumerator FileFetchAllVersionsOperation(string path, JCloudDocumentOperation operation) {
		// Failsafe
		if ((path == null) || (path == "")) {
			operation.error = JCloudDocumentError.InvalidArguments;
			operation.finished = true;
			yield break;
		}

#if !UNITY_EDITOR && ((UNITY_IPHONE && JCLOUDPLUGIN_IOS) || (UNITY_STANDALONE_OSX && JCLOUDPLUGIN_OSX))
		// Make sure our platfom is compatible
		if (JCloudManager.PlatformIsCloudCompatible() && (AcceptJailbrokenDevices || JCloudExtern.IsJailbroken() == false)) {
			// Prepare a new cloud document
			int documentUID = JCloudDocument.NewCloudDocument(path, false);
			
			// Any current operation on that document? Just wait yield until they end
			int lockId = JCloudManager.GetDocumentLock(documentUID);
			while (!JCloudManager.CheckDocumentLock(documentUID, lockId))
				yield return null;
					
			// Check for existence
			JCloudExtern.JCloudDocumentVersionsInternal versionsInternal = new JCloudExtern.JCloudDocumentVersionsInternal();
			if (JCloudExtern.CloudItemFetchAllVersions(documentUID, ref versionsInternal)) {
				JCloudManager.DocumentStatus? status;
				while (!JCloudManager.GetDocumentStatus(documentUID, out status))
					yield return null;
				
				if (status.Value.success) {
					// Spawn data in public struct
					JCloudDocumentVersions versions = new JCloudDocumentVersions();
					versions.versionsHash = Marshal.PtrToStringAnsi(versionsInternal.versionsHash);
					versions.versionsMetadata = new JCloudDocumentVersionMetadata[versionsInternal.versionsCount];
					
					System.IntPtr[] uniqueIdentifiersPointers = new System.IntPtr[versionsInternal.versionsCount];
					int[] uniqueIdentifiersLengthes = new int[versionsInternal.versionsCount];
					long[] modificationDates = new long[versionsInternal.versionsCount];
					byte[] isCurrent = new byte[versionsInternal.versionsCount];
					
					Marshal.Copy(versionsInternal.versionsUniqueIdentifiers, uniqueIdentifiersPointers, 0, versionsInternal.versionsCount);
					Marshal.Copy(versionsInternal.versionsUniqueIdentifiersLengthes, uniqueIdentifiersLengthes, 0, versionsInternal.versionsCount);
					Marshal.Copy(versionsInternal.versionsModificationDates, modificationDates, 0, versionsInternal.versionsCount);
					Marshal.Copy(versionsInternal.versionsIsCurrent, isCurrent, 0, versionsInternal.versionsCount);
					
					for (int i = 0; i < versionsInternal.versionsCount; i++) {
						JCloudDocumentVersionMetadata metadata = new JCloudDocumentVersionMetadata();
						
						metadata.uniqueIdentifier = new byte[uniqueIdentifiersLengthes[i]];
						Marshal.Copy(uniqueIdentifiersPointers[i], metadata.uniqueIdentifier, 0, uniqueIdentifiersLengthes[i]);
						metadata.modificationDate = new System.DateTime(1970, 1, 1, 0, 0, 0).AddSeconds(modificationDates[i]).ToLocalTime();
						metadata.isCurrent = (isCurrent[i] != 0);
							
						versions.versionsMetadata[i] = metadata;
					}
					
					// Free memory
					JCloudExtern.FreeMemory(versionsInternal.versionsHash);
					for (int i = 0; i < versionsInternal.versionsCount; i++) {
						JCloudExtern.FreeMemory(uniqueIdentifiersPointers[i]);
					}
					JCloudExtern.FreeMemory(versionsInternal.versionsUniqueIdentifiers);
					JCloudExtern.FreeMemory(versionsInternal.versionsUniqueIdentifiersLengthes);
					JCloudExtern.FreeMemory(versionsInternal.versionsModificationDates);
					JCloudExtern.FreeMemory(versionsInternal.versionsIsCurrent);
					
					operation.success = true;
					operation.result = versions;
				}
				operation.error = status.Value.error;
			} else
				operation.error = JCloudDocumentError.PluginError;
			
			// We have a result, no need to let user yield any longer
			operation.finished = true;
			
			// Dismiss document handling
			DismissCloudDocument(documentUID);
		} else {
#endif

#if !UNITY_WEBPLAYER && !UNITY_METRO
			operation.error = JCloudDocumentError.CloudUnavailable;
			operation.finished = true;
#else
			operation.error = JCloudDocumentError.InvalidPlatform;
			operation.finished = true;
#endif
#if !UNITY_EDITOR && ((UNITY_IPHONE && JCLOUDPLUGIN_IOS) || (UNITY_STANDALONE_OSX && JCLOUDPLUGIN_OSX))
		}
#endif
	}