tdf#107038 Poco::Timestamp replacement with std::chrono

Added functions to get file timestamp and to convert
chrono timestamp in ISO8601 fraction format and some
test cases.

Change-Id: I58961a31f7262b367cff9f33cffdec7571a2f8f7
diff --git a/common/Log.hpp b/common/Log.hpp
index 5b7e77f..fc04080 100644
--- a/common/Log.hpp
+++ b/common/Log.hpp
@@ -38,6 +38,12 @@
    return os;
}

inline std::ostream& operator<< (std::ostream& os, const std::chrono::system_clock::time_point& ts)
{
    os << Util::getIso8601FracformatTime(ts);
    return os;
}

namespace Log
{
    /// Initialize the logging system.
@@ -201,6 +207,16 @@
        return lhs;
    }

    inline StreamLogger& operator<<(StreamLogger& lhs, const std::chrono::system_clock::time_point& rhs)
    {
        if (lhs.enabled())
        {
            lhs.getStream() << Util::getIso8601FracformatTime(rhs);
        }

        return lhs;
    }

    inline void operator<<(StreamLogger& lhs, const _end_marker&)
    {
        (void)end;
diff --git a/common/Util.cpp b/common/Util.cpp
index 9cba4a2..00454f0 100644
--- a/common/Util.cpp
+++ b/common/Util.cpp
@@ -762,11 +762,11 @@

    std::string getHttpTimeNow()
    {
        char time_now[50];
        char time_now[64];
        std::chrono::system_clock::time_point now = std::chrono::system_clock::now();
        std::time_t now_c = std::chrono::system_clock::to_time_t(now);
        std::tm now_tm = *std::gmtime(&now_c);
        strftime(time_now, 50, "%a, %d %b %Y %T", &now_tm);
        strftime(time_now, sizeof(time_now), "%a, %d %b %Y %T", &now_tm);

        return time_now;
    }
@@ -784,6 +784,62 @@
        }
        return std::string::npos;
    }

    std::chrono::system_clock::time_point getFileTimestamp(std::string str_path)
    {
        struct stat file;
        stat(str_path.c_str(), &file);
        std::chrono::seconds ns{file.st_mtime};
        std::chrono::system_clock::time_point mod_time_point{ns};

        return mod_time_point;
    }

    std::string getIso8601FracformatTime(std::chrono::system_clock::time_point time){
        char time_modified[64];
        std::time_t lastModified_us_t = std::chrono::high_resolution_clock::to_time_t(time);
        std::tm lastModified_tm = *std::gmtime(&lastModified_us_t);
        strftime(time_modified, sizeof(time_modified), "%FT%T.", &lastModified_tm);

        auto lastModified_s = std::chrono::time_point_cast<std::chrono::seconds>(time);

        std::ostringstream oss;
        oss << std::setfill('0')
            << time_modified
            << std::setw(6)
            << (time - lastModified_s).count() / 1000
            << "Z";

        return oss.str();
    }

    std::chrono::system_clock::time_point iso8601ToTimestamp(const std::string& iso8601Time, const std::string& logName)
    {
        std::chrono::system_clock::time_point timestamp;
        std::tm tm{};
        std::istringstream iss(iso8601Time);
        if (!(iss >> std::get_time(&tm, "%Y-%m-%dT%H:%M:%S")))
        {
            LOG_WRN(logName << " [" << iso8601Time << "] is in invalid format."
                << "Returning " << timestamp.time_since_epoch().count());
            return timestamp;
        }
        timestamp += std::chrono::seconds(timegm(&tm));
        if (iss.eof())
            return timestamp;
        double us;
        if (iss.peek() != '.' || !(iss >> us))
        {
            LOG_WRN(logName << " [" << iso8601Time << "] is in invalid format."
                    << ". Returning " << timestamp.time_since_epoch().count());
            return timestamp;
        }
        std::size_t seconds_us = us * std::chrono::system_clock::period::den / std::chrono::system_clock::period::num;

        timestamp += std::chrono::system_clock::duration(seconds_us);

        return timestamp;
    }
}

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/common/Util.hpp b/common/Util.hpp
index e16479f..52b960c 100644
--- a/common/Util.hpp
+++ b/common/Util.hpp
@@ -924,6 +924,16 @@

    //// Return current time in HTTP format.
    std::string getHttpTimeNow();

    //// Return timestamp of file
    std::chrono::system_clock::time_point getFileTimestamp(std::string str_path);

    //// Return time in ISO8061 fraction format
    std::string getIso8601FracformatTime(std::chrono::system_clock::time_point time);

    //// Convert time from ISO8061 fraction format
    std::chrono::system_clock::time_point iso8601ToTimestamp(const std::string& iso8601Time, const std::string& logName);

} // end namespace Util

