Initial commit

Signed-off-by: Artem Senichev <a.senichev@yadro.com>
diff --git a/src/log_storage.cpp b/src/log_storage.cpp
new file mode 100644
index 0000000..1ada1c3
--- /dev/null
+++ b/src/log_storage.cpp
@@ -0,0 +1,173 @@
+/**
+ * @brief Log storage.
+ *
+ * This file is part of HostLogger project.
+ *
+ * Copyright (c) 2018 YADRO
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "config.hpp"
+#include "log_storage.hpp"
+
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+
+
+LogStorage::LogStorage()
+: last_complete_(true)
+{
+}
+
+
+void LogStorage::parse(const char* data, size_t len)
+{
+    // Split log stream to separate messages.
+    // Stream may not be ended with EOL, so we handle this situation by
+    // last_complete_ flag.
+    size_t pos = 0;
+    while (pos < len) {
+        // Search for EOL ('\n')
+        size_t eol = pos;
+        while (eol < len && data[eol] != '\n')
+            ++eol;
+        const bool eolFound = eol < len;
+        const char* msgText = data + pos;
+        size_t msgLen = (eolFound ? eol : len) - pos;
+
+        // Remove '\r' from the end of message
+        while (msgLen && msgText[msgLen - 1] == '\r')
+            --msgLen;
+
+        // Append message to store
+        if (msgLen)
+            append(msgText, msgLen);
+
+        pos = eol + 1; // Skip '\n'
+        last_complete_ = eolFound;
+    }
+}
+
+
+void LogStorage::append(const char* msg, size_t len)
+{
+    if (!last_complete_) {
+        // The last message is incomplete, add msg as part of it
+        if (!messages_.empty()) {
+            Message& last_msg = *messages_.rbegin();
+            last_msg.text.append(msg, len);
+        }
+    }
+    else {
+        Message new_msg;
+        time(&new_msg.timeStamp);
+        new_msg.text.assign(msg, len);
+        messages_.push_back(new_msg);
+        shrink();
+    }
+}
+
+
+void LogStorage::clear()
+{
+    messages_.clear();
+    last_complete_ = true;
+}
+
+
+bool LogStorage::empty() const
+{
+    return messages_.empty();
+}
+
+
+int LogStorage::write(const char* fileName) const
+{
+    int rc = 0;
+
+    if (empty()) {
+        printf("No messages to write\n");
+        return 0;
+    }
+
+    const gzFile fd = gzopen(fileName, "w");
+    if (fd == Z_NULL) {
+        rc = errno;
+        fprintf(stderr, "Unable to open file %s: error [%i] %s\n",
+                fileName, rc, strerror(rc));
+        return rc;
+    }
+
+    // Write full datetime stamp as the first record
+    const time_t& logStartTime = messages_.begin()->timeStamp;
+    tm localTime = { 0 };
+    localtime_r(&logStartTime, &localTime);
+    char msgText[64];
+    snprintf(msgText, sizeof(msgText),
+             ">>> Log collection started at %02i.%02i.%i %02i:%02i:%02i",
+             localTime.tm_mday, localTime.tm_mon + 1, localTime.tm_year + 1900,
+             localTime.tm_hour, localTime.tm_min, localTime.tm_sec);
+    const Message startMsg = { logStartTime, msgText };
+    rc |= write(fd, startMsg);
+
+    // Write messages
+    for (auto it = messages_.begin(); rc == 0 && it != messages_.end(); ++it)
+        rc |= write(fd, *it);
+
+    rc = gzclose_w(fd);
+    if (rc != Z_OK)
+        fprintf(stderr, "Unable to close file %s: error [%i]\n", fileName, rc);
+
+    return rc;
+}
+
+
+int LogStorage::write(gzFile fd, const Message& msg) const
+{
+    // Convert timestamp to local time
+    tm localTime = { 0 };
+    localtime_r(&msg.timeStamp, &localTime);
+
+    // Write message to the file
+    const int rc = gzprintf(fd, "[ %02i:%02i:%02i ]: %s\n",
+                            localTime.tm_hour,
+                            localTime.tm_min,
+                            localTime.tm_sec,
+                            msg.text.c_str());
+    if (rc <= 0) {
+        fprintf(stderr, "Unable to write file: error [%i]\n", -rc);
+        return EIO;
+    }
+
+    return 0;
+}
+
+
+void LogStorage::shrink()
+{
+    if (loggerConfig.storageSizeLimit) {
+        while (messages_.size() > static_cast<size_t>(loggerConfig.storageSizeLimit))
+            messages_.pop_front();
+    }
+    if (loggerConfig.storageTimeLimit) {
+        // Get time for N hours ago
+        time_t oldestTimeStamp;
+        time(&oldestTimeStamp);
+        oldestTimeStamp -= loggerConfig.storageTimeLimit * 60 * 60;
+        while (!messages_.empty() && messages_.begin()->timeStamp < oldestTimeStamp)
+            messages_.pop_front();
+    }
+}