diff --git a/indra/newview/llfloatersimplesnapshot.cpp b/indra/newview/llfloatersimplesnapshot.cpp index 55b39d9193..4671f74d6d 100644 --- a/indra/newview/llfloatersimplesnapshot.cpp +++ b/indra/newview/llfloatersimplesnapshot.cpp @@ -54,87 +54,162 @@ void post_thumbnail_image_coro(std::string cap_url, std::string path_to_image, L { LLCore::HttpRequest::policy_t httpPolicy(LLCore::HttpRequest::DEFAULT_POLICY_ID); LLCoreHttpUtil::HttpCoroutineAdapter::ptr_t - httpAdapter = std::make_shared("post_profile_image_coro", httpPolicy); + httpAdapter = std::make_shared("post_thumbnail_image_coro", httpPolicy); LLCore::HttpRequest::ptr_t httpRequest = std::make_shared(); LLCore::HttpHeaders::ptr_t httpHeaders; LLCore::HttpOptions::ptr_t httpOpts = std::make_shared(); httpOpts->setFollowRedirects(true); - LLSD result = httpAdapter->postAndSuspend(httpRequest, cap_url, first_data, httpOpts, httpHeaders); + // Retry stage-2 upload by re-requesting a fresh one-time uploader capability (up to 3 attempts total) + const S32 MAX_UPLOAD_RETRIES = 2; + S32 upload_retry_count = 0; + LLUUID result_uuid; - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (!status) - { - // todo: notification? - LL_WARNS("AvatarProperties") << "Failed to get uploader cap " << status.toString() << LL_ENDL; - return; - } - if (!result.has("uploader")) + while (upload_retry_count <= MAX_UPLOAD_RETRIES) { - // todo: notification? - LL_WARNS("AvatarProperties") << "Failed to get uploader cap, response contains no data." << LL_ENDL; - return; - } - std::string uploader_cap = result["uploader"].asString(); - if (uploader_cap.empty()) - { - LL_WARNS("AvatarProperties") << "Failed to get uploader cap, cap invalid." << LL_ENDL; - return; - } + // Stage 1: Request uploader URL + LLSD result = httpAdapter->postAndSuspend(httpRequest, cap_url, first_data, httpOpts, httpHeaders); - // Upload the image + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - LLCore::HttpRequest::ptr_t uploaderhttpRequest = std::make_shared(); - LLCore::HttpHeaders::ptr_t uploaderhttpHeaders = std::make_shared(); - LLCore::HttpOptions::ptr_t uploaderhttpOpts = std::make_shared(); - S64 length; + if (!status) + { + LL_WARNS("Thumbnail") << "Failed to get uploader cap " << status.toString() << LL_ENDL; + if (callback) + { + callback(LLUUID()); + } + LLFile::remove(path_to_image); + return; + } - { - llifstream instream(path_to_image.c_str(), std::iostream::binary | std::iostream::ate); - if (!instream.is_open()) + if (!result.has("uploader")) { - LL_WARNS("AvatarProperties") << "Failed to open file " << path_to_image << LL_ENDL; + LL_WARNS("Thumbnail") << "Failed to get uploader cap, response contains no data." << LL_ENDL; + if (callback) + { + callback(LLUUID()); + } + LLFile::remove(path_to_image); return; } - length = instream.tellg(); - } - uploaderhttpHeaders->append(HTTP_OUT_HEADER_CONTENT_TYPE, "application/jp2"); // optional - uploaderhttpHeaders->append(HTTP_OUT_HEADER_CONTENT_LENGTH, llformat("%d", length)); // required! - uploaderhttpOpts->setFollowRedirects(true); + std::string uploader_cap = result["uploader"].asString(); + if (uploader_cap.empty()) + { + LL_WARNS("Thumbnail") << "Failed to get uploader cap, cap invalid." << LL_ENDL; + if (callback) + { + callback(LLUUID()); + } + LLFile::remove(path_to_image); + return; + } - result = httpAdapter->postFileAndSuspend(uploaderhttpRequest, uploader_cap, path_to_image, uploaderhttpOpts, uploaderhttpHeaders); + // Stage 2: Upload the image + LLCore::HttpRequest::ptr_t uploaderhttpRequest = std::make_shared(); + LLCore::HttpHeaders::ptr_t uploaderhttpHeaders = std::make_shared(); + LLCore::HttpOptions::ptr_t uploaderhttpOpts = std::make_shared(); + S64 length; - httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + { + llifstream instream(path_to_image.c_str(), std::iostream::binary | std::iostream::ate); + if (!instream.is_open()) + { + LL_WARNS("Thumbnail") << "Failed to open file " << path_to_image << LL_ENDL; + if (callback) + { + callback(LLUUID()); + } + LLFile::remove(path_to_image); + return; + } + length = instream.tellg(); + } - LL_DEBUGS("Thumbnail") << result << LL_ENDL; + uploaderhttpHeaders->append(HTTP_OUT_HEADER_CONTENT_TYPE, "application/jp2"); + uploaderhttpHeaders->append(HTTP_OUT_HEADER_CONTENT_LENGTH, std::to_string(length)); + uploaderhttpOpts->setFollowRedirects(true); - if (!status) - { - LL_WARNS("Thumbnail") << "Failed to upload image " << status.toString() << LL_ENDL; - return; - } + result = httpAdapter->postFileAndSuspend(uploaderhttpRequest, uploader_cap, path_to_image, uploaderhttpOpts, uploaderhttpHeaders); - if (result["state"].asString() != "complete") - { - if (result.has("message")) - { - LL_WARNS("Thumbnail") << "Failed to upload image, state " << result["state"] << " message: " << result["message"] << LL_ENDL; - } - else + httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + + LL_DEBUGS("Thumbnail") << result << LL_ENDL; + + if (!status) { - LL_WARNS("Thumbnail") << "Failed to upload image " << result << LL_ENDL; + if (upload_retry_count < MAX_UPLOAD_RETRIES) + { + upload_retry_count++; + LL_WARNS("Thumbnail") << "Failed to upload image (attempt " << upload_retry_count + << " of " << (MAX_UPLOAD_RETRIES + 1) << "): " << status.toString() + << ", re-requesting uploader..." << LL_ENDL; + llcoro::suspendUntilTimeout(1.0f); + continue; + } + else + { + LL_WARNS("Thumbnail") << "Failed to upload image after " << (MAX_UPLOAD_RETRIES + 1) + << " attempts: " << status.toString() << LL_ENDL; + if (callback) + { + callback(LLUUID()); + } + LLFile::remove(path_to_image); + return; + } } - if (callback) + if (result["state"].asString() != "complete") { - callback(LLUUID()); + if (upload_retry_count < MAX_UPLOAD_RETRIES) + { + upload_retry_count++; + if (result.has("message")) + { + LL_WARNS("Thumbnail") << "Failed to upload image, state " << result["state"] + << " message: " << result["message"] << " (attempt " + << upload_retry_count << " of " << (MAX_UPLOAD_RETRIES + 1) + << "), re-requesting uploader..." << LL_ENDL; + } + else + { + LL_WARNS("Thumbnail") << "Failed to upload image (attempt " << upload_retry_count + << " of " << (MAX_UPLOAD_RETRIES + 1) + << "), re-requesting uploader..." << LL_ENDL; + } + llcoro::suspendUntilTimeout(1.0f); + continue; + } + else + { + if (result.has("message")) + { + LL_WARNS("Thumbnail") << "Failed to upload image after " << (MAX_UPLOAD_RETRIES + 1) + << " attempts, state " << result["state"] + << " message: " << result["message"] << LL_ENDL; + } + else + { + LL_WARNS("Thumbnail") << "Failed to upload image after " << (MAX_UPLOAD_RETRIES + 1) + << " attempts" << LL_ENDL; + } + if (callback) + { + callback(LLUUID()); + } + LLFile::remove(path_to_image); + return; + } } - return; + + // Success! + result_uuid = result["new_asset"].asUUID(); + break; } if (first_data.has("category_id")) @@ -143,7 +218,7 @@ void post_thumbnail_image_coro(std::string cap_url, std::string path_to_image, L LLViewerInventoryCategory* cat = gInventory.getCategory(cat_id); if (cat) { - cat->setThumbnailUUID(result["new_asset"].asUUID()); + cat->setThumbnailUUID(result_uuid); } gInventory.addChangedMask(LLInventoryObserver::INTERNAL, cat_id); } @@ -153,16 +228,18 @@ void post_thumbnail_image_coro(std::string cap_url, std::string path_to_image, L LLViewerInventoryItem* item = gInventory.getItem(item_id); if (item) { - item->setThumbnailUUID(result["new_asset"].asUUID()); + item->setThumbnailUUID(result_uuid); } - // Are we supposed to get BulkUpdateInventory? gInventory.addChangedMask(LLInventoryObserver::INTERNAL, item_id); } if (callback) { - callback(result["new_asset"].asUUID()); + callback(result_uuid); } + + // Cleanup + LLFile::remove(path_to_image); } ///---------------------------------------------------------------------------- diff --git a/indra/newview/llpanelprofile.cpp b/indra/newview/llpanelprofile.cpp index 34d2d4d6a5..b33696244d 100644 --- a/indra/newview/llpanelprofile.cpp +++ b/indra/newview/llpanelprofile.cpp @@ -113,77 +113,131 @@ LLUUID post_profile_image(std::string cap_url, const LLSD &first_data, std::stri LLCore::HttpOptions::ptr_t httpOpts = std::make_shared(); httpOpts->setFollowRedirects(true); - LLSD result = httpAdapter->postAndSuspend(httpRequest, cap_url, first_data, httpOpts, httpHeaders); + // Retry stage-2 upload by re-requesting a fresh one-time uploader capability (up to 3 attempts total) + const S32 MAX_UPLOAD_RETRIES = 2; + S32 upload_retry_count = 0; + LLUUID result_uuid; - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if (!status) - { - // todo: notification? - LL_WARNS("AvatarProperties") << "Failed to get uploader cap " << status.toString() << LL_ENDL; - return LLUUID::null; - } - if (!result.has("uploader")) + while (upload_retry_count <= MAX_UPLOAD_RETRIES) { - // todo: notification? - LL_WARNS("AvatarProperties") << "Failed to get uploader cap, response contains no data." << LL_ENDL; - return LLUUID::null; - } - std::string uploader_cap = result["uploader"].asString(); - if (uploader_cap.empty()) - { - LL_WARNS("AvatarProperties") << "Failed to get uploader cap, cap invalid." << LL_ENDL; - return LLUUID::null; - } + // Stage 1: Request uploader URL + LLSD result = httpAdapter->postAndSuspend(httpRequest, cap_url, first_data, httpOpts, httpHeaders); - // Upload the image - LLCore::HttpRequest::ptr_t uploaderhttpRequest = std::make_shared(); - LLCore::HttpHeaders::ptr_t uploaderhttpHeaders = std::make_shared(); - LLCore::HttpOptions::ptr_t uploaderhttpOpts = std::make_shared(); - S64 length; + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - { - llifstream instream(path_to_image.c_str(), std::iostream::binary | std::iostream::ate); - if (!instream.is_open()) + if (!status) { - LL_WARNS("AvatarProperties") << "Failed to open file " << path_to_image << LL_ENDL; + // todo: notification? + LL_WARNS("AvatarProperties") << "Failed to get uploader cap " << status.toString() << LL_ENDL; + return LLUUID::null; + } + + if (!result.has("uploader")) + { + // todo: notification? + LL_WARNS("AvatarProperties") << "Failed to get uploader cap, response contains no data." << LL_ENDL; return LLUUID::null; } - length = instream.tellg(); - } - uploaderhttpHeaders->append(HTTP_OUT_HEADER_CONTENT_TYPE, "application/jp2"); // optional - uploaderhttpHeaders->append(HTTP_OUT_HEADER_CONTENT_LENGTH, llformat("%d", length)); // required! - uploaderhttpOpts->setFollowRedirects(true); + std::string uploader_cap = result["uploader"].asString(); + if (uploader_cap.empty()) + { + LL_WARNS("AvatarProperties") << "Failed to get uploader cap, cap invalid." << LL_ENDL; + return LLUUID::null; + } + + // Stage 2: Upload the image + LLCore::HttpRequest::ptr_t uploaderhttpRequest = std::make_shared(); + LLCore::HttpHeaders::ptr_t uploaderhttpHeaders = std::make_shared(); + LLCore::HttpOptions::ptr_t uploaderhttpOpts = std::make_shared(); + S64 length; + + { + llifstream instream(path_to_image.c_str(), std::iostream::binary | std::iostream::ate); + if (!instream.is_open()) + { + LL_WARNS("AvatarProperties") << "Failed to open file " << path_to_image << LL_ENDL; + return LLUUID::null; + } + length = instream.tellg(); + } - result = httpAdapter->postFileAndSuspend(uploaderhttpRequest, uploader_cap, path_to_image, uploaderhttpOpts, uploaderhttpHeaders); + uploaderhttpHeaders->append(HTTP_OUT_HEADER_CONTENT_TYPE, "application/jp2"); + uploaderhttpHeaders->append(HTTP_OUT_HEADER_CONTENT_LENGTH, std::to_string(length)); + uploaderhttpOpts->setFollowRedirects(true); - httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); + result = httpAdapter->postFileAndSuspend(uploaderhttpRequest, uploader_cap, path_to_image, uploaderhttpOpts, uploaderhttpHeaders); - LL_DEBUGS("AvatarProperties") << result << LL_ENDL; + httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - if (!status) - { - LL_WARNS("AvatarProperties") << "Failed to upload image " << status.toString() << LL_ENDL; - return LLUUID::null; - } + LL_DEBUGS("AvatarProperties") << result << LL_ENDL; - if (result["state"].asString() != "complete") - { - if (result.has("message")) + if (!status) { - LL_WARNS("AvatarProperties") << "Failed to upload image, state " << result["state"] << " message: " << result["message"] << LL_ENDL; + if (upload_retry_count < MAX_UPLOAD_RETRIES) + { + upload_retry_count++; + LL_WARNS("AvatarProperties") << "Failed to upload image (attempt " << upload_retry_count + << " of " << (MAX_UPLOAD_RETRIES + 1) << "): " << status.toString() + << ", re-requesting uploader..." << LL_ENDL; + llcoro::suspendUntilTimeout(1.0f); + continue; + } + else + { + LL_WARNS("AvatarProperties") << "Failed to upload image after " << (MAX_UPLOAD_RETRIES + 1) + << " attempts: " << status.toString() << LL_ENDL; + return LLUUID::null; + } } - else + + // Todo: should we really repeat if 'complete' not set? + if (result["state"].asString() != "complete") { - LL_WARNS("AvatarProperties") << "Failed to upload image " << result << LL_ENDL; + if (upload_retry_count < MAX_UPLOAD_RETRIES) + { + upload_retry_count++; + if (result.has("message")) + { + LL_WARNS("AvatarProperties") << "Failed to upload image, state " << result["state"] + << " message: " << result["message"] << " (attempt " + << upload_retry_count << " of " << (MAX_UPLOAD_RETRIES + 1) + << "), re-requesting uploader..." << LL_ENDL; + } + else + { + LL_WARNS("AvatarProperties") << "Failed to upload image (attempt " << upload_retry_count + << " of " << (MAX_UPLOAD_RETRIES + 1) + << "), re-requesting uploader..." << LL_ENDL; + } + llcoro::suspendUntilTimeout(1.0f); + continue; + } + else + { + if (result.has("message")) + { + LL_WARNS("AvatarProperties") << "Failed to upload image after " << (MAX_UPLOAD_RETRIES + 1) + << " attempts, state " << result["state"] + << " message: " << result["message"] << LL_ENDL; + } + else + { + LL_WARNS("AvatarProperties") << "Failed to upload image after " << (MAX_UPLOAD_RETRIES + 1) + << " attempts" << LL_ENDL; + } + return LLUUID::null; + } } - return LLUUID::null; + + // Success! + result_uuid = result["new_asset"].asUUID(); + break; } - return result["new_asset"].asUUID(); + return result_uuid; } enum EProfileImageType diff --git a/indra/newview/llviewerassetupload.cpp b/indra/newview/llviewerassetupload.cpp index 65a69acc88..46c76e2953 100644 --- a/indra/newview/llviewerassetupload.cpp +++ b/indra/newview/llviewerassetupload.cpp @@ -881,6 +881,7 @@ void LLViewerAssetUpload::AssetInventoryUploadCoproc(LLCoreHttpUtil::HttpCorouti if (uploadInfo->showUploadDialog()) { + // todo: localize this string std::string uploadMessage = "Uploading...\n\n"; uploadMessage.append(uploadInfo->getDisplayName()); LLUploadDialog::modalUploadDialog(uploadMessage); @@ -888,66 +889,90 @@ void LLViewerAssetUpload::AssetInventoryUploadCoproc(LLCoreHttpUtil::HttpCorouti LLSD body = uploadInfo->generatePostBody(); - result = httpAdapter->postAndSuspend(httpRequest, url, body, httpOptions); + // Retry stage-2 upload by re-requesting a fresh one-time uploader capability (up to 3 attempts total) + const S32 MAX_UPLOAD_RETRIES = 2; + S32 upload_retry_count = 0; + LLCore::HttpStatus status; - LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; - LLCore::HttpStatus status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - - if ((!status) || (result.has("error"))) + while (upload_retry_count <= MAX_UPLOAD_RETRIES) { - HandleUploadError(status, result, uploadInfo); - if (uploadInfo->showUploadDialog()) - LLUploadDialog::modalUploadFinished(); - return; - } + // Stage 1: Request uploader URL + result = httpAdapter->postAndSuspend(httpRequest, url, body, httpOptions); - std::string uploader = result["uploader"].asString(); - - bool success = false; - if (!uploader.empty() && uploadInfo->getAssetId().notNull()) - { - result = httpAdapter->postFileAndSuspend(httpRequest, uploader, uploadInfo->getAssetId(), uploadInfo->getAssetType(), httpOptions); - httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + LLSD httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - std::string ulstate = result["state"].asString(); - - if ((!status) || (ulstate != "complete")) + if ((!status) || (result.has("error"))) { HandleUploadError(status, result, uploadInfo); if (uploadInfo->showUploadDialog()) LLUploadDialog::modalUploadFinished(); return; } - if (!result.has("success")) + + std::string uploader = result["uploader"].asString(); + if (uploader.empty() || uploadInfo->getAssetId().isNull()) { - result["success"] = LLSD::Boolean((ulstate == "complete") && status); + LL_WARNS() << "No upload url provided. Nothing uploaded, responding with previous result." << LL_ENDL; + break; } - S32 uploadPrice = result["upload_price"].asInteger(); + // Stage 2: Upload to the uploader URL + result = httpAdapter->postFileAndSuspend(httpRequest, uploader, uploadInfo->getAssetId(), uploadInfo->getAssetType(), httpOptions); + httpResults = result[LLCoreHttpUtil::HttpCoroutineAdapter::HTTP_RESULTS]; + status = LLCoreHttpUtil::HttpCoroutineAdapter::getStatusFromLLSD(httpResults); - if (uploadPrice > 0) - { - // this upload costed us L$, update our balance - // and display something saying that it cost L$ - LLStatusBar::sendMoneyBalanceRequest(); + std::string ulstate = result["state"].asString(); - LLSD args; - args["AMOUNT"] = llformat("%d", uploadPrice); - LLNotificationsUtil::add("UploadPayment", args); + if ((!status) || (ulstate != "complete")) + { + if (upload_retry_count < MAX_UPLOAD_RETRIES) + { + upload_retry_count++; + LL_WARNS() << "Upload to uploader failed (attempt " << upload_retry_count + << " of " << (MAX_UPLOAD_RETRIES + 1) << "), re-requesting uploader..." << LL_ENDL; + llcoro::suspendUntilTimeout(1.0f); + continue; + } + else + { + HandleUploadError(status, result, uploadInfo); + if (uploadInfo->showUploadDialog()) + LLUploadDialog::modalUploadFinished(); + return; + } } + + // Success! + break; } - else + + if (!result.has("success")) + { + result["success"] = LLSD::Boolean((result["state"].asString() == "complete") && status); + } + + S32 uploadPrice = result["upload_price"].asInteger(); + + if (uploadPrice > 0) { - LL_WARNS() << "No upload url provided. Nothing uploaded, responding with previous result." << LL_ENDL; + // this upload costed us L$, update our balance + // and display something saying that it cost L$ + LLStatusBar::sendMoneyBalanceRequest(); + + LLSD args; + args["AMOUNT"] = llformat("%d", uploadPrice); + LLNotificationsUtil::add("UploadPayment", args); } + LLUUID serverInventoryItem = uploadInfo->finishUpload(result); + bool succeeded = false; if (uploadInfo->showInventoryPanel()) { if (serverInventoryItem.notNull()) { - success = true; + succeeded = true; LLFocusableElement* focus = gFocusMgr.getKeyboardFocus(); @@ -973,7 +998,7 @@ void LLViewerAssetUpload::AssetInventoryUploadCoproc(LLCoreHttpUtil::HttpCorouti LLFloater* floater_snapshot = LLFloaterReg::findInstance("snapshot"); if (uploadInfo->getAssetType() == LLAssetType::AT_TEXTURE && floater_snapshot && floater_snapshot->isShown()) { - floater_snapshot->notify(LLSD().with("set-finished", LLSD().with("ok", success).with("msg", "inventory"))); + floater_snapshot->notify(LLSD().with("set-finished", LLSD().with("ok", succeeded).with("msg", "inventory"))); } }