#endif
diff --git a/test/TileCacheTests.cpp b/test/TileCacheTests.cpp
index ef8c1e4..48f6641 100644
--- a/test/TileCacheTests.cpp
+++ b/test/TileCacheTests.cpp
@@ -194,7 +194,7 @@

    // Create TileCache and pretend the file was modified as recently as
    // now, so it discards the cached data.
    TileCache tc("doc.ods", Poco::Timestamp());
    TileCache tc("doc.ods", std::chrono::system_clock::time_point());

    int part = 0;
    int width = 256;
diff --git a/test/WhiteBoxTests.cpp b/test/WhiteBoxTests.cpp
index 3f5f028..35c6faf 100644
--- a/test/WhiteBoxTests.cpp
+++ b/test/WhiteBoxTests.cpp
@@ -55,6 +55,7 @@
    void testAuthorization();
    void testJson();
    void testAnonymization();
    void testTime();
};

void WhiteBoxTests::testLOOLProtocolFunctions()
@@ -731,6 +732,34 @@
    CPPUNIT_ASSERT_EQUAL(urlAnonymized3, Util::anonymizeUrl(fileUrl, nAnonymizationSalt));
}

void WhiteBoxTests::testTime()
{
    std::ostringstream oss;

	std::chrono::system_clock::time_point t(std::chrono::nanoseconds(1567444337874777375));
    CPPUNIT_ASSERT_EQUAL(std::string("2019-09-02T17:12:17.874777Z"), Util::getIso8601FracformatTime(t));

    t = std::chrono::system_clock::time_point(std::chrono::nanoseconds(0));
    CPPUNIT_ASSERT_EQUAL(std::string("1970-01-01T00:00:00.000000Z"), Util::getIso8601FracformatTime(t));

    t = Util::iso8601ToTimestamp("2019-09-02T17:12:17.874777Z", "LastModifiedTime");
    oss << t.time_since_epoch().count();
    CPPUNIT_ASSERT_EQUAL(std::string("1567444337874777000"), oss.str());

    t = Util::iso8601ToTimestamp("1970-01-01T00:00:00.000000Z", "LastModifiedTime");
    oss << t.time_since_epoch().count();
    CPPUNIT_ASSERT_EQUAL(std::string("0"), oss.str());

    t = std::chrono::system_clock::now();
    uint64_t t_in_micros = (t.time_since_epoch().count() / 1000) * 1000;
    oss << t_in_micros;
    std::string first = oss.str();
    std::string s = Util::getIso8601FracformatTime(t);
    t = Util::iso8601ToTimestamp(s, "LastModifiedTime");
    oss << t.time_since_epoch().count();
    CPPUNIT_ASSERT_EQUAL(first, oss.str());
}

CPPUNIT_TEST_SUITE_REGISTRATION(WhiteBoxTests);

/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
diff --git a/wsd/DocumentBroker.cpp b/wsd/DocumentBroker.cpp
index 026bdda..046f916 100644
--- a/wsd/DocumentBroker.cpp
+++ b/wsd/DocumentBroker.cpp
@@ -69,7 +69,7 @@

void sendLastModificationTime(const std::shared_ptr<Session>& session,
                              DocumentBroker* documentBroker,
                              const Poco::Timestamp& documentLastModifiedTime)
                              const std::chrono::system_clock::time_point& documentLastModifiedTime)
{
    if (!session)
        return;
@@ -679,7 +679,7 @@
    {
        // Check if document has been modified by some external action
        LOG_TRC("Document modified time: " << fileInfo.getModifiedTime());
        static const Poco::Timestamp Zero(Poco::Timestamp::fromEpochTime(0));
        static const std::chrono::system_clock::time_point Zero;
        if (_documentLastModifiedTime != Zero &&
            fileInfo.getModifiedTime() != Zero &&
            _documentLastModifiedTime != fileInfo.getModifiedTime())
@@ -776,8 +776,8 @@
        _filename = fileInfo.getFilename();

        // Use the local temp file's timestamp.
        _lastFileModifiedTime = templateSource.empty() ? Poco::File(_storage->getRootFilePath()).getLastModified() :
                Poco::Timestamp::fromEpochTime(0);
        _lastFileModifiedTime = templateSource.empty() ? Util::getFileTimestamp(_storage->getRootFilePath()) :
                std::chrono::system_clock::time_point();

        bool dontUseCache = false;
#if MOBILEAPP
@@ -894,12 +894,14 @@
    const std::string uriAnonym = LOOLWSD::anonymizeUrl(uri);

    // If the file timestamp hasn't changed, skip saving.
    const Poco::Timestamp newFileModifiedTime = Poco::File(_storage->getRootFilePath()).getLastModified();
    const std::chrono::system_clock::time_point newFileModifiedTime = Util::getFileTimestamp(_storage->getRootFilePath());
    if (!isSaveAs && newFileModifiedTime == _lastFileModifiedTime && !isRename)
    {
        // Nothing to do.
        auto timeInSec = std::chrono::duration_cast<std::chrono::seconds>
                                            (std::chrono::system_clock::now() - _lastFileModifiedTime);
        LOG_DBG("Skipping unnecessary saving to URI [" << uriAnonym << "] with docKey [" << _docKey <<
                "]. File last modified " << _lastFileModifiedTime.elapsed() / 1000000 << " seconds ago.");
                "]. File last modified " << timeInSec.count() << " seconds ago.");
        _poll->wakeup();
        return true;
    }
@@ -1108,7 +1110,7 @@
    if (_sessions.find(sessionId) != _sessions.end())
    {
        // Invalidate the timestamp to force persisting.
        _lastFileModifiedTime = Poco::Timestamp::fromEpochTime(0);
        _lastFileModifiedTime = std::chrono::system_clock::time_point();

        // We do not want save to terminate editing mode if we are in edit mode now

@@ -1578,7 +1580,7 @@
    {
            std::ostringstream oss;
            oss << "HTTP/1.1 200 OK\r\n"
                << "Last-Modified: " << Poco::DateTimeFormatter::format(Poco::Timestamp(), Poco::DateTimeFormat::HTTP_FORMAT) << "\r\n"
                << "Last-Modified: " << Util::getHttpTimeNow() << "\r\n"
                << "User-Agent: " << WOPI_AGENT_STRING << "\r\n"
                << "Content-Length: " << saved->length() << "\r\n"
                << "Content-Type: application/octet-stream\r\n"
@@ -1601,7 +1603,7 @@
    // Bad request.
    std::ostringstream oss;
    oss << "HTTP/1.1 400\r\n"
        << "Date: " << Poco::DateTimeFormatter::format(Poco::Timestamp(), Poco::DateTimeFormat::HTTP_FORMAT) << "\r\n"
        << "Date: " << Util::getHttpTimeNow() << "\r\n"
        << "User-Agent: LOOLWSD WOPI Agent\r\n"
        << "Content-Length: 0\r\n"
        << "\r\n"
diff --git a/wsd/DocumentBroker.hpp b/wsd/DocumentBroker.hpp
index fe370cd..d3bdaa6 100644
--- a/wsd/DocumentBroker.hpp
+++ b/wsd/DocumentBroker.hpp
@@ -449,10 +449,10 @@
    std::chrono::steady_clock::time_point _lastSaveResponseTime;

    /// The document's last-modified time on storage.
    Poco::Timestamp _documentLastModifiedTime;
    std::chrono::system_clock::time_point _documentLastModifiedTime;

    /// The jailed file last-modified time.
    Poco::Timestamp _lastFileModifiedTime;
    std::chrono::system_clock::time_point _lastFileModifiedTime;

    /// All session of this DocBroker by ID.
    std::map<std::string, std::shared_ptr<ClientSession> > _sessions;
diff --git a/wsd/Storage.cpp b/wsd/Storage.cpp
index 0057f8c..b7fef17 100644
--- a/wsd/Storage.cpp
+++ b/wsd/Storage.cpp
@@ -18,8 +18,6 @@
#include <iconv.h>
#include <string>

#include <Poco/DateTime.h>
#include <Poco/DateTimeParser.h>
#include <Poco/Exception.h>
#include <Poco/JSON/Object.h>
#include <Poco/JSON/Parser.h>
@@ -41,7 +39,6 @@
#endif

#include <Poco/StreamCopier.h>
#include <Poco/Timestamp.h>
#include <Poco/URI.h>

#include "Auth.hpp"
@@ -261,9 +258,10 @@
    const Poco::Path path = Poco::Path(getUri().getPath());
    LOG_DBG("Getting info for local uri [" << LOOLWSD::anonymizeUrl(getUri().toString()) << "], path [" << LOOLWSD::anonymizeUrl(path.toString()) << "].");

    std::string str_path = path.toString();
    const auto& filename = path.getFileName();
    const Poco::File file = Poco::File(path);
    const Poco::Timestamp lastModified = file.getLastModified();
    std::chrono::high_resolution_clock::time_point lastModified = Util::getFileTimestamp(str_path);
    const size_t size = file.getSize();

    setFileInfo(FileInfo({filename, "localhost", lastModified, size}));
@@ -350,7 +348,9 @@

        // update its fileinfo object. This is used later to check if someone else changed the
        // document while we are/were editing it
        getFileInfo().setModifiedTime(Poco::File(getUri().getPath()).getLastModified());
        const Poco::Path path = Poco::Path(getUri().getPath());
        std::string str_path = path.toString();
        getFileInfo().setModifiedTime(Util::getFileTimestamp(str_path));
        LOG_TRC("New FileInfo modified time in storage " << getFileInfo().getModifiedTime());
    }
    catch (const Poco::Exception& exc)
@@ -397,28 +397,6 @@
#endif
}

Poco::Timestamp iso8601ToTimestamp(const std::string& iso8601Time, const std::string& name)
{
    Poco::Timestamp timestamp = Poco::Timestamp::fromEpochTime(0);
    try
    {
        if (!iso8601Time.empty())
        {
            int timeZoneDifferential;
            Poco::DateTime dateTime;
            Poco::DateTimeParser::parse(Poco::DateTimeFormat::ISO8601_FRAC_FORMAT, iso8601Time, dateTime, timeZoneDifferential);
            timestamp = dateTime.timestamp();
        }
    }
    catch (const Poco::SyntaxException& exc)
    {
        LOG_WRN(name << " [" << iso8601Time << "] is in invalid format: " << exc.displayText() <<
                (exc.nested() ? " (" + exc.nested()->displayText() + ")" : "") << ". Returning " << timestamp);
    }

    return timestamp;
}

} // anonymous namespace

