Skip to content

Commit

Permalink
Merge pull request #6434 from nextcloud/feature/detect-open-files
Browse files Browse the repository at this point in the history
Feature/detect open files
  • Loading branch information
allexzander authored Feb 14, 2024
2 parents 39611b9 + b2aca21 commit e2410ff
Show file tree
Hide file tree
Showing 7 changed files with 151 additions and 1 deletion.
12 changes: 12 additions & 0 deletions src/common/utility.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,18 @@ Q_DECLARE_LOGGING_CATEGORY(lcUtility)
* @{
*/
namespace Utility {
struct ProcessInfosForOpenFile {
ulong processId;
QString processName;
};
/**
* @brief Queries the OS for processes that are keeping the file open(using it)
*
* @param filePath absolute file path
* @return list of ProcessInfosForOpenFile
*/
OCSYNC_EXPORT QVector<ProcessInfosForOpenFile> queryProcessInfosKeepingFileOpen(const QString &filePath);

OCSYNC_EXPORT int rand();
OCSYNC_EXPORT void sleep(int sec);
OCSYNC_EXPORT void usleep(int usec);
Expand Down
6 changes: 6 additions & 0 deletions src/common/utility_mac.mm
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@

namespace OCC {

QVector<Utility::ProcessInfosForOpenFile> Utility::queryProcessInfosKeepingFileOpen(const QString &filePath)
{
Q_UNUSED(filePath)
return {};
}

void Utility::setupFavLink(const QString &folder)
{
// Finder: Place under "Places"/"Favorites" on the left sidebar
Expand Down
6 changes: 6 additions & 0 deletions src/common/utility_unix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@

namespace OCC {

QVector<Utility::ProcessInfosForOpenFile> Utility::queryProcessInfosKeepingFileOpen(const QString &filePath)
{
Q_UNUSED(filePath)
return {};
}

void Utility::setupFavLink(const QString &folder)
{
// Nautilus: add to ~/.gtk-bookmarks
Expand Down
68 changes: 68 additions & 0 deletions src/common/utility_win.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@

#include <comdef.h>
#include <Lmcons.h>
#include <psapi.h>
#include <RestartManager.h>
#include <shlguid.h>
#include <shlobj.h>
#include <string>
Expand All @@ -43,6 +45,72 @@ static const char runPathC[] = R"(HKEY_CURRENT_USER\Software\Microsoft\Windows\C

namespace OCC {

QVector<Utility::ProcessInfosForOpenFile> Utility::queryProcessInfosKeepingFileOpen(const QString &filePath)
{
QVector<ProcessInfosForOpenFile> results;

DWORD restartManagerSession = 0;
WCHAR restartManagerSessionKey[CCH_RM_SESSION_KEY + 1] = {0};
auto errorStatus = RmStartSession(&restartManagerSession, 0, restartManagerSessionKey);
if (errorStatus != ERROR_SUCCESS) {
return results;
}

LPCWSTR files[] = {reinterpret_cast<LPCWSTR>(filePath.utf16())};
errorStatus = RmRegisterResources(restartManagerSession, 1, files, 0, NULL, 0, NULL);
if (errorStatus != ERROR_SUCCESS) {
RmEndSession(restartManagerSession);
return results;
}

DWORD rebootReasons = 0;
UINT rmProcessInfosNeededCount = 0;
std::vector<RM_PROCESS_INFO> rmProcessInfos;
auto rmProcessInfosRequestedCount = static_cast<UINT>(rmProcessInfos.size());
errorStatus = RmGetList(restartManagerSession, &rmProcessInfosNeededCount, &rmProcessInfosRequestedCount, rmProcessInfos.data(), &rebootReasons);

if (errorStatus == ERROR_MORE_DATA) {
rmProcessInfos.resize(rmProcessInfosNeededCount, {});
rmProcessInfosRequestedCount = static_cast<UINT>(rmProcessInfos.size());
errorStatus = RmGetList(restartManagerSession, &rmProcessInfosNeededCount, &rmProcessInfosRequestedCount, rmProcessInfos.data(), &rebootReasons);
}

if (errorStatus != ERROR_SUCCESS || rmProcessInfos.empty()) {
RmEndSession(restartManagerSession);
return results;
}

for (size_t i = 0; i < rmProcessInfos.size(); ++i) {
const auto processHandle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, rmProcessInfos[i].Process.dwProcessId);
if (!processHandle) {
continue;
}

FILETIME ftCreate, ftExit, ftKernel, ftUser;

if (!GetProcessTimes(processHandle, &ftCreate, &ftExit, &ftKernel, &ftUser)
|| CompareFileTime(&rmProcessInfos[i].Process.ProcessStartTime, &ftCreate) != 0) {
CloseHandle(processHandle);
continue;
}

WCHAR processFullPath[MAX_PATH];
DWORD processFullPathLength = MAX_PATH;
if (QueryFullProcessImageNameW(processHandle, 0, processFullPath, &processFullPathLength) && processFullPathLength <= MAX_PATH) {
const auto processFullPathString = QDir::fromNativeSeparators(QString::fromWCharArray(processFullPath));
const QFileInfo fileInfoForProcess(processFullPathString);
const auto processName = fileInfoForProcess.fileName();
if (!processName.isEmpty()) {
results.push_back(Utility::ProcessInfosForOpenFile{rmProcessInfos[i].Process.dwProcessId, processName});
}
}
CloseHandle(processHandle);
}
RmEndSession(restartManagerSession);

return results;
}

void Utility::setupFavLink(const QString &folder)
{
// First create a Desktop.ini so that the folder and favorite link show our application's icon.
Expand Down
2 changes: 1 addition & 1 deletion src/csync/ConfigureChecks.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ if (NOT LINUX)
endif (NOT LINUX)

if(WIN32)
set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} psapi kernel32)
set(CMAKE_REQUIRED_LIBRARIES ${CMAKE_REQUIRED_LIBRARIES} psapi kernel32 Rstrtmgr)
endif()

check_function_exists(utimes HAVE_UTIMES)
Expand Down
56 changes: 56 additions & 0 deletions src/libsync/discovery.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,12 @@
#include "csync_exclude.h"
#include "csync.h"

namespace
{
constexpr const char *editorNamesForDelayedUpload[] = {"PowerPDF"};
constexpr const char *fileExtensionsToCheckIfOpenForSigning[] = {".pdf"};
constexpr auto delayIntervalForSyncRetryForOpenedForSigningFilesSeconds = 60;
}

namespace OCC {

Expand Down Expand Up @@ -1022,6 +1028,19 @@ void ProcessDirectoryJob::processFileAnalyzeLocalInfo(
item->_status = SyncFileItem::Status::NormalError;
}

{
const auto foundEditorsKeepingFileBusy = queryEditorsKeepingFileBusy(item, path);
if (!foundEditorsKeepingFileBusy.isEmpty()) {
item->_instruction = CSYNC_INSTRUCTION_ERROR;
const auto editorsString = foundEditorsKeepingFileBusy.join(", ");
qCInfo(lcDisco) << "Failed, because it is open in the editor." << item->_file << "direction" << item->_direction << editorsString;
item->_errorString = tr("Could not upload file, because it is open in \"%1\".").arg(editorsString);
item->_status = SyncFileItem::Status::SoftError;
_discoveryData->_anotherSyncNeeded = true;
_discoveryData->_filesNeedingScheduledSync.insert(path._original, delayIntervalForSyncRetryForOpenedForSigningFilesSeconds);
}
}

if (dbEntry.isValid() && item->isDirectory()) {
item->_e2eEncryptionStatus = EncryptionStatusEnums::fromDbEncryptionStatus(dbEntry._e2eEncryptionStatus);
if (item->isEncrypted()) {
Expand Down Expand Up @@ -1839,6 +1858,43 @@ bool ProcessDirectoryJob::isRename(const QString &originalPath) const
*/
}

QStringList ProcessDirectoryJob::queryEditorsKeepingFileBusy(const SyncFileItemPtr &item, const PathTuple &path) const
{
QStringList matchingEditorsKeepingFileBusy;

if (item->isDirectory() || item->_direction != SyncFileItem::Up) {
return matchingEditorsKeepingFileBusy;
}

const auto isMatchingFileExtension = std::find_if(std::cbegin(fileExtensionsToCheckIfOpenForSigning), std::cend(fileExtensionsToCheckIfOpenForSigning),
[path](const auto &matchingExtension) {
return path._local.endsWith(matchingExtension, Qt::CaseInsensitive);
}) != std::cend(fileExtensionsToCheckIfOpenForSigning);

if (!isMatchingFileExtension) {
return matchingEditorsKeepingFileBusy;
}

const QString fullLocalPath(_discoveryData->_localDir + path._local);
const auto editorsKeepingFileBusy = Utility::queryProcessInfosKeepingFileOpen(fullLocalPath);

for (const auto &detectedEditorName : editorsKeepingFileBusy) {
const auto isMatchingEditorFound = std::find_if(std::cbegin(editorNamesForDelayedUpload), std::cend(editorNamesForDelayedUpload),
[detectedEditorName](const auto &matchingEditorName) {
return detectedEditorName.processName.startsWith(matchingEditorName, Qt::CaseInsensitive);
}) != std::cend(editorNamesForDelayedUpload);
if (isMatchingEditorFound) {
matchingEditorsKeepingFileBusy.push_back(detectedEditorName.processName);
}
}

if (!matchingEditorsKeepingFileBusy.isEmpty()) {
matchingEditorsKeepingFileBusy.push_back("PowerPDF.exe");
}

return matchingEditorsKeepingFileBusy;
}

auto ProcessDirectoryJob::checkMovePermissions(RemotePermissions srcPerm, const QString &srcPath,
bool isDirectory)
-> MovePermissionResult
Expand Down
2 changes: 2 additions & 0 deletions src/libsync/discovery.h
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,8 @@ class ProcessDirectoryJob : public QObject

[[nodiscard]] bool isRename(const QString &originalPath) const;

[[nodiscard]] QStringList queryEditorsKeepingFileBusy(const SyncFileItemPtr &item, const PathTuple &path) const;

struct MovePermissionResult
{
// whether moving/renaming the source is ok
Expand Down

0 comments on commit e2410ff

Please sign in to comment.