libreoffice-online/common/FileUtil.cpp
Tor Lillqvist 95eb849217 Still more iOS app and related Online C++ code hacking
Re-think the plumbing between the different parts of the C++ Online
code. Do try to have it work more like in real Online on all but the
lowest socket level. Except that we don't have multiple processes, but
threads inside the same process. And instead of using actual system
sockets for WebSocket traffic between the threads, we use our own
FakeSocket things, with no WebSocket framing of messages.

Reduce the amount of #ifdef MOBILEAPP a bit also by compiling in the
UnitFoo things. Hardcode that so that no unit testing is ever
attempted, though. We don't try to dlopen any library.

Corresponding changes in the app Objective-C code. Plus fixes and
functionality improvements.

Now it gets so far that the JavaScript code thinks it has the document
tiles presented, and doesn't crash. But it hangs occasionally. And all
tiles show up blank.

Anyway, progress.

Change-Id: I769497c9a46ddb74984bc7af36d132b7b43895d4
2018-09-19 11:31:18 +03:00

284 lines
8.2 KiB
C++

/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
/*
* This file is part of the LibreOffice project.
*
* 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 <config.h>
#include "FileUtil.hpp"
#include <ftw.h>
#include <sys/stat.h>
#ifdef __linux
#include <sys/vfs.h>
#elif defined IOS
#import <Foundation/Foundation.h>
#endif
#include <chrono>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <mutex>
#include <string>
#include <Poco/TemporaryFile.h>
#include "Log.hpp"
#include "Util.hpp"
#include "Unit.hpp"
namespace
{
void alertAllUsersAndLog(const std::string& message, const std::string& cmd, const std::string& kind)
{
LOG_ERR(message);
Util::alertAllUsers(cmd, kind);
}
}
namespace FileUtil
{
std::string createRandomDir(const std::string& path)
{
const std::string name = Util::rng::getFilename(64);
Poco::File(Poco::Path(path, name)).createDirectories();
return name;
}
std::string getTempFilePath(const std::string& srcDir, const std::string& srcFilename, const std::string& dstFilenamePrefix)
{
const std::string srcPath = srcDir + '/' + srcFilename;
const std::string dstFilename = dstFilenamePrefix + Util::encodeId(Util::rng::getNext()) + '_' + srcFilename;
const std::string dstPath = Poco::Path(Poco::Path::temp(), dstFilename).toString();
Poco::File(srcPath).copyTo(dstPath);
Poco::TemporaryFile::registerForDeletion(dstPath);
return dstPath;
}
std::string getTempFilePath(const std::string& srcDir, const std::string& srcFilename)
{
return getTempFilePath(srcDir, srcFilename, "");
}
bool saveDataToFileSafely(const std::string& fileName, const char *data, size_t size)
{
const auto tempFileName = fileName + ".temp";
std::fstream outStream(tempFileName, std::ios::out);
// If we can't create the file properly, just remove it
if (!outStream.good())
{
alertAllUsersAndLog("Creating " + tempFileName + " failed, disk full?", "internal", "diskfull");
// Try removing both just in case
std::remove(tempFileName.c_str());
std::remove(fileName.c_str());
return false;
}
else
{
outStream.write(data, size);
if (!outStream.good())
{
alertAllUsersAndLog("Writing to " + tempFileName + " failed, disk full?", "internal", "diskfull");
outStream.close();
std::remove(tempFileName.c_str());
std::remove(fileName.c_str());
return false;
}
else
{
outStream.close();
if (!outStream.good())
{
alertAllUsersAndLog("Closing " + tempFileName + " failed, disk full?", "internal", "diskfull");
std::remove(tempFileName.c_str());
std::remove(fileName.c_str());
return false;
}
else
{
// Everything OK, rename the file to its proper name
if (std::rename(tempFileName.c_str(), fileName.c_str()) == 0)
{
LOG_DBG("Renaming " << tempFileName << " to " << fileName << " OK.");
return true;
}
else
{
alertAllUsersAndLog("Renaming " + tempFileName + " to " + fileName + " failed, disk full?", "internal", "diskfull");
std::remove(tempFileName.c_str());
std::remove(fileName.c_str());
return false;
}
}
}
}
}
static int nftw_cb(const char *fpath, const struct stat*, int type, struct FTW*)
{
if (type == FTW_DP)
{
rmdir(fpath);
}
else if (type == FTW_F || type == FTW_SL)
{
unlink(fpath);
}
// Always continue even when things go wrong.
return 0;
}
void removeFile(const std::string& path, const bool recursive)
{
try
{
struct stat sb;
if (!recursive || stat(path.c_str(), &sb) == -1 || S_ISREG(sb.st_mode))
{
// Non-recursive directories, and files.
Poco::File(path).remove(recursive);
}
else
{
// Directories only.
nftw(path.c_str(), nftw_cb, 128, FTW_DEPTH | FTW_PHYS);
}
}
catch (const std::exception&)
{
// Already removed or we don't care about failures.
}
}
} // namespace FileUtil
namespace
{
struct fs
{
fs(const std::string& p, dev_t d)
: path(p), dev(d)
{
}
std::string path;
dev_t dev;
};
struct fsComparator
{
bool operator() (const fs& lhs, const fs& rhs) const
{
return (lhs.dev < rhs.dev);
}
};
static std::mutex fsmutex;
static std::set<fs, fsComparator> filesystems;
} // anonymous namespace
namespace FileUtil
{
#ifndef MOBILEAPP
void registerFileSystemForDiskSpaceChecks(const std::string& path)
{
const std::string::size_type lastSlash = path.rfind('/');
assert(path.empty() || lastSlash != std::string::npos);
if (lastSlash != std::string::npos)
{
const std::string dirPath = path.substr(0, lastSlash + 1) + '.';
LOG_INF("Registering filesystem for space checks: [" << dirPath << "]");
std::lock_guard<std::mutex> lock(fsmutex);
struct stat s;
if (stat(dirPath.c_str(), &s) == 0)
{
filesystems.insert(fs(dirPath, s.st_dev));
}
}
}
std::string checkDiskSpaceOnRegisteredFileSystems(const bool cacheLastCheck)
{
static std::chrono::steady_clock::time_point lastCheck;
std::chrono::steady_clock::time_point now(std::chrono::steady_clock::now());
std::lock_guard<std::mutex> lock(fsmutex);
// Don't check more often than once a minute
if (std::chrono::duration_cast<std::chrono::seconds>(now - lastCheck).count() < 60)
return std::string();
if (cacheLastCheck)
lastCheck = now;
for (const auto& i: filesystems)
{
if (!checkDiskSpace(i.path))
{
return i.path;
}
}
return std::string();
}
#endif
bool checkDiskSpace(const std::string& path)
{
assert(!path.empty());
#ifndef MOBILEAPP
bool hookResult;
if (UnitBase::get().filterCheckDiskSpace(path, hookResult))
return hookResult;
#endif
// we should be able to run just OK with 5GB
constexpr int64_t ENOUGH_SPACE = int64_t(5)*1024*1024*1024;
#ifdef __linux
struct statfs sfs;
if (statfs(path.c_str(), &sfs) == -1)
return true;
const int64_t freeBytes = static_cast<int64_t>(sfs.f_bavail) * sfs.f_bsize;
LOG_INF("Filesystem [" << path << "] has " << (freeBytes / 1024 / 1024) <<
" MB free (" << (sfs.f_bavail * 100. / sfs.f_blocks) << "%).");
if (freeBytes > ENOUGH_SPACE)
return true;
if (static_cast<double>(sfs.f_bavail) / sfs.f_blocks <= 0.05)
return false;
#elif defined IOS
NSDictionary *atDict = [[NSFileManager defaultManager] attributesOfFileSystemForPath:@"/" error:NULL];
long long freeSpace = [[atDict objectForKey:NSFileSystemFreeSize] longLongValue];
long long totalSpace = [[atDict objectForKey:NSFileSystemSize] longLongValue];
if (freeSpace > ENOUGH_SPACE)
return true;
if (static_cast<double>(freeSpace) / totalSpace <= 0.05)
return false;
#endif
return true;
}
} // namespace FileUtil
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */