public void StartDownloadThread(Bundle bundleInfo) { if (m_isApplicationQuitting) { return; } if (!ShouldDownloadWithCurrentConnection(bundleInfo)) { return; } if (m_downloadThread != null && m_downloadThread.IsAlive) { return; } if (bundleInfo != null) { Debug.Log("AssetBundleDownloadManager. Starting Download: " + bundleInfo.assetBundleName); m_downloadThread = new Thread(() => DoDownload(bundleInfo, AssetBundleManager.Instance.CurrentReachability)); m_downloadThread.Start(); AssetBundleStateChanged.Invoke(this, new AssetBundleStateChangedEventArgs(bundleInfo.assetBundleName, AssetBundleDownloadState.Downloading)); } }
public void GetBundleCRCInformationFromServer() { Debug.Log("AssetBundleInformationManager: GetBundleCRCInformation"); try { var request = (HttpWebRequest)HttpWebRequest.Create(AssetBundleManager.Instance.BundleServerURL + "assetbundles.crc"); request.Timeout = 5000; //5 secs timeout Debug.Log("Request URI (CRC): " + request.RequestUri.AbsoluteUri); using (var response = (HttpWebResponse)request.GetResponse()) { m_realtimeToPollAgain += m_realtimeToPollAgain; // Take longer each time we call to prevent over polling. if ((int)response.StatusCode == 200) { Debug.Log("AssetBundleInformationManager: LoadCRCInformation Server"); Stream dataStream = response.GetResponseStream(); StreamReader reader = new StreamReader(dataStream); string strResponse = reader.ReadToEnd(); ParseCRCInformation(strResponse, m_serverAssetBundleCRCInformation); //SERVER CRC INFO: // Compare server CRC's with local CRC's for changes. foreach (var newServerCrc in m_serverAssetBundleCRCInformation) { foreach (AssetBundleCRCInfo localCrc in m_localAssetBundleCRCInformation) { if (localCrc.m_name == newServerCrc.m_name) { //this is an asset bundle we already know about from local crc info if (localCrc.m_fileSizeBytes != newServerCrc.m_fileSizeBytes || localCrc.m_CRC != newServerCrc.m_CRC) { Debug.Log("AssetBundleInformationManager::GetBundleCRCInformationFromServer - Detected a new version of the bundle from the server. Wiping the current file and downloading the new one."); //the server has a file that's different from the one we have here. Wipe any copy of this file, but don't count it as a failure to load AssetBundleLocalFileChecked.Invoke(this, new AssetBundleLocalFileCheckedEventArgs(localCrc.m_name, false, 0, newServerCrc.m_fileSizeBytes)); AssetBundleDownloadCorruptedOrBroken.Invoke(this, new AssetBundleDeleteAssetBundleFileEventArgs(localCrc.m_name, null)); AssetBundleRevokePermision.Invoke(this, new AssetBundleEventArgs(localCrc.m_name)); //no point keeping old crc info for a file we never had/couldn't load. Simply replace the crc info int index = m_localAssetBundleCRCInformation.IndexOf(localCrc); m_localAssetBundleCRCInformation[index] = newServerCrc; } //otherwise it didn't change, and we can simply continue goto localCrcFound; } } //this code is skipped if the file was found in the known assetbundle list //unknown asset bundle, add it to the list m_localAssetBundleCRCInformation.Add(newServerCrc); AssetBundleLocalFileChecked.Invoke(this, new AssetBundleLocalFileCheckedEventArgs(newServerCrc.m_name, false, 0, newServerCrc.m_fileSizeBytes)); AssetBundleStateChanged.Invoke(this, new AssetBundleStateChangedEventArgs(newServerCrc.m_name, AssetBundleDownloadState.WaitingManualPermission, null)); localCrcFound :; } WriteCRCInfoToDisk(); reader.Close(); HasLoadedServerCRCInfo = true; m_AssetBundleCRCInformationThread = null; } else { Debug.LogError("AssetBundleInformationManager: GetBundleCRCInformation Failed With Response: " + response.StatusCode); m_AssetBundleCRCInformationThread = null; } response.Close(); } } catch (WebException e) { if (AssetBundleManager.Instance.CurrentReachability == NetworkReachability.NotReachable) { Debug.LogWarning("No internet access, cannot get asset bundle data"); return; } m_realtimeToPollAgain += m_realtimeToPollAgain; Debug.LogException(e); Debug.LogError("AssetBundleInformationManager: GetBundleCRCInformationFromServer WebException " + e.ToString()); m_AssetBundleCRCInformationThread = null; } catch (ThreadAbortException) { m_realtimeToPollAgain += m_realtimeToPollAgain; Debug.LogWarning("AssetBundleInformationManager: GetBundleCRCInformationFromServer Thread aborted. Assuming intentionally."); } catch (Exception e) { m_realtimeToPollAgain += m_realtimeToPollAgain; Debug.LogException(e); Debug.LogError("AssetBundleInformationManager: GetBundleCRCInformationFromServer Exception " + e.ToString()); } }
public void CheckLocalAssetBundle(AssetBundleCRCInfo localCrc, int localCrcIndex, AssetBundleCRCInfo?serverCrc) { bool isInGame = AssetBundleManager.Instance.IsInGame; string fullFileName = AssetBundleManager.Instance.CurrentVersionDownloadLocation + "/" + localCrc.m_name; string tempFileName = fullFileName + ".incomplete"; FileInfo fullAssetFileInfo = new FileInfo(fullFileName); if (fullAssetFileInfo.Exists) { //check filesize first if (fullAssetFileInfo.Length == localCrc.m_fileSizeBytes) { if (isInGame) { //the file is the correct size, but CRC is slow, so we'll delay the CRC //don't do this now! return; } Debug.Log("AssetBundleInformationManager::CheckLocalAssetBundle : Completed file: " + localCrc.m_name); //sweet, same length, it's complete, now to check CRC uint crc = AssetBundleUtils.GenerateCRC32FromFile(fullFileName); if (crc == localCrc.m_CRC) { //it's the right file, untampered. //it's loadable. Debug.Log("AssetBundleInformationManager::CheckLocalAssetBundle : Local Asset Bundle " + localCrc.m_name + " is Loadable. SUCCESS"); AssetBundleLocalFileChecked.Invoke(this, new AssetBundleLocalFileCheckedEventArgs(localCrc.m_name, true, localCrc.m_fileSizeBytes, localCrc.m_fileSizeBytes)); AssetBundleStateChanged.Invoke(this, new AssetBundleStateChangedEventArgs(localCrc.m_name, AssetBundleDownloadState.Loadable, null)); AssetBundleRevokePermision.Invoke(this, new AssetBundleEventArgs(localCrc.m_name)); } else { //tampered? or damaged? Delete it. Debug.LogError("AssetBundleInformationManager::CheckLocalAssetBundle : Local Asset Bundle " + localCrc.m_name + " failed the CRC check. Deleting."); AssetBundleDownloadCorruptedOrBroken.Invoke(this, new AssetBundleDeleteAssetBundleFileEventArgs(localCrc.m_name, "FAILED_LOCAL_CRC_CHECK")); } } else { //it's not an incomplete dl, and it's either too big or too small, not sure how that could happen, wipe it and force a redownload //no point crcing as the bytes are definitely different. Debug.LogError("AssetBundleInformationManager: Local Asset Bundle " + localCrc.m_name + " failed the LENGTH check. How!? Deleting."); AssetBundleDownloadCorruptedOrBroken.Invoke(this, new AssetBundleDeleteAssetBundleFileEventArgs(localCrc.m_name, "FAILED_LOCAL_LENGTH_CHECK")); } } FileInfo tempAssetFileInfo = new FileInfo(tempFileName); if (tempAssetFileInfo.Exists) { Debug.Log("AssetBundleInformationManager::CheckLocalAssetBundle : Temporary File: " + localCrc.m_name); if (isInGame) { if (serverCrc.HasValue && tempAssetFileInfo.Length < serverCrc.Value.m_fileSizeBytes) { // We don't know what this file is, but we'll assume it's a partially downloaded NEW asset bundle and put it back in the queue Debug.Log("AssetBundleInformationManager::CheckLocalAssetBundle : Temporary Asset Bundle " + localCrc.m_name + " failed CRC. It's small enough to feasibly be a partially-downloaded new asset bundle. Download continuing."); AssetBundleLocalFileChecked.Invoke(this, new AssetBundleLocalFileCheckedEventArgs(serverCrc.Value.m_name, false, tempAssetFileInfo.Length, serverCrc.Value.m_fileSizeBytes)); AssetBundleStateChanged.Invoke(this, new AssetBundleStateChangedEventArgs(serverCrc.Value.m_name, AssetBundleDownloadState.Queued, null)); } return; } else { uint crc = AssetBundleUtils.GenerateCRC32FromFile(tempFileName); bool crcMatchesLocal = localCrc.m_CRC == crc; bool crcMatchesServer = serverCrc.HasValue && serverCrc.Value.m_CRC == crc; if (crcMatchesLocal || crcMatchesServer) { // Crc matches either! if (!crcMatchesLocal) { // The recently completed ".incomplete" file will now be moved to the final destination (oooh). // Thus, the local CRC info needs to match the new crc. m_localAssetBundleCRCInformation[localCrcIndex] = serverCrc.Value; } // The file is loadable, so move it to the right file name. Yay! Debug.Log("AssetBundleInformationManager::CheckLocalAssetBundle : Temporary Asset Bundle " + localCrc.m_name + " matched a CRC check and is now loadable. SUCCESS." + "\nServer: " + crcMatchesServer + ", Local: " + crcMatchesLocal); AssetBundleFinishedAndRequiresMoving.Invoke(this, new AssetBundleEventArgs(localCrc.m_name)); } else if (serverCrc.HasValue && tempAssetFileInfo.Length < serverCrc.Value.m_fileSizeBytes) { // We don't know what this file is, but we'll assume it's a partially downloaded NEW asset bundle. Debug.Log("AssetBundleInformationManager::CheckLocalAssetBundle : Temporary Asset Bundle " + localCrc.m_name + " failed CRC. It's small enough to feasibly be a partially-downloaded new asset bundle. Download continuing."); AssetBundleLocalFileChecked.Invoke(this, new AssetBundleLocalFileCheckedEventArgs(serverCrc.Value.m_name, false, tempAssetFileInfo.Length, serverCrc.Value.m_fileSizeBytes)); AssetBundleStateChanged.Invoke(this, new AssetBundleStateChangedEventArgs(serverCrc.Value.m_name, AssetBundleDownloadState.Queued, null)); } else if (HasLoadedServerCRCInfo) { // It failed all CRC AND we have got all server CRC's. Thus, this file must be corrupted. Debug.LogError("AssetBundleInformationManager::CheckLocalAssetBundle : Local Asset Bundle " + localCrc.m_name + " does not match any CRC, and we have all server CRC's. Assumed corrupted."); AssetBundleDownloadCorruptedOrBroken.Invoke(this, new AssetBundleDeleteAssetBundleFileEventArgs(localCrc.m_name, "FAILED_SERVER_CRC_CHECK")); } else { // We don't know if this is a new version, so no checks are worth doing on this until we have server info Debug.Log("AssetBundleInformationManager::CheckLocalAssetBundle : Local Asset Bundle " + localCrc.m_name + " does not match local CRC and we do not currently have the server CRC's. " + "It's probably a new bundle in a server CRC that we haven't downloaded for this play session yet. We'll queue it for download, pending the server CRC's."); AssetBundleLocalFileChecked.Invoke(this, new AssetBundleLocalFileCheckedEventArgs(localCrc.m_name, false, tempAssetFileInfo.Length, localCrc.m_fileSizeBytes)); AssetBundleStateChanged.Invoke(this, new AssetBundleStateChangedEventArgs(localCrc.m_name, AssetBundleDownloadState.WaitingManualPermission, null)); } } } // CRC checking complete. Phew. tempAssetFileInfo.Refresh(); fullAssetFileInfo.Refresh(); if (!tempAssetFileInfo.Exists && !fullAssetFileInfo.Exists) { // No file left after CRC checks, so the only thing to do is begin downloading afresh. AssetBundleLocalFileChecked.Invoke(this, new AssetBundleLocalFileCheckedEventArgs(localCrc.m_name, false, 0, localCrc.m_fileSizeBytes)); AssetBundleStateChanged.Invoke(this, new AssetBundleStateChangedEventArgs(localCrc.m_name, AssetBundleDownloadState.WaitingManualPermission, null)); } }
private void DoDownload(Bundle BundleInfo, NetworkReachability reachability) { Debug.Log("Download Thread Started: " + BundleInfo.assetBundleName); NetworkReachability startingReachability = reachability; string downloadLocation = AssetBundleManager.Instance.CurrentVersionDownloadLocation + "/" + BundleInfo.assetBundleName; FileInfo tempFileInfo = new FileInfo(downloadLocation + ".incomplete"); FileStream saveFileStream = null; try { Debug.Log("AssetBundler DoDownload " + BundleInfo.assetBundleName + " to " + AssetBundleManager.Instance.CurrentVersionDownloadLocation); CreateDownloadDirectory(); long existingLength = 0; if (tempFileInfo.Exists) { existingLength = tempFileInfo.Length; } else { //making file incomplete, to mark it for future automatic download try { FileStream fs = tempFileInfo.Create(); fs.Close(); } catch (Exception e) { //if we can't make a temp file, we quit. go away Debug.LogException(e); Debug.LogError("AssetBundler DoDownload: Critical failure in creating temp file: " + e.ToString()); AssetBundleStateChanged.Invoke(this, new AssetBundleStateChangedEventArgs(BundleInfo.assetBundleName, AssetBundleDownloadState.Blocked, "FAILED_TO_WRITE_TO_DISK")); return; } } Debug.Log("AssetBundler DoDownload: Resuming incomplete DL. " + existingLength + " bytes downloaded already."); string downloadURL = AssetBundleManager.Instance.BundleServerURL + BundleInfo.assetBundleName; Debug.Log("AssetBundler DoDownload: " + downloadURL); HttpWebRequest request = (HttpWebRequest)HttpWebRequest.Create(downloadURL); request.Timeout = 5000; //5 secs timeout request.ReadWriteTimeout = 5000; request.AddRange((int)existingLength, (int)BundleInfo.downloadSizeBytes); request.KeepAlive = false; Debug.Log("Request URI: " + request.RequestUri.AbsoluteUri); bool didComplete = false; using (HttpWebResponse response = (HttpWebResponse)request.GetResponse()) { if (response.ContentLength == 0) { //no more to download from the server if (existingLength >= BundleInfo.downloadSizeBytes) { didComplete = true; } else { throw new Exception("Error. Data on server not sufficient: " + existingLength + " bytes available, expected " + BundleInfo.downloadSizeBytes); } } long serverFileSize = existingLength + response.ContentLength; //response.ContentLength gives me the size that is remaining to be downloaded bool downloadResumable; // need it for not sending any progress int responseCode = (int)response.StatusCode; downloadResumable = CheckForResumeableReponseCode(BundleInfo, downloadURL, responseCode); if (!downloadResumable) { existingLength = 0; } DownloadProgressChanged.Invoke(this, new DownloadProgressChangedEventArgs(BundleInfo.assetBundleName, existingLength, 0)); using (saveFileStream = tempFileInfo.Open(downloadResumable ? FileMode.Append : FileMode.Create, FileAccess.Write, FileShare.ReadWrite)) { using (Stream stream = response.GetResponseStream()) { stream.ReadTimeout = 5000; byte[] downBuffer = new byte[4096]; long totalReceived = existingLength; long sessionReceived = 0; Stopwatch stopwatch = Stopwatch.StartNew(); while (totalReceived < serverFileSize) { #if !PRODUCTION && !PREPRODUCTION bool paused = false;// Assets.Code.Common.DebugOptions.GetToggleValue(Assets.Code.Common.DebugToggle.downloadsPaused); if (paused) { Thread.Sleep(100); continue; } DebugNoInterentConnection(); DebugThrottleConnectionSpeed(); #endif //if connection has downgraded to a non-allowed network, //or if user has changed the current prioritised download, this pauses the download if (startingReachability != AssetBundleManager.Instance.CurrentReachability || AssetBundleManager.Instance.CheckAndResetCurrentPriorityChanged() || AssetBundleManager.Instance.CheckAndResetDownloadTimeout()) { AssetBundleStateChanged.Invoke(this, new AssetBundleStateChangedEventArgs(BundleInfo.assetBundleName, AssetBundleDownloadState.Queued)); AssetBundleManager.Instance.ResetPollingTime(); //we reset the poll to start the next download immediately stopwatch.Stop(); stream.Close(); response.Close(); saveFileStream.Close(); saveFileStream = null; return; } int byteSize = stream.Read(downBuffer, 0, downBuffer.Length); saveFileStream.Write(downBuffer, 0, byteSize); totalReceived += byteSize; sessionReceived += byteSize; float currentSpeed = sessionReceived / (float)stopwatch.Elapsed.TotalSeconds; DownloadProgressChanged.Invoke(this, new DownloadProgressChangedEventArgs(BundleInfo.assetBundleName, totalReceived, (long)currentSpeed)); } didComplete = true; stopwatch.Stop(); stream.Close(); } } response.Close(); saveFileStream.Close(); saveFileStream = null; } if (didComplete) { Debug.Log("AssetBundler DoDownload. Completed. Throwing to CRC Check."); //download completed AssetBundleStateChanged.Invoke(this, new AssetBundleStateChangedEventArgs(BundleInfo.assetBundleName, AssetBundleDownloadState.CRCCheck)); AssetBundleManager.Instance.ResetPollingTime(); //we reset the poll to start the next download immediately } } catch (WebException we) { //416 error means we requested some bytes which don't exist. //this means the local game is expecting more data than the server has. //in this case, the file must be finished (no more data on the server), so we throw it to a CRC check to confirm if ((int)we.Status == 416) { Debug.LogWarning("AssetBundler DoDownload: Error 416. Assuming File is completed"); //this is considered an error for retry purposes AssetBundleStateChanged.Invoke(this, new AssetBundleStateChangedEventArgs(BundleInfo.assetBundleName, AssetBundleDownloadState.CRCCheck, "ERROR_416_FROM_SERVER")); } if (we.Status == WebExceptionStatus.Timeout) { Debug.LogException(we); Debug.LogWarning("Connection Timed Out, likly due to poor or loss of connection, restarting bundle download"); AssetBundleStateChanged.Invoke(this, new AssetBundleStateChangedEventArgs(BundleInfo.assetBundleName, AssetBundleDownloadState.CRCCheck)); } else { Debug.LogException(we); Debug.LogError("AssetBundler DoDownload: Exception caused assetbundle download failure " + BundleInfo.assetBundleName + " . Performing full file/CRC to work out file status: " + we.ToString()); AssetBundleStateChanged.Invoke(this, new AssetBundleStateChangedEventArgs(BundleInfo.assetBundleName, AssetBundleDownloadState.CRCCheck)); //no error because timeouts aren't a problem with the bundle } } catch (IOException ioe) { Debug.LogException(ioe); Debug.LogError("AssetBundler DoDownload: IO/Write Exception. Deleting offending file: " + tempFileInfo.Name + ": " + ioe.ToString()); AssetBundleDownloadCorruptedOrBroken.Invoke(this, new AssetBundleDeleteAssetBundleFileEventArgs(BundleInfo.assetBundleName, "ASSETBUNDLE_IO_WRITE_EXCEPTION")); //kick it right back off again } catch (ObjectDisposedException objectDisposedException) { Debug.LogWarning(objectDisposedException.Message); Debug.LogWarning("Aborting download " + BundleInfo.assetBundleName + ", likly due to loss of connection, recheck bundle"); AssetBundleStateChanged.Invoke(this, new AssetBundleStateChangedEventArgs(BundleInfo.assetBundleName, AssetBundleDownloadState.CRCCheck)); } catch (ThreadAbortException) { Debug.LogWarning("AssetBundler DoDownload: Thread aborted. Assuming intentionally."); //AssetBundleStateChanged.Invoke(this, new AssetBundleStateChangedEventArgs(assetBundleQueueInfo.AssetBundleName, AssetBundleDownloadState.CRCCheck, "")); } catch (Exception e) { Debug.LogException(e); Debug.LogError("AssetBundler DoDownload: Exception caused assetbundle download failure. Performing full file/CRC to work out file status. Error: " + e.ToString()); AssetBundleStateChanged.Invoke(this, new AssetBundleStateChangedEventArgs(BundleInfo.assetBundleName, AssetBundleDownloadState.CRCCheck, e.ToString())); } finally { if (saveFileStream != null) { saveFileStream.Close(); saveFileStream = null; } } Debug.Log("Download Thread Ended: " + BundleInfo.assetBundleName); }
public void ForceRecheckOfBundle(Bundle assetBundleQueueInfo) { AssetBundleStateChanged.Invoke(this, new AssetBundleStateChangedEventArgs(assetBundleQueueInfo.assetBundleName, AssetBundleDownloadState.CRCCheck)); }