std::unique_ptr<WopiStorage::WOPIFileInfo> WopiStorage::getWOPIFileInfo(const Authorization& auth)
@@ -610,7 +588,7 @@
        throw UnauthorizedRequestException("Access denied. WOPI::CheckFileInfo failed on: " + uriAnonym);
    }

    const Poco::Timestamp modifiedTime = iso8601ToTimestamp(lastModifiedTime, "LastModifiedTime");
    const std::chrono::system_clock::time_point modifiedTime = Util::iso8601ToTimestamp(lastModifiedTime, "LastModifiedTime");
    setFileInfo(FileInfo({filename, ownerId, modifiedTime, size}));

    return std::unique_ptr<WopiStorage::WOPIFileInfo>(new WOPIFileInfo(
@@ -745,9 +723,7 @@
            if (!getForceSave())
            {
                // Request WOPI host to not overwrite if timestamps mismatch
                request.set("X-LOOL-WOPI-Timestamp",
                            Poco::DateTimeFormatter::format(Poco::DateTime(getFileInfo().getModifiedTime()),
                                                            Poco::DateTimeFormat::ISO8601_FRAC_FORMAT));
                request.set("X-LOOL-WOPI-Timestamp", Util::getIso8601FracformatTime(getFileInfo().getModifiedTime()));
            }
        }
        else
@@ -855,7 +831,7 @@
            {
                const std::string lastModifiedTime = JsonUtil::getJSONValue<std::string>(object, "LastModifiedTime");
                LOG_TRC(wopiLog << " returns LastModifiedTime [" << lastModifiedTime << "].");
                getFileInfo().setModifiedTime(iso8601ToTimestamp(lastModifiedTime, "LastModifiedTime"));
                getFileInfo().setModifiedTime(Util::iso8601ToTimestamp(lastModifiedTime, "LastModifiedTime"));

                if (isSaveAs || isRename)
                {
diff --git a/wsd/Storage.hpp b/wsd/Storage.hpp
index db37087..06887c6 100644
--- a/wsd/Storage.hpp
+++ b/wsd/Storage.hpp
@@ -34,7 +34,7 @@
    public:
        FileInfo(const std::string& filename,
                 const std::string& ownerId,
                 const Poco::Timestamp& modifiedTime,
                 const std::chrono::system_clock::time_point& modifiedTime,
                 size_t /*size*/)
            : _filename(filename),
              _ownerId(ownerId),
@@ -52,14 +52,14 @@

        const std::string& getOwnerId() const { return _ownerId; }

        void setModifiedTime(const Poco::Timestamp& modifiedTime) { _modifiedTime = modifiedTime; }
        void setModifiedTime(const std::chrono::system_clock::time_point& modifiedTime) { _modifiedTime = modifiedTime; }

        const Poco::Timestamp& getModifiedTime() const { return _modifiedTime; }
        const std::chrono::system_clock::time_point& getModifiedTime() const { return _modifiedTime; }

    private:
        std::string _filename;
        std::string _ownerId;
        Poco::Timestamp _modifiedTime;
        std::chrono::system_clock::time_point _modifiedTime;
    };

    class SaveResult
@@ -124,7 +124,7 @@
        _uri(uri),
        _localStorePath(localStorePath),
        _jailPath(jailPath),
        _fileInfo("", "lool", Poco::Timestamp::fromEpochTime(0), 0),
        _fileInfo("", "lool", std::chrono::system_clock::time_point(), 0),
        _isLoaded(false),
        _forceSave(false),
        _isUserModified(false),
diff --git a/wsd/TileCache.cpp b/wsd/TileCache.cpp
index a4f16a5..c0d2705 100644
--- a/wsd/TileCache.cpp
+++ b/wsd/TileCache.cpp
@@ -27,7 +27,6 @@
#include <Poco/File.h>
#include <Poco/Path.h>
#include <Poco/StringTokenizer.h>
#include <Poco/Timestamp.h>
#include <Poco/URI.h>

#include "ClientSession.hpp"
@@ -40,18 +39,17 @@
using namespace LOOLProtocol;

using Poco::StringTokenizer;
using Poco::Timestamp;

TileCache::TileCache(const std::string& docURL,
                     const Timestamp& modifiedTime,
                     const std::chrono::system_clock::time_point& modifiedTime,
                     bool dontCache) :
    _docURL(docURL),
    _dontCache(dontCache)
{
#ifndef BUILDING_TESTS
    LOG_INF("TileCache ctor for uri [" << LOOLWSD::anonymizeUrl(_docURL) <<
            "], modifiedTime=" << (modifiedTime.raw()/1000000) <<
            "], dontCache=" << _dontCache);
            "], modifiedTime=" << std::chrono::duration_cast<std::chrono::seconds>
							(modifiedTime.time_since_epoch()).count() << "], dontCache=" << _dontCache);
#endif
    (void)modifiedTime;
}
diff --git a/wsd/TileCache.hpp b/wsd/TileCache.hpp
index d922b94..88d837e 100644
--- a/wsd/TileCache.hpp
+++ b/wsd/TileCache.hpp
@@ -16,7 +16,6 @@
#include <string>
#include <unordered_map>

#include <Poco/Timestamp.h>
#include <Rectangle.hpp>

#include "TileDesc.hpp"
@@ -83,7 +82,7 @@
    /// When the docURL is a non-file:// url, the timestamp has to be provided by the caller.
    /// For file:// url's, it's ignored.
    /// When it is missing for non-file:// url, it is assumed the document must be read, and no cached value used.
    TileCache(const std::string& docURL, const Poco::Timestamp& modifiedTime, bool dontCache = false);
    TileCache(const std::string& docURL, const std::chrono::system_clock::time_point& modifiedTime, bool dontCache = false);
    ~TileCache();

    /// Completely clear the cache contents.