summaryrefslogtreecommitdiffstats
path: root/dom/system/gonk/mozstumbler/WriteStumbleOnThread.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/system/gonk/mozstumbler/WriteStumbleOnThread.cpp')
-rw-r--r--dom/system/gonk/mozstumbler/WriteStumbleOnThread.cpp321
1 files changed, 321 insertions, 0 deletions
diff --git a/dom/system/gonk/mozstumbler/WriteStumbleOnThread.cpp b/dom/system/gonk/mozstumbler/WriteStumbleOnThread.cpp
new file mode 100644
index 000000000..e58e771c4
--- /dev/null
+++ b/dom/system/gonk/mozstumbler/WriteStumbleOnThread.cpp
@@ -0,0 +1,321 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WriteStumbleOnThread.h"
+#include "StumblerLogging.h"
+#include "UploadStumbleRunnable.h"
+#include "nsDumpUtils.h"
+#include "nsGZFileWriter.h"
+#include "nsIFileStreams.h"
+#include "nsIInputStream.h"
+#include "nsPrintfCString.h"
+
+#define MAXFILESIZE_KB (15 * 1024)
+#define ONEDAY_IN_MSEC (24 * 60 * 60 * 1000)
+#define MAX_UPLOAD_ATTEMPTS 20
+
+mozilla::Atomic<bool> WriteStumbleOnThread::sIsFileWaitingForUpload(false);
+mozilla::Atomic<bool> WriteStumbleOnThread::sIsAlreadyRunning(false);
+WriteStumbleOnThread::UploadFreqGuard WriteStumbleOnThread::sUploadFreqGuard = {0};
+
+#define FILENAME_INPROGRESS NS_LITERAL_CSTRING("stumbles.json.gz")
+#define FILENAME_COMPLETED NS_LITERAL_CSTRING("stumbles.done.json.gz")
+#define OUTPUT_DIR NS_LITERAL_CSTRING("mozstumbler")
+
+class DeleteRunnable : public Runnable
+{
+ public:
+ DeleteRunnable() {}
+
+ NS_IMETHOD
+ Run() override
+ {
+ nsCOMPtr<nsIFile> tmpFile;
+ nsresult rv = nsDumpUtils::OpenTempFile(FILENAME_COMPLETED,
+ getter_AddRefs(tmpFile),
+ OUTPUT_DIR,
+ nsDumpUtils::CREATE);
+ if (NS_SUCCEEDED(rv)) {
+ tmpFile->Remove(true);
+ }
+ // critically, this sets this flag to false so writing can happen again
+ WriteStumbleOnThread::sIsAlreadyRunning = false;
+ WriteStumbleOnThread::sIsFileWaitingForUpload = false;
+ return NS_OK;
+ }
+
+ private:
+ ~DeleteRunnable() {}
+};
+
+bool
+WriteStumbleOnThread::IsFileWaitingForUpload()
+{
+ return sIsFileWaitingForUpload;
+}
+
+void
+WriteStumbleOnThread::UploadEnded(bool deleteUploadFile)
+{
+ if (!deleteUploadFile) {
+ sIsAlreadyRunning = false;
+ return;
+ }
+
+ nsCOMPtr<nsIEventTarget> target = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+ MOZ_ASSERT(target);
+ nsCOMPtr<nsIRunnable> event = new DeleteRunnable();
+ target->Dispatch(event, NS_DISPATCH_NORMAL);
+}
+
+void
+WriteStumbleOnThread::WriteJSON(Partition aPart)
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ nsCOMPtr<nsIFile> tmpFile;
+ nsresult rv;
+ rv = nsDumpUtils::OpenTempFile(FILENAME_INPROGRESS, getter_AddRefs(tmpFile),
+ OUTPUT_DIR, nsDumpUtils::CREATE);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ STUMBLER_ERR("Open a file for stumble failed");
+ return;
+ }
+
+ RefPtr<nsGZFileWriter> gzWriter = new nsGZFileWriter(nsGZFileWriter::Append);
+ rv = gzWriter->Init(tmpFile);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ STUMBLER_ERR("gzWriter init failed");
+ return;
+ }
+
+ /*
+ The json format is like below.
+ {items:[
+ {item},
+ {item},
+ {item}
+ ]}
+ */
+
+ // Need to add "]}" after the last item
+ if (aPart == Partition::End) {
+ rv = gzWriter->Write("]}");
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ STUMBLER_ERR("gzWriter Write failed");
+ }
+
+ rv = gzWriter->Finish();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ STUMBLER_ERR("ostream finish failed");
+ }
+
+ nsCOMPtr<nsIFile> targetFile;
+ nsresult rv = nsDumpUtils::OpenTempFile(FILENAME_COMPLETED, getter_AddRefs(targetFile),
+ OUTPUT_DIR, nsDumpUtils::CREATE);
+ nsAutoString targetFilename;
+ rv = targetFile->GetLeafName(targetFilename);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ STUMBLER_ERR("Get Filename failed");
+ return;
+ }
+ rv = targetFile->Remove(true);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ STUMBLER_ERR("Remove File failed");
+ return;
+ }
+ // Rename tmpfile
+ rv = tmpFile->MoveTo(/* directory */ nullptr, targetFilename);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ STUMBLER_ERR("Rename File failed");
+ return;
+ }
+ return;
+ }
+
+ // Need to add "{items:[" before the first item
+ if (aPart == Partition::Begining) {
+ rv = gzWriter->Write("{\"items\":[{");
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ STUMBLER_ERR("ostream write begining failed");
+ }
+ } else if (aPart == Partition::Middle) {
+ rv = gzWriter->Write(",{");
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ STUMBLER_ERR("ostream write middle failed");
+ }
+ }
+ rv = gzWriter->Write(mDesc.get());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ STUMBLER_ERR("ostream write mDesc failed");
+ }
+ // one item is ended with '}' (e.g. {item})
+ rv = gzWriter->Write("}");
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ STUMBLER_ERR("ostream write end failed");
+ }
+
+ rv = gzWriter->Finish();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ STUMBLER_ERR("ostream finish failed");
+ }
+
+ // check if it is the end of this file
+ int64_t fileSize = 0;
+ rv = tmpFile->GetFileSize(&fileSize);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ STUMBLER_ERR("GetFileSize failed");
+ return;
+ }
+ if (fileSize >= MAXFILESIZE_KB) {
+ WriteJSON(Partition::End);
+ return;
+ }
+}
+
+WriteStumbleOnThread::Partition
+WriteStumbleOnThread::GetWritePosition()
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ nsCOMPtr<nsIFile> tmpFile;
+ nsresult rv = nsDumpUtils::OpenTempFile(FILENAME_INPROGRESS, getter_AddRefs(tmpFile),
+ OUTPUT_DIR, nsDumpUtils::CREATE);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ STUMBLER_ERR("Open a file for stumble failed");
+ return Partition::Unknown;
+ }
+
+ int64_t fileSize = 0;
+ rv = tmpFile->GetFileSize(&fileSize);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ STUMBLER_ERR("GetFileSize failed");
+ return Partition::Unknown;
+ }
+
+ if (fileSize == 0) {
+ return Partition::Begining;
+ } else if (fileSize >= MAXFILESIZE_KB) {
+ return Partition::End;
+ } else {
+ return Partition::Middle;
+ }
+}
+
+NS_IMETHODIMP
+WriteStumbleOnThread::Run()
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ bool b = sIsAlreadyRunning.exchange(true);
+ if (b) {
+ return NS_OK;
+ }
+
+ UploadFileStatus status = GetUploadFileStatus();
+
+ if (UploadFileStatus::NoFile != status) {
+ if (UploadFileStatus::ExistsAndReadyToUpload == status) {
+ sIsFileWaitingForUpload = true;
+ Upload();
+ return NS_OK;
+ }
+ } else {
+ Partition partition = GetWritePosition();
+ if (partition == Partition::Unknown) {
+ STUMBLER_ERR("GetWritePosition failed, skip once");
+ } else {
+ WriteJSON(partition);
+ }
+ }
+
+ sIsFileWaitingForUpload = false;
+ sIsAlreadyRunning = false;
+ return NS_OK;
+}
+
+
+/*
+ If the upload file exists, then check if it is one day old.
+ • if it is a day old -> ExistsAndReadyToUpload
+ • if it is less than the current day old -> Exists
+ • otherwise -> NoFile
+
+ The Exists case means that the upload and the stumbling is rate limited
+ per-day to the size of the one file.
+ */
+WriteStumbleOnThread::UploadFileStatus
+WriteStumbleOnThread::GetUploadFileStatus()
+{
+ nsCOMPtr<nsIFile> tmpFile;
+ nsresult rv = nsDumpUtils::OpenTempFile(FILENAME_COMPLETED, getter_AddRefs(tmpFile),
+ OUTPUT_DIR, nsDumpUtils::CREATE);
+ int64_t fileSize;
+ rv = tmpFile->GetFileSize(&fileSize);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ STUMBLER_ERR("GetFileSize failed");
+ return UploadFileStatus::NoFile;
+ }
+ if (fileSize <= 0) {
+ tmpFile->Remove(true);
+ return UploadFileStatus::NoFile;
+ }
+
+ PRTime lastModifiedTime;
+ tmpFile->GetLastModifiedTime(&lastModifiedTime);
+ if ((PR_Now() / PR_USEC_PER_MSEC) - lastModifiedTime >= ONEDAY_IN_MSEC) {
+ return UploadFileStatus::ExistsAndReadyToUpload;
+ }
+ return UploadFileStatus::Exists;
+}
+
+void
+WriteStumbleOnThread::Upload()
+{
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ time_t seconds = time(0);
+ int day = seconds / (60 * 60 * 24);
+
+ if (sUploadFreqGuard.daySinceEpoch < day) {
+ sUploadFreqGuard.daySinceEpoch = day;
+ sUploadFreqGuard.attempts = 0;
+ }
+
+ sUploadFreqGuard.attempts++;
+ if (sUploadFreqGuard.attempts > MAX_UPLOAD_ATTEMPTS) {
+ STUMBLER_ERR("Too many upload attempts today");
+ sIsAlreadyRunning = false;
+ return;
+ }
+
+ nsCOMPtr<nsIFile> tmpFile;
+ nsresult rv = nsDumpUtils::OpenTempFile(FILENAME_COMPLETED, getter_AddRefs(tmpFile),
+ OUTPUT_DIR, nsDumpUtils::CREATE);
+ int64_t fileSize;
+ rv = tmpFile->GetFileSize(&fileSize);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ STUMBLER_ERR("GetFileSize failed");
+ sIsAlreadyRunning = false;
+ return;
+ }
+
+ if (fileSize <= 0) {
+ sIsAlreadyRunning = false;
+ return;
+ }
+
+ // prepare json into nsIInputStream
+ nsCOMPtr<nsIInputStream> inStream;
+ rv = NS_NewLocalFileInputStream(getter_AddRefs(inStream), tmpFile);
+ if (NS_FAILED(rv)) {
+ sIsAlreadyRunning = false;
+ return;
+ }
+
+ RefPtr<nsIRunnable> uploader = new UploadStumbleRunnable(inStream);
+ NS_DispatchToMainThread(uploader);
+}