summaryrefslogtreecommitdiff
path: root/FreeFileSync/Source/fs/gdrive.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'FreeFileSync/Source/fs/gdrive.cpp')
-rw-r--r--FreeFileSync/Source/fs/gdrive.cpp1119
1 files changed, 540 insertions, 579 deletions
diff --git a/FreeFileSync/Source/fs/gdrive.cpp b/FreeFileSync/Source/fs/gdrive.cpp
index f8044a57..cc778456 100644
--- a/FreeFileSync/Source/fs/gdrive.cpp
+++ b/FreeFileSync/Source/fs/gdrive.cpp
@@ -170,7 +170,7 @@ std::wstring tryFormatHttpErrorCode(int ec) //https://en.wikipedia.org/wiki/List
if (ec == 415) return L"Unsupported Media Type.";
if (ec == 416) return L"Range Not Satisfiable.";
if (ec == 417) return L"Expectation Failed.";
- if (ec == 418) return L"I\'m a teapot.";
+ if (ec == 418) return L"I'm a teapot.";
if (ec == 421) return L"Misdirected Request.";
if (ec == 422) return L"Unprocessable Entity.";
if (ec == 423) return L"Locked.";
@@ -202,18 +202,11 @@ Global<UniSessionCounter> httpSessionCount(createUniSessionCounter());
class HttpSession
{
public:
- HttpSession(const HttpSessionId& sessionId, const Zstring& caCertFilePath) : //throw FileError
+ HttpSession(const HttpSessionId& sessionId, const Zstring& caCertFilePath) : //throw SysError
sessionId_(sessionId),
- caCertFilePath_(utfTo<std::string>(caCertFilePath))
- {
- try
- {
- libsshCurlUnifiedInitCookie_ = getLibsshCurlUnifiedInitCookie(httpSessionCount); //throw SysError
- }
- catch (const SysError& e) { throw FileError(replaceCpy(_("Unable to connect to %x."), L"%x", fmtPath(sessionId_.server)), e.toString()); }
-
- lastSuccessfulUseTime_ = std::chrono::steady_clock::now();
- }
+ caCertFilePath_(utfTo<std::string>(caCertFilePath)),
+ libsshCurlUnifiedInitCookie_(getLibsshCurlUnifiedInitCookie(httpSessionCount)), //throw SysError
+ lastSuccessfulUseTime_(std::chrono::steady_clock::now()) {}
~HttpSession()
{
@@ -239,7 +232,7 @@ public:
//std::string contentType;
};
HttpResult perform(const std::string& serverRelPath,
- const std::vector<std::string>& extraHeaders, const std::vector<Option>& extraOptions, //throw FileError
+ const std::vector<std::string>& extraHeaders, const std::vector<Option>& extraOptions, //throw SysError
const std::function<void (const void* buffer, size_t bytesToWrite)>& writeResponse /*throw X*/, //optional
const std::function<size_t( void* buffer, size_t bytesToRead )>& readRequest /*throw X*/) //
{
@@ -247,8 +240,7 @@ public:
{
easyHandle_ = ::curl_easy_init();
if (!easyHandle_)
- throw FileError(replaceCpy(_("Unable to connect to %x."), L"%x", fmtPath(sessionId_.server)),
- formatSystemError(L"curl_easy_init", formatCurlErrorRaw(CURLE_OUT_OF_MEMORY), std::wstring()));
+ throw SysError(formatSystemError(L"curl_easy_init", formatCurlErrorRaw(CURLE_OUT_OF_MEMORY), std::wstring()));
}
else
::curl_easy_reset(easyHandle_);
@@ -360,9 +352,8 @@ public:
{
const CURLcode rc = ::curl_easy_setopt(easyHandle_, opt.option, opt.value);
if (rc != CURLE_OK)
- throw FileError(replaceCpy(_("Unable to connect to %x."), L"%x", fmtPath(sessionId_.server)),
- formatSystemError(L"curl_easy_setopt " + numberTo<std::wstring>(opt.option),
- formatCurlErrorRaw(rc), utfTo<std::wstring>(::curl_easy_strerror(rc))));
+ throw SysError(formatSystemError(L"curl_easy_setopt " + numberTo<std::wstring>(opt.option),
+ formatCurlErrorRaw(rc), utfTo<std::wstring>(::curl_easy_strerror(rc))));
}
//=======================================================================================================
@@ -376,28 +367,14 @@ public:
std::rethrow_exception(userCallbackException); //throw X
//=======================================================================================================
- try
- {
- long httpStatusCode = 0; //optional
- {
- const CURLcode rc = ::curl_easy_getinfo(easyHandle_, CURLINFO_RESPONSE_CODE, &httpStatusCode);
- if (rc != CURLE_OK)
- throw SysError(formatSystemError(L"curl_easy_getinfo: CURLINFO_RESPONSE_CODE", formatCurlErrorRaw(rc), utfTo<std::wstring>(::curl_easy_strerror(rc))));
- }
- //char* contentType = nullptr; //optional; owned by libcurl
- //{
- // const CURLcode rc = ::curl_easy_getinfo(easyHandle_, CURLINFO_CONTENT_TYPE, &contentType);
- // if (rc != CURLE_OK)
- // throw SysError(formatSystemError(L"curl_easy_getinfo: CURLINFO_CONTENT_TYPE", formatCurlErrorRaw(rc), utfTo<std::wstring>(::curl_easy_strerror(rc))));
- //}
-
- if (rcPerf != CURLE_OK)
- throw SysError(formatLastCurlError(L"curl_easy_perform", rcPerf, httpStatusCode));
-
- lastSuccessfulUseTime_ = std::chrono::steady_clock::now();
- return { static_cast<int>(httpStatusCode) /*, contentType ? contentType : ""*/ };
- }
- catch (const SysError& e) { throw FileError(replaceCpy(_("Unable to access %x."), L"%x", fmtPath(sessionId_.server)), e.toString()); }
+ long httpStatusCode = 0; //optional
+ /*const CURLcode rc = */ ::curl_easy_getinfo(easyHandle_, CURLINFO_RESPONSE_CODE, &httpStatusCode);
+
+ if (rcPerf != CURLE_OK)
+ throw SysError(formatLastCurlError(L"curl_easy_perform", rcPerf, httpStatusCode));
+
+ lastSuccessfulUseTime_ = std::chrono::steady_clock::now();
+ return { static_cast<int>(httpStatusCode) /*, contentType ? contentType : ""*/ };
}
//------------------------------------------------------------------------------------------------------------
@@ -439,8 +416,8 @@ private:
CURL* easyHandle_ = nullptr;
char curlErrorBuf_[CURL_ERROR_SIZE] = {};
- std::chrono::steady_clock::time_point lastSuccessfulUseTime_;
std::shared_ptr<UniCounterCookie> libsshCurlUnifiedInitCookie_;
+ std::chrono::steady_clock::time_point lastSuccessfulUseTime_;
};
//----------------------------------------------------------------------------------------------------------------
@@ -464,7 +441,7 @@ public:
using IdleHttpSessions = std::vector<std::unique_ptr<HttpSession>>;
- void access(const HttpSessionId& login, const std::function<void(HttpSession& session)>& useHttpSession /*throw X*/) //throw FileError, X
+ void access(const HttpSessionId& login, const std::function<void(HttpSession& session)>& useHttpSession /*throw X*/) //throw SysError, X
{
Protected<HttpSessionManager::IdleHttpSessions>& sessionStore = getSessionStore(login);
@@ -482,7 +459,7 @@ public:
//create new HTTP session outside the lock: 1. don't block other threads 2. non-atomic regarding "sessionStore"! => one session too many is not a problem!
if (!httpSession)
- httpSession = std::make_unique<HttpSession>(login, caCertFilePath_); //throw FileError
+ httpSession = std::make_unique<HttpSession>(login, caCertFilePath_); //throw SysError
ZEN_ON_SCOPE_EXIT(
if (httpSession->isHealthy()) //thread that created the "!isHealthy()" session is responsible for clean up (avoid hitting server connection limits!)
@@ -565,7 +542,7 @@ Global<HttpSessionManager> httpSessionManager;
//===========================================================================================================================
//try to get a grip on this crazy REST API: - parameters are passed via query string, header, or body, using GET, POST, PUT, PATCH, DELETE, ... it's a dice roll
-HttpSession::HttpResult googleHttpsRequest(const std::string& serverRelPath, //throw FileError
+HttpSession::HttpResult googleHttpsRequest(const std::string& serverRelPath, //throw SysError
const std::vector<std::string>& extraHeaders,
const std::vector<HttpSession::Option>& extraOptions,
const std::function<void (const void* buffer, size_t bytesToWrite)>& writeResponse /*throw X*/, //optional
@@ -573,11 +550,11 @@ HttpSession::HttpResult googleHttpsRequest(const std::string& serverRelPath, //t
{
const std::shared_ptr<HttpSessionManager> mgr = httpSessionManager.get();
if (!mgr)
- throw FileError(replaceCpy(_("Unable to connect to %x."), L"%x", fmtPath(GOOGLE_REST_API_SERVER)), L"Function call not allowed during process init/shutdown.");
+ throw SysError(L"googleHttpsRequest() function call not allowed during init/shutdown.");
HttpSession::HttpResult httpResult;
- mgr->access(HttpSessionId(GOOGLE_REST_API_SERVER), [&](HttpSession& session) //throw FileError
+ mgr->access(HttpSessionId(GOOGLE_REST_API_SERVER), [&](HttpSession& session) //throw SysError
{
std::vector<HttpSession::Option> options =
{
@@ -588,7 +565,7 @@ HttpSession::HttpResult googleHttpsRequest(const std::string& serverRelPath, //t
};
append(options, extraOptions);
- httpResult = session.perform(serverRelPath, extraHeaders, options, writeResponse, readRequest); //throw FileError
+ httpResult = session.perform(serverRelPath, extraHeaders, options, writeResponse, readRequest); //throw SysError
});
return httpResult;
}
@@ -600,7 +577,7 @@ struct GoogleUserInfo
std::wstring displayName;
Zstring email;
};
-GoogleUserInfo getUserInfo(const std::string& accessToken) //throw FileError
+GoogleUserInfo getUserInfo(const std::string& accessToken) //throw SysError
{
//https://developers.google.com/drive/api/v3/reference/about
const std::string queryParams = xWwwFormUrlEncode(
@@ -608,7 +585,7 @@ GoogleUserInfo getUserInfo(const std::string& accessToken) //throw FileError
{ "fields", "user/displayName,user/emailAddress" },
});
std::string response;
- googleHttpsRequest("/drive/v3/about?" + queryParams, { "Authorization: Bearer " + accessToken }, {} /*extraOptions*/, //throw FileError
+ googleHttpsRequest("/drive/v3/about?" + queryParams, { "Authorization: Bearer " + accessToken }, {} /*extraOptions*/, //throw SysError
[&](const void* buffer, size_t bytesToWrite) { response.append(static_cast<const char*>(buffer), bytesToWrite); }, nullptr /*readRequest*/);
JsonValue jresponse;
@@ -623,7 +600,7 @@ GoogleUserInfo getUserInfo(const std::string& accessToken) //throw FileError
return { utfTo<std::wstring>(*displayName), utfTo<Zstring>(*email) };
}
- throw FileError(replaceCpy(_("Failed to get information about server %x."), L"%x", fmtPath(Zstr("Google Drive"))), formatGoogleErrorRaw(response));
+ throw SysError(formatGoogleErrorRaw(response));
}
@@ -663,7 +640,7 @@ struct GoogleAuthCode
struct GoogleAccessToken
{
std::string value;
- time_t validUntil; //remaining lifetime of the access token
+ time_t validUntil = 0; //remaining lifetime of the access token
};
struct GoogleAccessInfo
@@ -673,7 +650,7 @@ struct GoogleAccessInfo
GoogleUserInfo userInfo;
};
-GoogleAccessInfo googleDriveExchangeAuthCode(const GoogleAuthCode& authCode) //throw FileError
+GoogleAccessInfo googleDriveExchangeAuthCode(const GoogleAuthCode& authCode) //throw SysError
{
//https://developers.google.com/identity/protocols/OAuth2InstalledApp#exchange-authorization-code
const std::string postBuf = xWwwFormUrlEncode(
@@ -686,7 +663,7 @@ GoogleAccessInfo googleDriveExchangeAuthCode(const GoogleAuthCode& authCode) //t
{ "code_verifier", authCode.codeChallenge },
});
std::string response;
- googleHttpsRequest("/oauth2/v4/token", {} /*extraHeaders*/, { { CURLOPT_POSTFIELDS, postBuf.c_str() } }, //throw FileError
+ googleHttpsRequest("/oauth2/v4/token", {} /*extraHeaders*/, { { CURLOPT_POSTFIELDS, postBuf.c_str() } }, //throw SysError
[&](const void* buffer, size_t bytesToWrite) { response.append(static_cast<const char*>(buffer), bytesToWrite); }, nullptr /*readRequest*/);
JsonValue jresponse;
@@ -697,81 +674,80 @@ GoogleAccessInfo googleDriveExchangeAuthCode(const GoogleAuthCode& authCode) //t
const std::optional<std::string> refreshToken = getPrimitiveFromJsonObject(jresponse, "refresh_token");
const std::optional<std::string> expiresIn = getPrimitiveFromJsonObject(jresponse, "expires_in"); //e.g. 3600 seconds
if (!accessToken || !refreshToken || !expiresIn)
- throw FileError(replaceCpy(_("Unable to connect to %x."), L"%x", L"Google Drive"), formatGoogleErrorRaw(response));
+ throw SysError(formatGoogleErrorRaw(response));
- const GoogleUserInfo userInfo = getUserInfo(*accessToken); //throw FileError
+ const GoogleUserInfo userInfo = getUserInfo(*accessToken); //throw SysError
return { { *accessToken, std::time(nullptr) + stringTo<time_t>(*expiresIn) }, *refreshToken, userInfo };
}
-GoogleAccessInfo authorizeAccessToGoogleDrive(const Zstring& googleLoginHint, const std::function<void()>& updateGui /*throw X*/) //throw FileError, X
+GoogleAccessInfo authorizeAccessToGoogleDrive(const Zstring& googleLoginHint, const std::function<void()>& updateGui /*throw X*/) //throw SysError, X
{
- try //spin up a web server to wait for the HTTP GET after Google authentication
- {
- ::addrinfo hints = {};
- hints.ai_family = AF_INET; //make sure our server is reached by IPv4 127.0.0.1, not IPv6 [::1]
- hints.ai_socktype = SOCK_STREAM; //we *do* care about this one!
- hints.ai_flags = AI_PASSIVE; //the returned socket addresses will be suitable for bind(2)ing a socket that will accept(2) connections.
- hints.ai_flags |= AI_ADDRCONFIG; //no such issue on Linux: https://bugs.chromium.org/p/chromium/issues/detail?id=5234
- ::addrinfo* servinfo = nullptr;
- ZEN_ON_SCOPE_EXIT(if (servinfo) ::freeaddrinfo(servinfo));
-
- //ServiceName == "0" => open the next best free port
- const int rcGai = ::getaddrinfo(nullptr, //_In_opt_ PCSTR pNodeName,
- "0", //_In_opt_ PCSTR pServiceName,
- &hints, //_In_opt_ const ADDRINFOA* pHints,
- &servinfo); //_Outptr_ PADDRINFOA* ppResult
- if (rcGai != 0)
- throw SysError(formatSystemError(L"getaddrinfo", replaceCpy(_("Error Code %x"), L"%x", numberTo<std::wstring>(rcGai)), utfTo<std::wstring>(::gai_strerror(rcGai))));
- if (!servinfo)
- throw SysError(L"getaddrinfo: empty server info");
-
- auto getBoundSocket = [&](const auto& /*::addrinfo*/ ai)
- {
- SocketType testSocket = ::socket(ai.ai_family, ai.ai_socktype, ai.ai_protocol);
- if (testSocket == invalidSocket)
- THROW_LAST_SYS_ERROR_WSA(L"socket");
- ZEN_ON_SCOPE_FAIL(closeSocket(testSocket));
-
- if (::bind(testSocket, ai.ai_addr, static_cast<int>(ai.ai_addrlen)) != 0)
- THROW_LAST_SYS_ERROR_WSA(L"bind");
+ //spin up a web server to wait for the HTTP GET after Google authentication
+ ::addrinfo hints = {};
+ hints.ai_family = AF_INET; //make sure our server is reached by IPv4 127.0.0.1, not IPv6 [::1]
+ hints.ai_socktype = SOCK_STREAM; //we *do* care about this one!
+ hints.ai_flags = AI_PASSIVE; //the returned socket addresses will be suitable for bind(2)ing a socket that will accept(2) connections.
+ hints.ai_flags |= AI_ADDRCONFIG; //no such issue on Linux: https://bugs.chromium.org/p/chromium/issues/detail?id=5234
+ ::addrinfo* servinfo = nullptr;
+ ZEN_ON_SCOPE_EXIT(if (servinfo) ::freeaddrinfo(servinfo));
+
+ //ServiceName == "0" => open the next best free port
+ const int rcGai = ::getaddrinfo(nullptr, //_In_opt_ PCSTR pNodeName,
+ "0", //_In_opt_ PCSTR pServiceName,
+ &hints, //_In_opt_ const ADDRINFOA* pHints,
+ &servinfo); //_Outptr_ PADDRINFOA* ppResult
+ if (rcGai != 0)
+ throw SysError(formatSystemError(L"getaddrinfo", replaceCpy(_("Error Code %x"), L"%x", numberTo<std::wstring>(rcGai)), utfTo<std::wstring>(::gai_strerror(rcGai))));
+ if (!servinfo)
+ throw SysError(L"getaddrinfo: empty server info");
+
+ auto getBoundSocket = [&](const auto& /*::addrinfo*/ ai)
+ {
+ SocketType testSocket = ::socket(ai.ai_family, ai.ai_socktype, ai.ai_protocol);
+ if (testSocket == invalidSocket)
+ THROW_LAST_SYS_ERROR_WSA(L"socket");
+ ZEN_ON_SCOPE_FAIL(closeSocket(testSocket));
+
+ if (::bind(testSocket, ai.ai_addr, static_cast<int>(ai.ai_addrlen)) != 0)
+ THROW_LAST_SYS_ERROR_WSA(L"bind");
+
+ return testSocket;
+ };
- return testSocket;
- };
+ SocketType socket = invalidSocket;
- SocketType socket = invalidSocket;
+ std::optional<SysError> firstError;
+ for (const auto* /*::addrinfo*/ si = servinfo; si; si = si->ai_next)
+ try
+ {
+ socket = getBoundSocket(*si); //throw SysError; pass ownership
+ break;
+ }
+ catch (const SysError& e) { if (!firstError) firstError = e; }
- std::optional<SysError> firstError;
- for (const auto* /*::addrinfo*/ si = servinfo; si; si = si->ai_next)
- try
- {
- socket = getBoundSocket(*si); //throw SysError; pass ownership
- break;
- }
- catch (const SysError& e) { if (!firstError) firstError = e; }
+ if (socket == invalidSocket)
+ throw* firstError; //list was not empty, so there must have been an error!
- if (socket == invalidSocket)
- throw* firstError; //list was not empty, so there must have been an error!
+ ZEN_ON_SCOPE_EXIT(closeSocket(socket));
- ZEN_ON_SCOPE_EXIT(closeSocket(socket));
-
- sockaddr_storage addr = {}; //"sufficiently large to store address information for IPv4 or IPv6" => sockaddr_in and sockaddr_in6
- socklen_t addrLen = sizeof(addr);
- if (::getsockname(socket, reinterpret_cast<sockaddr*>(&addr), &addrLen) != 0)
- THROW_LAST_SYS_ERROR_WSA(L"getsockname");
+ sockaddr_storage addr = {}; //"sufficiently large to store address information for IPv4 or IPv6" => sockaddr_in and sockaddr_in6
+ socklen_t addrLen = sizeof(addr);
+ if (::getsockname(socket, reinterpret_cast<sockaddr*>(&addr), &addrLen) != 0)
+ THROW_LAST_SYS_ERROR_WSA(L"getsockname");
- if (addr.ss_family != AF_INET &&
- addr.ss_family != AF_INET6)
- throw SysError(L"getsockname: unknown protocol family (" + numberTo<std::wstring>(addr.ss_family) + L")");
+ if (addr.ss_family != AF_INET &&
+ addr.ss_family != AF_INET6)
+ throw SysError(L"getsockname: unknown protocol family (" + numberTo<std::wstring>(addr.ss_family) + L")");
-const int port = ntohs(reinterpret_cast<const sockaddr_in&>(addr).sin_port);
-//the socket is not bound to a specific local IP => inet_ntoa(reinterpret_cast<const sockaddr_in&>(addr).sin_addr) == "0.0.0.0"
-const std::string redirectUrl = "http://127.0.0.1:" + numberTo<std::string>(port);
+ const int port = ntohs(reinterpret_cast<const sockaddr_in&>(addr).sin_port);
+ //the socket is not bound to a specific local IP => inet_ntoa(reinterpret_cast<const sockaddr_in&>(addr).sin_addr) == "0.0.0.0"
+ const std::string redirectUrl = "http://127.0.0.1:" + numberTo<std::string>(port);
-if (::listen(socket, SOMAXCONN) != 0)
- THROW_LAST_SYS_ERROR_WSA(L"listen");
+ if (::listen(socket, SOMAXCONN) != 0)
+ THROW_LAST_SYS_ERROR_WSA(L"listen");
//"A code_verifier is a high-entropy cryptographic random string using the unreserved characters:"
@@ -784,138 +760,135 @@ if (::listen(socket, SOMAXCONN) != 0)
//authenticate Google Drive via browser: https://developers.google.com/identity/protocols/OAuth2InstalledApp#step-2-send-a-request-to-googles-oauth-20-server
const std::string oauthUrl = "https://accounts.google.com/o/oauth2/v2/auth?" + xWwwFormUrlEncode(
-{
- { "client_id", GOOGLE_DRIVE_CLIENT_ID },
- { "redirect_uri", redirectUrl },
- { "response_type", "code" },
- { "scope", "https://www.googleapis.com/auth/drive" },
- { "code_challenge", codeChallenge },
- { "code_challenge_method", "plain" },
- { "login_hint", utfTo<std::string>(googleLoginHint) },
-});
-openWithDefaultApplication(utfTo<Zstring>(oauthUrl)); //throw FileError
-//[!] no need to map to SysError
-
-//process incoming HTTP requests
-for (;;)
-{
-for (;;) //::accept() blocks forever if no client connects (e.g. user just closes the browser window!) => wait for incoming traffic with a time-out via ::select()
{
- if (updateGui) updateGui(); //throw X
-
- fd_set rfd = {};
- FD_ZERO(&rfd);
- FD_SET(socket, &rfd);
- fd_set* readfds = &rfd;
-
- struct ::timeval tv = {};
- tv.tv_usec = static_cast<long>(100 /*ms*/) * 1000;
-
- //WSAPoll broken, even ::poll() on OS X? https://daniel.haxx.se/blog/2012/10/10/wsapoll-is-broken/
- //perf: no significant difference compared to ::WSAPoll()
- const int rc = ::select(socket + 1, readfds, nullptr /*writefds*/, nullptr /*errorfds*/, &tv);
- if (rc < 0)
- THROW_LAST_SYS_ERROR_WSA(L"select");
- if (rc != 0)
- break;
- //else: time-out!
+ { "client_id", GOOGLE_DRIVE_CLIENT_ID },
+ { "redirect_uri", redirectUrl },
+ { "response_type", "code" },
+ { "scope", "https://www.googleapis.com/auth/drive" },
+ { "code_challenge", codeChallenge },
+ { "code_challenge_method", "plain" },
+ { "login_hint", utfTo<std::string>(googleLoginHint) },
+ });
+ try
+ {
+ openWithDefaultApplication(utfTo<Zstring>(oauthUrl)); //throw FileError
}
- //potential race! if the connection is gone right after ::select() and before ::accept(), latter will hang
- const SocketType clientSocket = ::accept(socket, //SOCKET s,
- nullptr, //sockaddr *addr,
- nullptr); //int *addrlen
- if (clientSocket == invalidSocket)
- THROW_LAST_SYS_ERROR_WSA(L"accept");
+ catch (const FileError& e) { throw SysError(e.toString()); } //errors should be further enriched by context info => SysError
- //receive first line of HTTP request
- std::string reqLine;
+ //process incoming HTTP requests
for (;;)
{
- const size_t blockSize = 64 * 1024;
- reqLine.resize(reqLine.size() + blockSize);
- const size_t bytesReceived = tryReadSocket(clientSocket, &*(reqLine.end() - blockSize), blockSize); //throw SysError
- reqLine.resize(reqLine.size() - blockSize + bytesReceived); //caveat: unsigned arithmetics
-
- if (contains(reqLine, "\r\n"))
+ for (;;) //::accept() blocks forever if no client connects (e.g. user just closes the browser window!) => wait for incoming traffic with a time-out via ::select()
{
- reqLine = beforeFirst(reqLine, "\r\n", IF_MISSING_RETURN_NONE);
- break;
- }
- if (bytesReceived == 0 || reqLine.size() >= 100000 /*bogus line length*/)
- break;
- }
+ if (updateGui) updateGui(); //throw X
- //get OAuth2.0 authorization result from Google, either:
- std::string code;
- std::string error;
+ fd_set rfd = {};
+ FD_ZERO(&rfd);
+ FD_SET(socket, &rfd);
+ fd_set* readfds = &rfd;
- //parse header; e.g.: GET http://127.0.0.1:62054/?code=4/ZgBRsB9k68sFzc1Pz1q0__Kh17QK1oOmetySrGiSliXt6hZtTLUlYzm70uElNTH9vt1OqUMzJVeFfplMsYsn4uI HTTP/1.1
- const std::vector<std::string> statusItems = split(reqLine, ' ', SplitType::ALLOW_EMPTY); //Method SP Request-URI SP HTTP-Version CRLF
+ struct ::timeval tv = {};
+ tv.tv_usec = static_cast<long>(100 /*ms*/) * 1000;
- if (statusItems.size() == 3 && statusItems[0] == "GET" && startsWith(statusItems[2], "HTTP/"))
- {
- for (const auto& [name, value] : xWwwFormUrlDecode(afterFirst(statusItems[1], "?", IF_MISSING_RETURN_NONE)))
- if (name == "code")
- code = value;
- else if (name == "error")
- error = value; //e.g. "access_denied" => no more detailed error info available :(
- } //"add explicit braces to avoid dangling else [-Wdangling-else]"
+ //WSAPoll broken, even ::poll() on OS X? https://daniel.haxx.se/blog/2012/10/10/wsapoll-is-broken/
+ //perf: no significant difference compared to ::WSAPoll()
+ const int rc = ::select(socket + 1, readfds, nullptr /*writefds*/, nullptr /*errorfds*/, &tv);
+ if (rc < 0)
+ THROW_LAST_SYS_ERROR_WSA(L"select");
+ if (rc != 0)
+ break;
+ //else: time-out!
+ }
+ //potential race! if the connection is gone right after ::select() and before ::accept(), latter will hang
+ const SocketType clientSocket = ::accept(socket, //SOCKET s,
+ nullptr, //sockaddr *addr,
+ nullptr); //int *addrlen
+ if (clientSocket == invalidSocket)
+ THROW_LAST_SYS_ERROR_WSA(L"accept");
+
+ //receive first line of HTTP request
+ std::string reqLine;
+ for (;;)
+ {
+ const size_t blockSize = 64 * 1024;
+ reqLine.resize(reqLine.size() + blockSize);
+ const size_t bytesReceived = tryReadSocket(clientSocket, &*(reqLine.end() - blockSize), blockSize); //throw SysError
+ reqLine.resize(reqLine.size() - blockSize + bytesReceived); //caveat: unsigned arithmetics
+
+ if (contains(reqLine, "\r\n"))
+ {
+ reqLine = beforeFirst(reqLine, "\r\n", IF_MISSING_RETURN_NONE);
+ break;
+ }
+ if (bytesReceived == 0 || reqLine.size() >= 100000 /*bogus line length*/)
+ break;
+ }
- std::optional<std::variant<GoogleAccessInfo, FileError>> authResult;
+ //get OAuth2.0 authorization result from Google, either:
+ std::string code;
+ std::string error;
- //send HTTP response; https://www.w3.org/Protocols/HTTP/1.0/spec.html#Request-Line
- std::string httpResponse;
- if (code.empty() && error.empty()) //parsing error or unrelated HTTP request
- httpResponse = "HTTP/1.0 400 Bad Request" "\r\n" "\r\n" "400 Bad Request\n" + reqLine;
- else
- {
- std::string htmlMsg = htmlMessageTemplate;
- try
+ //parse header; e.g.: GET http://127.0.0.1:62054/?code=4/ZgBRsB9k68sFzc1Pz1q0__Kh17QK1oOmetySrGiSliXt6hZtTLUlYzm70uElNTH9vt1OqUMzJVeFfplMsYsn4uI HTTP/1.1
+ const std::vector<std::string> statusItems = split(reqLine, ' ', SplitType::ALLOW_EMPTY); //Method SP Request-URI SP HTTP-Version CRLF
+
+ if (statusItems.size() == 3 && statusItems[0] == "GET" && startsWith(statusItems[2], "HTTP/"))
{
- if (!error.empty())
- throw FileError(replaceCpy(_("Unable to connect to %x."), L"%x", L"Google Drive"),
- replaceCpy(_("Error Code %x"), L"%x", + L"\"" + utfTo<std::wstring>(error) + L"\""));
+ for (const auto& [name, value] : xWwwFormUrlDecode(afterFirst(statusItems[1], "?", IF_MISSING_RETURN_NONE)))
+ if (name == "code")
+ code = value;
+ else if (name == "error")
+ error = value; //e.g. "access_denied" => no more detailed error info available :(
+ } //"add explicit braces to avoid dangling else [-Wdangling-else]"
- //do as many login-related tasks as possible while we have the browser as an error output device!
- //see AFS::connectNetworkFolder() => errors will be lost after time out in dir_exist_async.h!
- authResult = googleDriveExchangeAuthCode({ code, redirectUrl, codeChallenge }); //throw FileError
- replace(htmlMsg, "TITLE_PLACEHOLDER", utfTo<std::string>(_("Authentication completed.")));
- replace(htmlMsg, "MESSAGE_PLACEHOLDER", utfTo<std::string>(_("You may close this page now and continue with FreeFileSync.")));
- }
- catch (const FileError& e)
+ std::optional<std::variant<GoogleAccessInfo, SysError>> authResult;
+
+ //send HTTP response; https://www.w3.org/Protocols/HTTP/1.0/spec.html#Request-Line
+ std::string httpResponse;
+ if (code.empty() && error.empty()) //parsing error or unrelated HTTP request
+ httpResponse = "HTTP/1.0 400 Bad Request" "\r\n" "\r\n" "400 Bad Request\n" + reqLine;
+ else
{
- authResult = e;
- replace(htmlMsg, "TITLE_PLACEHOLDER", utfTo<std::string>(_("Authentication failed.")));
- replace(htmlMsg, "MESSAGE_PLACEHOLDER", utfTo<std::string>(e.toString()));
+ std::string htmlMsg = htmlMessageTemplate;
+ try
+ {
+ if (!error.empty())
+ throw SysError(replaceCpy(_("Error Code %x"), L"%x", + L"\"" + utfTo<std::wstring>(error) + L"\""));
+
+ //do as many login-related tasks as possible while we have the browser as an error output device!
+ //see AFS::connectNetworkFolder() => errors will be lost after time out in dir_exist_async.h!
+ authResult = googleDriveExchangeAuthCode({ code, redirectUrl, codeChallenge }); //throw SysError
+ replace(htmlMsg, "TITLE_PLACEHOLDER", utfTo<std::string>(_("Authentication completed.")));
+ replace(htmlMsg, "MESSAGE_PLACEHOLDER", utfTo<std::string>(_("You may close this page now and continue with FreeFileSync.")));
+ }
+ catch (const SysError& e)
+ {
+ authResult = e;
+ replace(htmlMsg, "TITLE_PLACEHOLDER", utfTo<std::string>(_("Authentication failed.")));
+ replace(htmlMsg, "MESSAGE_PLACEHOLDER", utfTo<std::string>(replaceCpy(_("Unable to connect to %x."), L"%x", L"Google Drive") + L"\n\n" + e.toString()));
+ }
+ httpResponse = "HTTP/1.0 200 OK" "\r\n"
+ "Content-Type: text/html" "\r\n"
+ "Content-Length: " + numberTo<std::string>(strLength(htmlMsg)) + "\r\n"
+ "\r\n" + htmlMsg;
}
- httpResponse = "HTTP/1.0 200 OK" "\r\n"
- "Content-Type: text/html" "\r\n"
- "Content-Length: " + numberTo<std::string>(strLength(htmlMsg)) + "\r\n"
- "\r\n" + htmlMsg;
- }
- for (size_t bytesToSend = httpResponse.size(); bytesToSend > 0;)
- bytesToSend -= tryWriteSocket(clientSocket, &*(httpResponse.end() - bytesToSend), bytesToSend); //throw SysError
+ for (size_t bytesToSend = httpResponse.size(); bytesToSend > 0;)
+ bytesToSend -= tryWriteSocket(clientSocket, &*(httpResponse.end() - bytesToSend), bytesToSend); //throw SysError
- shutdownSocketSend(clientSocket); //throw SysError
- //---------------------------------------------------------------
+ shutdownSocketSend(clientSocket); //throw SysError
+ //---------------------------------------------------------------
- if (authResult)
- {
- if (const FileError* e = std::get_if<FileError>(&*authResult))
- throw *e;
- return std::get<GoogleAccessInfo>(*authResult);
+ if (authResult)
+ {
+ if (const SysError* e = std::get_if<SysError>(&*authResult))
+ throw *e;
+ return std::get<GoogleAccessInfo>(*authResult);
+ }
}
}
-}
-catch (const SysError& e)
-{
- throw FileError(replaceCpy(_("Unable to connect to %x."), L"%x", L"Google Drive"), e.toString());
-}
-}
-GoogleAccessToken refreshAccessToGoogleDrive(const std::string& refreshToken, const Zstring& googleUserEmail) //throw FileError
+GoogleAccessToken refreshAccessToGoogleDrive(const std::string& refreshToken) //throw SysError
{
//https://developers.google.com/identity/protocols/OAuth2InstalledApp#offline
const std::string postBuf = xWwwFormUrlEncode(
@@ -927,7 +900,7 @@ GoogleAccessToken refreshAccessToGoogleDrive(const std::string& refreshToken, co
});
std::string response;
- googleHttpsRequest("/oauth2/v4/token", {} /*extraHeaders*/, { { CURLOPT_POSTFIELDS, postBuf.c_str() } }, //throw FileError
+ googleHttpsRequest("/oauth2/v4/token", {} /*extraHeaders*/, { { CURLOPT_POSTFIELDS, postBuf.c_str() } }, //throw SysError
[&](const void* buffer, size_t bytesToWrite) { response.append(static_cast<const char*>(buffer), bytesToWrite); }, nullptr /*readRequest*/);
JsonValue jresponse;
@@ -937,39 +910,38 @@ GoogleAccessToken refreshAccessToGoogleDrive(const std::string& refreshToken, co
const std::optional<std::string> accessToken = getPrimitiveFromJsonObject(jresponse, "access_token");
const std::optional<std::string> expiresIn = getPrimitiveFromJsonObject(jresponse, "expires_in"); //e.g. 3600 seconds
if (!accessToken || !expiresIn)
- throw FileError(replaceCpy(_("Unable to connect to %x."), L"%x", fmtPath(getGoogleDisplayPath({ googleUserEmail, AfsPath() }))), formatGoogleErrorRaw(response));
+ throw SysError(formatGoogleErrorRaw(response));
return { *accessToken, std::time(nullptr) + stringTo<time_t>(*expiresIn) };
}
-void revokeAccessToGoogleDrive(const std::string& accessToken, const Zstring& googleUserEmail) //throw FileError
+void revokeAccessToGoogleDrive(const std::string& accessToken, const Zstring& googleUserEmail) //throw SysError
{
//https://developers.google.com/identity/protocols/OAuth2InstalledApp#tokenrevoke
const std::shared_ptr<HttpSessionManager> mgr = httpSessionManager.get();
if (!mgr)
- throw FileError(replaceCpy(_("Unable to access %x."), L"%x", fmtPath(getGoogleDisplayPath({ googleUserEmail, AfsPath() }))),
- L"Function call not allowed during process init/shutdown.");
+ throw SysError(L"revokeAccessToGoogleDrive() Function call not allowed during process init/shutdown.");
HttpSession::HttpResult httpResult;
std::string response;
- mgr->access(HttpSessionId(Zstr("accounts.google.com")), [&](HttpSession& session) //throw FileError
+ mgr->access(HttpSessionId(Zstr("accounts.google.com")), [&](HttpSession& session) //throw SysError
{
httpResult = session.perform("/o/oauth2/revoke?token=" + accessToken, { "Content-Type: application/x-www-form-urlencoded" }, {} /*extraOptions*/,
- [&](const void* buffer, size_t bytesToWrite) { response.append(static_cast<const char*>(buffer), bytesToWrite); }, nullptr /*readRequest*/); //throw FileError
+ [&](const void* buffer, size_t bytesToWrite) { response.append(static_cast<const char*>(buffer), bytesToWrite); }, nullptr /*readRequest*/); //throw SysError
});
if (httpResult.statusCode != 200)
- throw FileError(replaceCpy(_("Unable to disconnect from %x."), L"%x", fmtPath(getGoogleDisplayPath({ googleUserEmail, AfsPath() }))), formatGoogleErrorRaw(response));
+ throw SysError(formatGoogleErrorRaw(response));
}
-uint64_t gdriveGetFreeDiskSpace(const std::string& accessToken) //throw FileError, SysError; returns 0 if not available
+uint64_t gdriveGetFreeDiskSpace(const std::string& accessToken) //throw SysError; returns 0 if not available
{
//https://developers.google.com/drive/api/v3/reference/about
std::string response;
- googleHttpsRequest("/drive/v3/about?fields=storageQuota", { "Authorization: Bearer " + accessToken }, {} /*extraOptions*/, //throw FileError
+ googleHttpsRequest("/drive/v3/about?fields=storageQuota", { "Authorization: Bearer " + accessToken }, {} /*extraOptions*/, //throw SysError
[&](const void* buffer, size_t bytesToWrite) { response.append(static_cast<const char*>(buffer), bytesToWrite); }, nullptr /*readRequest*/);
JsonValue jresponse;
@@ -1018,15 +990,12 @@ struct GoogleFileItem
std::string itemId;
GoogleItemDetails details;
};
-std::vector<GoogleFileItem> readFolderContent(const std::string& folderId, //throw FileError
- const std::string& accessToken, const GdrivePath& gdrivePath)
+std::vector<GoogleFileItem> readFolderContent(const std::string& folderId, const std::string& accessToken) //throw SysError
{
-
warn_static("perf: trashed=false and ('114231411234' in parents or '123123' in parents)")
//https://developers.google.com/drive/api/v3/reference/files/list
std::vector<GoogleFileItem> childItems;
- try
{
std::optional<std::string> nextPageToken;
do
@@ -1044,7 +1013,7 @@ std::vector<GoogleFileItem> readFolderContent(const std::string& folderId, //thr
queryParams += '&' + xWwwFormUrlEncode({ { "pageToken", *nextPageToken } });
std::string response;
- googleHttpsRequest("/drive/v3/files?" + queryParams, { "Authorization: Bearer " + accessToken }, {} /*extraOptions*/, //throw FileError
+ googleHttpsRequest("/drive/v3/files?" + queryParams, { "Authorization: Bearer " + accessToken }, {} /*extraOptions*/, //throw SysError
[&](const void* buffer, size_t bytesToWrite) { response.append(static_cast<const char*>(buffer), bytesToWrite); }, nullptr /*readRequest*/);
JsonValue jresponse;
@@ -1091,8 +1060,6 @@ std::vector<GoogleFileItem> readFolderContent(const std::string& folderId, //thr
}
while (nextPageToken);
}
- catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot read directory %x."), L"%x", fmtPath(getGoogleDisplayPath(gdrivePath))), e.toString()); }
-
return childItems;
}
@@ -1107,107 +1074,103 @@ struct ChangesDelta
std::string newStartPageToken;
std::vector<ChangeItem> changes;
};
-ChangesDelta getChangesDelta(const std::string& startPageToken, //throw FileError
- const std::string& accessToken, const Zstring& googleUserEmail)
+ChangesDelta getChangesDelta(const std::string& startPageToken, const std::string& accessToken) //throw SysError
{
- try //https://developers.google.com/drive/api/v3/reference/changes/list
+ //https://developers.google.com/drive/api/v3/reference/changes/list
+ ChangesDelta delta;
+ std::optional<std::string> nextPageToken = startPageToken;
+ for (;;)
{
- ChangesDelta delta;
- std::optional<std::string> nextPageToken = startPageToken;
- for (;;)
+ std::string queryParams = xWwwFormUrlEncode(
{
- std::string queryParams = xWwwFormUrlEncode(
- {
- { "pageToken", *nextPageToken },
- { "pageSize", "1000" }, //"[1, 1000] Default: 100"
- { "restrictToMyDrive", "true" }, //important! otherwise we won't get "removed: true" (because file may still be accessible from other Corpora)
- { "spaces", "drive" },
- { "fields", "kind,nextPageToken,newStartPageToken,changes(kind,removed,fileId,file(name,mimeType,size,modifiedTime,parents,trashed))" },
- });
+ { "pageToken", *nextPageToken },
+ { "pageSize", "1000" }, //"[1, 1000] Default: 100"
+ { "restrictToMyDrive", "true" }, //important! otherwise we won't get "removed: true" (because file may still be accessible from other Corpora)
+ { "spaces", "drive" },
+ { "fields", "kind,nextPageToken,newStartPageToken,changes(kind,removed,fileId,file(name,mimeType,size,modifiedTime,parents,trashed))" },
+ });
- std::string response;
- googleHttpsRequest("/drive/v3/changes?" + queryParams, { "Authorization: Bearer " + accessToken }, {} /*extraOptions*/, //throw FileError
- [&](const void* buffer, size_t bytesToWrite) { response.append(static_cast<const char*>(buffer), bytesToWrite); }, nullptr /*readRequest*/);
+ std::string response;
+ googleHttpsRequest("/drive/v3/changes?" + queryParams, { "Authorization: Bearer " + accessToken }, {} /*extraOptions*/, //throw SysError
+ [&](const void* buffer, size_t bytesToWrite) { response.append(static_cast<const char*>(buffer), bytesToWrite); }, nullptr /*readRequest*/);
- JsonValue jresponse;
- try { jresponse = parseJson(response); }
- catch (JsonParsingError&) {}
+ JsonValue jresponse;
+ try { jresponse = parseJson(response); }
+ catch (JsonParsingError&) {}
+
+ /**/ nextPageToken = getPrimitiveFromJsonObject(jresponse, "nextPageToken");
+ const std::optional<std::string> newStartPageToken = getPrimitiveFromJsonObject(jresponse, "newStartPageToken");
+ const std::optional<std::string> listKind = getPrimitiveFromJsonObject(jresponse, "kind");
+ const JsonValue* changes = getChildFromJsonObject (jresponse, "changes");
- /**/ nextPageToken = getPrimitiveFromJsonObject(jresponse, "nextPageToken");
- const std::optional<std::string> newStartPageToken = getPrimitiveFromJsonObject(jresponse, "newStartPageToken");
- const std::optional<std::string> listKind = getPrimitiveFromJsonObject(jresponse, "kind");
- const JsonValue* changes = getChildFromJsonObject (jresponse, "changes");
+ if (!!nextPageToken == !!newStartPageToken || //there can be only one
+ !listKind || *listKind != "drive#changeList" ||
+ !changes || changes->type != JsonValue::Type::array)
+ throw SysError(formatGoogleErrorRaw(response));
- if (!!nextPageToken == !!newStartPageToken || //there can be only one
- !listKind || *listKind != "drive#changeList" ||
- !changes || changes->type != JsonValue::Type::array)
+ for (const auto& childVal : changes->arrayVal)
+ {
+ const std::optional<std::string> kind = getPrimitiveFromJsonObject(*childVal, "kind");
+ const std::optional<std::string> removed = getPrimitiveFromJsonObject(*childVal, "removed");
+ const std::optional<std::string> itemId = getPrimitiveFromJsonObject(*childVal, "fileId");
+ const JsonValue* file = getChildFromJsonObject (*childVal, "file");
+ if (!kind || *kind != "drive#change" || !removed || !itemId)
throw SysError(formatGoogleErrorRaw(response));
- for (const auto& childVal : changes->arrayVal)
+ ChangeItem changeItem;
+ changeItem.itemId = *itemId;
+ if (*removed != "true")
{
- const std::optional<std::string> kind = getPrimitiveFromJsonObject(*childVal, "kind");
- const std::optional<std::string> removed = getPrimitiveFromJsonObject(*childVal, "removed");
- const std::optional<std::string> itemId = getPrimitiveFromJsonObject(*childVal, "fileId");
- const JsonValue* file = getChildFromJsonObject (*childVal, "file");
- if (!kind || *kind != "drive#change" || !removed || !itemId)
+ if (!file)
throw SysError(formatGoogleErrorRaw(response));
- ChangeItem changeItem;
- changeItem.itemId = *itemId;
- if (*removed != "true")
+ const std::optional<std::string> itemName = getPrimitiveFromJsonObject(*file, "name");
+ const std::optional<std::string> mimeType = getPrimitiveFromJsonObject(*file, "mimeType");
+ const std::optional<std::string> size = getPrimitiveFromJsonObject(*file, "size");
+ const std::optional<std::string> modifiedTime = getPrimitiveFromJsonObject(*file, "modifiedTime");
+ const std::optional<std::string> trashed = getPrimitiveFromJsonObject(*file, "trashed");
+ const JsonValue* parents = getChildFromJsonObject (*file, "parents");
+ if (!itemName || !mimeType || !modifiedTime || !trashed || !parents)
+ throw SysError(formatGoogleErrorRaw(response));
+
+ if (*trashed != "true")
{
- if (!file)
- throw SysError(formatGoogleErrorRaw(response));
+ GoogleItemDetails itemDetails = {};
+ itemDetails.itemName = *itemName;
+ itemDetails.isFolder = *mimeType == googleFolderMimeType;
+ itemDetails.fileSize = size ? stringTo<uint64_t>(*size) : 0; //not available for folders
- const std::optional<std::string> itemName = getPrimitiveFromJsonObject(*file, "name");
- const std::optional<std::string> mimeType = getPrimitiveFromJsonObject(*file, "mimeType");
- const std::optional<std::string> size = getPrimitiveFromJsonObject(*file, "size");
- const std::optional<std::string> modifiedTime = getPrimitiveFromJsonObject(*file, "modifiedTime");
- const std::optional<std::string> trashed = getPrimitiveFromJsonObject(*file, "trashed");
- const JsonValue* parents = getChildFromJsonObject (*file, "parents");
- if (!itemName || !mimeType || !modifiedTime || !trashed || !parents)
- throw SysError(formatGoogleErrorRaw(response));
+ //RFC 3339 date-time: e.g. "2018-09-29T08:39:12.053Z"
+ itemDetails.modTime = utcToTimeT(parseTime("%Y-%m-%dT%H:%M:%S", beforeLast(*modifiedTime, '.', IF_MISSING_RETURN_ALL))); //returns -1 on error
+ if (itemDetails.modTime == -1 || !endsWith(*modifiedTime, 'Z')) //'Z' means "UTC" => it seems Google doesn't use the time-zone offset postfix
+ throw SysError(L"Modification time could not be parsed. (" + utfTo<std::wstring>(*modifiedTime) + L")");
- if (*trashed != "true")
+ for (const auto& parentVal : parents->arrayVal)
{
- GoogleItemDetails itemDetails = {};
- itemDetails.itemName = *itemName;
- itemDetails.isFolder = *mimeType == googleFolderMimeType;
- itemDetails.fileSize = size ? stringTo<uint64_t>(*size) : 0; //not available for folders
-
- //RFC 3339 date-time: e.g. "2018-09-29T08:39:12.053Z"
- itemDetails.modTime = utcToTimeT(parseTime("%Y-%m-%dT%H:%M:%S", beforeLast(*modifiedTime, '.', IF_MISSING_RETURN_ALL))); //returns -1 on error
- if (itemDetails.modTime == -1 || !endsWith(*modifiedTime, 'Z')) //'Z' means "UTC" => it seems Google doesn't use the time-zone offset postfix
- throw SysError(L"Modification time could not be parsed. (" + utfTo<std::wstring>(*modifiedTime) + L")");
-
- for (const auto& parentVal : parents->arrayVal)
- {
- if (parentVal->type != JsonValue::Type::string)
- throw SysError(formatGoogleErrorRaw(response));
- itemDetails.parentIds.push_back(parentVal->primVal);
- }
- changeItem.details = std::move(itemDetails);
+ if (parentVal->type != JsonValue::Type::string)
+ throw SysError(formatGoogleErrorRaw(response));
+ itemDetails.parentIds.push_back(parentVal->primVal);
}
+ changeItem.details = std::move(itemDetails);
}
- delta.changes.push_back(std::move(changeItem));
}
+ delta.changes.push_back(std::move(changeItem));
+ }
- if (!nextPageToken)
- {
- delta.newStartPageToken = *newStartPageToken;
- return delta;
- }
+ if (!nextPageToken)
+ {
+ delta.newStartPageToken = *newStartPageToken;
+ return delta;
}
}
- catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(getGoogleDisplayPath({ googleUserEmail, AfsPath() }))), e.toString()); }
}
-std::string /*startPageToken*/ getChangesCurrentToken(const std::string& accessToken, const Zstring& googleUserEmail) //throw FileError
+std::string /*startPageToken*/ getChangesCurrentToken(const std::string& accessToken) //throw SysError
{
//https://developers.google.com/drive/api/v3/reference/changes/getStartPageToken
std::string response;
- googleHttpsRequest("/drive/v3/changes/startPageToken", { "Authorization: Bearer " + accessToken }, {} /*extraOptions*/, //throw FileError
+ googleHttpsRequest("/drive/v3/changes/startPageToken", { "Authorization: Bearer " + accessToken }, {} /*extraOptions*/, //throw SysError
[&](const void* buffer, size_t bytesToWrite) { response.append(static_cast<const char*>(buffer), bytesToWrite); }, nullptr /*readRequest*/);
JsonValue jresponse;
@@ -1216,7 +1179,7 @@ std::string /*startPageToken*/ getChangesCurrentToken(const std::string& accessT
const std::optional<std::string> startPageToken = getPrimitiveFromJsonObject(jresponse, "startPageToken");
if (!startPageToken)
- throw FileError(replaceCpy(_("Cannot monitor directory %x."), L"%x", fmtPath(getGoogleDisplayPath({ googleUserEmail, AfsPath() }))), formatGoogleErrorRaw(response));
+ throw SysError(formatGoogleErrorRaw(response));
return *startPageToken;
}
@@ -1224,11 +1187,11 @@ std::string /*startPageToken*/ getChangesCurrentToken(const std::string& accessT
//- if item is a folder: deletes recursively!!!
//- even deletes a hardlink with multiple parents => use gdriveUnlinkParent() first
-void gdriveDeleteItem(const std::string& itemId, const std::string& accessToken) //throw FileError, SysError
+void gdriveDeleteItem(const std::string& itemId, const std::string& accessToken) //throw SysError
{
//https://developers.google.com/drive/api/v3/reference/files/delete
std::string response;
- const HttpSession::HttpResult httpResult = googleHttpsRequest("/drive/v3/files/" + itemId, { "Authorization: Bearer " + accessToken }, //throw FileError
+ const HttpSession::HttpResult httpResult = googleHttpsRequest("/drive/v3/files/" + itemId, { "Authorization: Bearer " + accessToken }, //throw SysError
{ { CURLOPT_CUSTOMREQUEST, "DELETE" } },
[&](const void* buffer, size_t bytesToWrite) { response.append(static_cast<const char*>(buffer), bytesToWrite); }, nullptr /*readRequest*/);
@@ -1239,7 +1202,7 @@ void gdriveDeleteItem(const std::string& itemId, const std::string& accessToken)
//item is NOT deleted when last parent is removed: it is just not accessible via the "My Drive" hierarchy but still adds to quota! => use for hard links only!
-void gdriveUnlinkParent(const std::string& itemId, const std::string& parentFolderId, const std::string& accessToken) //throw FileError, SysError
+void gdriveUnlinkParent(const std::string& itemId, const std::string& parentFolderId, const std::string& accessToken) //throw SysError
{
//https://developers.google.com/drive/api/v3/reference/files/update
const std::string queryParams = xWwwFormUrlEncode(
@@ -1248,7 +1211,7 @@ void gdriveUnlinkParent(const std::string& itemId, const std::string& parentFold
{ "fields", "id,parents"}, //for test if operation was successful
});
std::string response;
- googleHttpsRequest("/drive/v3/files/" + itemId + '?' + queryParams, //throw FileError
+ googleHttpsRequest("/drive/v3/files/" + itemId + '?' + queryParams, //throw SysError
{ "Authorization: Bearer " + accessToken, "Content-Type: application/json; charset=UTF-8" },
{ { CURLOPT_CUSTOMREQUEST, "PATCH" }, { CURLOPT_POSTFIELDS, "{}" } },
[&](const void* buffer, size_t bytesToWrite) { response.append(static_cast<const char*>(buffer), bytesToWrite); }, nullptr /*readRequest*/);
@@ -1266,19 +1229,19 @@ void gdriveUnlinkParent(const std::string& itemId, const std::string& parentFold
if (parents->type != JsonValue::Type::array ||
std::any_of(parents->arrayVal.begin(), parents->arrayVal.end(),
[&](const std::unique_ptr<JsonValue>& jval) { return jval->type == JsonValue::Type::string && jval->primVal == parentFolderId; }))
- throw SysError(L"Google Drive internal failure"); //user should never see this...
+ throw SysError(L"gdriveUnlinkParent: Google Drive internal failure"); //user should never see this...
}
//- if item is a folder: trashes recursively!!!
//- a hardlink with multiple parents will be not be accessible anymore via any of its path aliases!
-void gdriveMoveToTrash(const std::string& itemId, const std::string& accessToken) //throw FileError, SysError
+void gdriveMoveToTrash(const std::string& itemId, const std::string& accessToken) //throw SysError
{
//https://developers.google.com/drive/api/v3/reference/files/update
const std::string postBuf = R"({ "trashed": true })";
std::string response;
- googleHttpsRequest("/drive/v3/files/" + itemId + "?fields=trashed", //throw FileError
+ googleHttpsRequest("/drive/v3/files/" + itemId + "?fields=trashed", //throw SysError
{ "Authorization: Bearer " + accessToken, "Content-Type: application/json; charset=UTF-8" },
{ { CURLOPT_CUSTOMREQUEST, "PATCH" }, { CURLOPT_POSTFIELDS, postBuf.c_str() } },
[&](const void* buffer, size_t bytesToWrite) { response.append(static_cast<const char*>(buffer), bytesToWrite); }, nullptr /*readRequest*/);
@@ -1294,7 +1257,7 @@ void gdriveMoveToTrash(const std::string& itemId, const std::string& accessToken
//folder name already existing? will (happily) create duplicate folders => caller must check!
-std::string /*folderId*/ gdriveCreateFolderPlain(const Zstring& folderName, const std::string& parentFolderId, const std::string& accessToken) //throw FileError, SysError
+std::string /*folderId*/ gdriveCreateFolderPlain(const Zstring& folderName, const std::string& parentFolderId, const std::string& accessToken) //throw SysError
{
//https://developers.google.com/drive/api/v3/folder#creating_a_folder
std::string postBuf = "{\n";
@@ -1305,8 +1268,8 @@ std::string /*folderId*/ gdriveCreateFolderPlain(const Zstring& folderName, cons
std::string response;
googleHttpsRequest("/drive/v3/files?fields=id", { "Authorization: Bearer " + accessToken, "Content-Type: application/json; charset=UTF-8" },
- { { CURLOPT_POSTFIELDS, postBuf.c_str() } }, //throw FileError
- [&](const void* buffer, size_t bytesToWrite) { response.append(static_cast<const char*>(buffer), bytesToWrite); }, nullptr /*readRequest*/);
+ { { CURLOPT_POSTFIELDS, postBuf.c_str() } },
+ [&](const void* buffer, size_t bytesToWrite) { response.append(static_cast<const char*>(buffer), bytesToWrite); }, nullptr /*readRequest*/); //throw SysError
JsonValue jresponse;
try { jresponse = parseJson(response); }
@@ -1320,17 +1283,17 @@ std::string /*folderId*/ gdriveCreateFolderPlain(const Zstring& folderName, cons
//target name already existing? will (happily) create duplicate items => caller must check!
-void gdriveMoveAndRenameItem(const std::string& itemId, const std::string& parentIdOld, const std::string& parentIdNew,
- const Zstring& newName, time_t newModTime, const std::string& accessToken) //throw FileError, SysError
+void gdriveMoveAndRenameItem(const std::string& itemId, const std::string& parentIdFrom, const std::string& parentIdTo,
+ const Zstring& newName, time_t newModTime, const std::string& accessToken) //throw SysError
{
//https://developers.google.com/drive/api/v3/folder#moving_files_between_folders
std::string queryParams = "fields=name,parents"; //for test if operation was successful
- if (parentIdOld != parentIdNew)
+ if (parentIdFrom != parentIdTo)
queryParams += '&' + xWwwFormUrlEncode(
{
- { "removeParents", parentIdOld },
- { "addParents", parentIdNew },
+ { "removeParents", parentIdFrom },
+ { "addParents", parentIdTo },
});
//more Google Drive peculiarities: changing the file name changes modifiedTime!!! => workaround:
@@ -1347,7 +1310,7 @@ void gdriveMoveAndRenameItem(const std::string& itemId, const std::string& paren
postBuf += "}";
std::string response;
- googleHttpsRequest("/drive/v3/files/" + itemId + '?' + queryParams, //throw FileError
+ googleHttpsRequest("/drive/v3/files/" + itemId + '?' + queryParams, //throw SysError
{ "Authorization: Bearer " + accessToken, "Content-Type: application/json; charset=UTF-8" },
{ { CURLOPT_CUSTOMREQUEST, "PATCH" }, { CURLOPT_POSTFIELDS, postBuf.c_str() } },
[&](const void* buffer, size_t bytesToWrite) { response.append(static_cast<const char*>(buffer), bytesToWrite); }, nullptr /*readRequest*/);
@@ -1363,52 +1326,45 @@ void gdriveMoveAndRenameItem(const std::string& itemId, const std::string& paren
throw SysError(formatGoogleErrorRaw(response));
if (!std::any_of(parents->arrayVal.begin(), parents->arrayVal.end(),
- [&](const std::unique_ptr<JsonValue>& jval) { return jval->type == JsonValue::Type::string && jval->primVal == parentIdNew; }))
- throw SysError(L"Google Drive internal failure"); //user should never see this...
+ [&](const std::unique_ptr<JsonValue>& jval) { return jval->type == JsonValue::Type::string && jval->primVal == parentIdTo; }))
+ throw SysError(L"gdriveMoveAndRenameItem: Google Drive internal failure"); //user should never see this...
}
#if 0
-void setModTime(const std::string& itemId, time_t modTime, //throw FileError
- const std::string& accessToken, const GdrivePath& gdrivePath)
+void setModTime(const std::string& itemId, time_t modTime, const std::string& accessToken) //throw SysError
{
- try //https://developers.google.com/drive/api/v3/reference/files/update
- {
- //RFC 3339 date-time: e.g. "2018-09-29T08:39:12.053Z"
- const std::string dateTime = formatTime<std::string>("%Y-%m-%dT%H:%M:%S.000Z", getUtcTime(modTime)); //returns empty string on failure
- if (dateTime.empty())
- throw SysError(L"Invalid modification time (time_t: " + numberTo<std::wstring>(modTime) + L")");
+ //https://developers.google.com/drive/api/v3/reference/files/update
+ //RFC 3339 date-time: e.g. "2018-09-29T08:39:12.053Z"
+ const std::string dateTime = formatTime<std::string>("%Y-%m-%dT%H:%M:%S.000Z", getUtcTime(modTime)); //returns empty string on failure
+ if (dateTime.empty())
+ throw SysError(L"Invalid modification time (time_t: " + numberTo<std::wstring>(modTime) + L")");
- const std::string postBuf = R"({ "modifiedTime": ")" + dateTime + "\" }";
+ const std::string postBuf = R"({ "modifiedTime": ")" + dateTime + "\" }";
- std::string response;
- googleHttpsRequest("/drive/v3/files/" + itemId + "?fields=modifiedTime", //throw FileError
- { "Authorization: Bearer " + accessToken, "Content-Type: application/json; charset=UTF-8" },
- { { CURLOPT_CUSTOMREQUEST, "PATCH" }, { CURLOPT_POSTFIELDS, postBuf.c_str() } },
- [&](const void* buffer, size_t bytesToWrite) { response.append(static_cast<const char*>(buffer), bytesToWrite); }, nullptr /*readRequest*/);
+ std::string response;
+ googleHttpsRequest("/drive/v3/files/" + itemId + "?fields=modifiedTime", //throw SysError
+ { "Authorization: Bearer " + accessToken, "Content-Type: application/json; charset=UTF-8" },
+ { { CURLOPT_CUSTOMREQUEST, "PATCH" }, { CURLOPT_POSTFIELDS, postBuf.c_str() } },
+ [&](const void* buffer, size_t bytesToWrite) { response.append(static_cast<const char*>(buffer), bytesToWrite); }, nullptr /*readRequest*/);
- JsonValue jresponse;
- try { jresponse = parseJson(response); /*throw JsonParsingError*/ }
- catch (const JsonParsingError&) {}
+ JsonValue jresponse;
+ try { jresponse = parseJson(response); /*throw JsonParsingError*/ }
+ catch (const JsonParsingError&) {}
- const std::optional<std::string> modifiedTime = getPrimitiveFromJsonObject(jresponse, "modifiedTime");
- if (!modifiedTime || *modifiedTime != dateTime)
- throw SysError(formatGoogleErrorRaw(response));
- }
- catch (const SysError& e)
- {
- throw FileError(replaceCpy(_("Cannot write modification time of %x."), L"%x", fmtPath(getGoogleDisplayPath(gdrivePath))), e.toString());
- }
+ const std::optional<std::string> modifiedTime = getPrimitiveFromJsonObject(jresponse, "modifiedTime");
+ if (!modifiedTime || *modifiedTime != dateTime)
+ throw SysError(formatGoogleErrorRaw(response));
}
#endif
-void gdriveDownloadFile(const std::string& itemId, const std::function<void(const void* buffer, size_t bytesToWrite)>& writeBlock /*throw X*/, //throw FileError, X
- const std::string& accessToken, const GdrivePath& gdrivePath)
+void gdriveDownloadFile(const std::string& itemId, const std::function<void(const void* buffer, size_t bytesToWrite)>& writeBlock /*throw X*/, //throw SysError, X
+ const std::string& accessToken)
{
//https://developers.google.com/drive/api/v3/manage-downloads
std::string response;
- const HttpSession::HttpResult httpResult = googleHttpsRequest("/drive/v3/files/" + itemId + "?alt=media", //throw FileError, X
+ const HttpSession::HttpResult httpResult = googleHttpsRequest("/drive/v3/files/" + itemId + "?alt=media", //throw SysError, X
{ "Authorization: Bearer " + accessToken }, {} /*extraOptions*/,
[&](const void* buffer, size_t bytesToWrite)
{
@@ -1418,7 +1374,7 @@ void gdriveDownloadFile(const std::string& itemId, const std::function<void(cons
}, nullptr /*readRequest*/);
if (httpResult.statusCode / 100 != 2)
- throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(getGoogleDisplayPath(gdrivePath))), formatGoogleErrorRaw(response));
+ throw SysError(formatGoogleErrorRaw(response));
}
@@ -1426,9 +1382,9 @@ void gdriveDownloadFile(const std::string& itemId, const std::function<void(cons
//file name already existing? => duplicate file created!
//note: Google Drive upload is already transactional!
//upload "small files" (5 MB or less; enforced by Google?) in a single round-trip
-std::string /*itemId*/ gdriveUploadSmallFile(const Zstring& fileName, const std::string& parentFolderId, uint64_t streamSize, std::optional<time_t> modTime, //throw FileError, X
+std::string /*itemId*/ gdriveUploadSmallFile(const Zstring& fileName, const std::string& parentFolderId, uint64_t streamSize, std::optional<time_t> modTime, //throw SysError, X
const std::function<size_t(void* buffer, size_t bytesToRead)>& readBlock /*throw X*/, //returning 0 signals EOF: Posix read() semantics
- const std::string& accessToken, const GdrivePath& gdrivePath)
+ const std::string& accessToken)
{
//https://developers.google.com/drive/api/v3/folder#inserting_a_file_in_a_folder
//https://developers.google.com/drive/api/v3/multipart-upload
@@ -1498,41 +1454,34 @@ std::string /*itemId*/ gdriveUploadSmallFile(const Zstring& fileName, const std:
TODO:
gzip-compress HTTP request body!
- try
+ std::string response;
+ const HttpSession::HttpResult httpResult = googleHttpsRequest("/upload/drive/v3/files?uploadType=multipart", //throw SysError, X
{
- std::string response;
- const HttpSession::HttpResult httpResult = googleHttpsRequest("/upload/drive/v3/files?uploadType=multipart", //throw FileError, X
- {
- "Authorization: Bearer " + accessToken,
- "Content-Type: multipart/related; boundary=" + boundaryString,
- "Content-Length: " + numberTo<std::string>(postBufHead.size() + streamSize + postBufTail.size())
- },
- { { CURLOPT_POST, 1 } }, //otherwise HttpSession::perform() will PUT
- [&](const void* buffer, size_t bytesToWrite) { response.append(static_cast<const char*>(buffer), bytesToWrite); }, readMultipartBlock );
+ "Authorization: Bearer " + accessToken,
+ "Content-Type: multipart/related; boundary=" + boundaryString,
+ "Content-Length: " + numberTo<std::string>(postBufHead.size() + streamSize + postBufTail.size())
+ },
+ { { CURLOPT_POST, 1 } }, //otherwise HttpSession::perform() will PUT
+ [&](const void* buffer, size_t bytesToWrite) { response.append(static_cast<const char*>(buffer), bytesToWrite); }, readMultipartBlock );
- JsonValue jresponse;
- try { jresponse = parseJson(response); }
- catch (JsonParsingError&) {}
+ JsonValue jresponse;
+ try { jresponse = parseJson(response); }
+ catch (JsonParsingError&) {}
- const std::optional<std::string> itemId = getPrimitiveFromJsonObject(jresponse, "id");
- if (!itemId)
- throw SysError(formatGoogleErrorRaw(response));
+ const std::optional<std::string> itemId = getPrimitiveFromJsonObject(jresponse, "id");
+ if (!itemId)
+ throw SysError(formatGoogleErrorRaw(response));
- return *itemId;
- }
- catch (const SysError& e)
- {
- throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getGoogleDisplayPath(gdrivePath))), e.toString());
- }
+ return *itemId;
}
#endif
//file name already existing? => duplicate file created!
//note: Google Drive upload is already transactional!
-std::string /*itemId*/ gdriveUploadFile(const Zstring& fileName, const std::string& parentFolderId, std::optional<time_t> modTime, //throw FileError, X
+std::string /*itemId*/ gdriveUploadFile(const Zstring& fileName, const std::string& parentFolderId, std::optional<time_t> modTime, //throw SysError, X
const std::function<size_t(void* buffer, size_t bytesToRead)>& readBlock /*throw X*/, //returning 0 signals EOF: Posix read() semantics
- const std::string& accessToken, const GdrivePath& gdrivePath)
+ const std::string& accessToken)
{
//https://developers.google.com/drive/api/v3/folder#inserting_a_file_in_a_folder
//https://developers.google.com/drive/api/v3/resumable-upload
@@ -1578,7 +1527,7 @@ std::string /*itemId*/ gdriveUploadFile(const Zstring& fileName, const std::stri
};
std::string response;
- const HttpSession::HttpResult httpResult = googleHttpsRequest("/upload/drive/v3/files?uploadType=resumable", //throw FileError
+ const HttpSession::HttpResult httpResult = googleHttpsRequest("/upload/drive/v3/files?uploadType=resumable", //throw SysError
{ "Authorization: Bearer " + accessToken, "Content-Type: application/json; charset=UTF-8" },
{ { CURLOPT_POSTFIELDS, postBuf.c_str() }, { CURLOPT_HEADERDATA, &onBytesReceived }, { CURLOPT_HEADERFUNCTION, onBytesReceivedWrapper } },
[&](const void* buffer, size_t bytesToWrite) { response.append(static_cast<const char*>(buffer), bytesToWrite); }, nullptr /*readRequest*/);
@@ -1597,11 +1546,11 @@ std::string /*itemId*/ gdriveUploadFile(const Zstring& fileName, const std::stri
//not officially documented, but Google Drive supports compressed file upload when "Content-Encoding: gzip" is set! :)))
InputStreamAsGzip gzipStream(readBlock); //throw ZlibInternalError
- auto readBlockAsGzip = [&](void* buffer, size_t bytesToRead) { return gzipStream.read(buffer, bytesToRead); }; //throw ZlibInternalError, X;
+ auto readBlockAsGzip = [&](void* buffer, size_t bytesToRead) { return gzipStream.read(buffer, bytesToRead); }; //throw ZlibInternalError, X
//returns "bytesToRead" bytes unless end of stream! => fits into "0 signals EOF: Posix read() semantics"
std::string response;
- googleHttpsRequest(uploadUrlRelative, { "Content-Encoding: gzip" }, {} /*extraOptions*/, //throw FileError, X
+ googleHttpsRequest(uploadUrlRelative, { "Content-Encoding: gzip" }, {} /*extraOptions*/, //throw SysError, ZlibInternalError, X
[&](const void* buffer, size_t bytesToWrite) { response.append(static_cast<const char*>(buffer), bytesToWrite); }, readBlockAsGzip);
JsonValue jresponse;
@@ -1616,21 +1565,17 @@ std::string /*itemId*/ gdriveUploadFile(const Zstring& fileName, const std::stri
}
catch (ZlibInternalError&)
{
- throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getGoogleDisplayPath(gdrivePath))), L"zlib internal error");
- }
- catch (const SysError& e)
- {
- throw FileError(replaceCpy(_("Cannot write file %x."), L"%x", fmtPath(getGoogleDisplayPath(gdrivePath))), e.toString());
+ throw SysError(L"zlib internal error");
}
}
//instead of the "root" alias Google uses an actual ID in file metadata
-std::string /*itemId*/ getRootItemId(const std::string& accessToken, const Zstring& googleUserEmail) //throw FileError
+std::string /*itemId*/ getRootItemId(const std::string& accessToken) //throw SysError
{
//https://developers.google.com/drive/api/v3/reference/files/get
std::string response;
- googleHttpsRequest("/drive/v3/files/root?fields=id", { "Authorization: Bearer " + accessToken }, {} /*extraOptions*/, //throw FileError
+ googleHttpsRequest("/drive/v3/files/root?fields=id", { "Authorization: Bearer " + accessToken }, {} /*extraOptions*/, //throw SysError
[&](const void* buffer, size_t bytesToWrite) { response.append(static_cast<const char*>(buffer), bytesToWrite); }, nullptr /*readRequest*/);
JsonValue jresponse;
@@ -1639,7 +1584,7 @@ std::string /*itemId*/ getRootItemId(const std::string& accessToken, const Zstri
const std::optional<std::string> itemId = getPrimitiveFromJsonObject(jresponse, "id");
if (!itemId)
- throw FileError(replaceCpy(_("Cannot read file attributes of %x."), L"%x", fmtPath(getGoogleDisplayPath({ googleUserEmail, AfsPath() }))), formatGoogleErrorRaw(response));
+ throw SysError(formatGoogleErrorRaw(response));
return *itemId;
}
@@ -1669,10 +1614,10 @@ public:
writeContainer(stream, utfTo<std::string>(accessInfo_.userInfo.email));
}
- std::string getAccessToken() //throw FileError
+ std::string getAccessToken() //throw SysError
{
if (accessInfo_.accessToken.validUntil <= std::time(nullptr) + std::chrono::seconds(HTTP_SESSION_ACCESS_TIME_OUT).count() + 5 /*some leeway*/) //expired/will expire
- accessInfo_.accessToken = refreshAccessToGoogleDrive(accessInfo_.refreshToken, accessInfo_.userInfo.email); //throw FileError
+ accessInfo_.accessToken = refreshAccessToGoogleDrive(accessInfo_.refreshToken); //throw SysError
assert(accessInfo_.accessToken.validUntil > std::time(nullptr) + std::chrono::seconds(HTTP_SESSION_ACCESS_TIME_OUT).count());
return accessInfo_.accessToken.value;
@@ -1702,11 +1647,10 @@ class GooglePersistentSessions;
class GoogleFileState //per-user-session! => serialize access (perf: amortized fully buffered!)
{
public:
- GoogleFileState(GoogleAccessBuffer& accessBuf) : accessBuf_(accessBuf) //throw FileError
- {
- lastSyncToken_ = getChangesCurrentToken(accessBuf_.getAccessToken(), accessBuf_.getUserEmail()); //throw FileError
- rootId_ = getRootItemId (accessBuf_.getAccessToken(), accessBuf_.getUserEmail()); //throw FileError
- }
+ GoogleFileState(GoogleAccessBuffer& accessBuf) : //throw SysError
+ lastSyncToken_(getChangesCurrentToken(accessBuf.getAccessToken())), //
+ rootId_ (getRootItemId (accessBuf.getAccessToken())), //throw SysError
+ accessBuf_(accessBuf) {} //
GoogleFileState(MemoryStreamIn<ByteArray>& stream, GoogleAccessBuffer& accessBuf) : accessBuf_(accessBuf) //throw UnexpectedEndOfStreamError
{
@@ -1880,7 +1824,7 @@ public:
markSyncDue();
}
- void notifyMoveAndRename(const FileStateDelta& stateDelta, const std::string& itemId, const std::string& parentIdOld, const std::string& parentIdNew, const Zstring& newName)
+ void notifyMoveAndRename(const FileStateDelta& stateDelta, const std::string& itemId, const std::string& parentIdFrom, const std::string& parentIdTo, const Zstring& newName)
{
auto it = itemDetails_.find(itemId);
if (it != itemDetails_.end())
@@ -1888,8 +1832,8 @@ public:
GoogleItemDetails detailsNew = it->second;
detailsNew.itemName = utfTo<std::string>(newName);
- eraseIf(detailsNew.parentIds, [&](const std::string& id) { return id == parentIdOld || id == parentIdNew; }); //
- detailsNew.parentIds.push_back(parentIdNew); //not a duplicate
+ eraseIf(detailsNew.parentIds, [&](const std::string& id) { return id == parentIdFrom || id == parentIdTo; }); //
+ detailsNew.parentIds.push_back(parentIdTo); //not a duplicate
notifyItemUpdate(stateDelta, itemId, detailsNew);
}
@@ -1930,9 +1874,9 @@ private:
void markSyncDue() { lastSyncTime_ = std::chrono::steady_clock::now() - GOOGLE_DRIVE_SYNC_INTERVAL; }
- void syncWithGoogle() //throw FileError
+ void syncWithGoogle() //throw SysError
{
- const ChangesDelta delta = getChangesDelta(lastSyncToken_, accessBuf_.getAccessToken(), accessBuf_.getUserEmail()); //throw FileError
+ const ChangesDelta delta = getChangesDelta(lastSyncToken_, accessBuf_.getAccessToken()); //throw SysError
for (const ChangeItem& item : delta.changes)
updateItemState(item.itemId, item.details);
@@ -1954,11 +1898,7 @@ private:
childItems = &(itKnown->second.childItems);
else
{
- try
- {
- notifyFolderContent(registerFileStateDelta(), folderId, readFolderContent(folderId, accessBuf_.getAccessToken(), { accessBuf_.getUserEmail(), folderPath })); //throw FileError
- }
- catch (const FileError& e) { throw SysError(e.toString()); } //path-resolution errors should be further enriched by context info => SysError
+ notifyFolderContent(registerFileStateDelta(), folderId, readFolderContent(folderId, accessBuf_.getAccessToken())); //throw SysError
if (!folderContents_[folderId].isKnownFolder)
throw std::logic_error("Contract violation! " + std::string(__FILE__) + ":" + numberTo<std::string>(__LINE__));
@@ -2130,7 +2070,7 @@ public:
saveSession(dbFilePathTmp, *holder.session); //throw FileError
- moveAndRenameItem(dbFilePathTmp, dbFilePath, true /*replaceExisting*/); //throw FileError, ErrorDifferentVolume, (ErrorTargetExisting)
+ moveAndRenameItem(dbFilePathTmp, dbFilePath, true /*replaceExisting*/); //throw FileError, (ErrorMoveUnsupported), (ErrorTargetExisting)
}
catch (FileError&) { if (!firstError) firstError = std::current_exception(); }
});
@@ -2140,56 +2080,62 @@ public:
}
}
- Zstring addUserSession(const Zstring& googleLoginHint, const std::function<void()>& updateGui /*throw X*/) //throw FileError, X
+ Zstring addUserSession(const Zstring& googleLoginHint, const std::function<void()>& updateGui /*throw X*/) //throw SysError, X
{
- const GoogleAccessInfo accessInfo = authorizeAccessToGoogleDrive(googleLoginHint, updateGui); //throw FileError, X
+ const GoogleAccessInfo accessInfo = authorizeAccessToGoogleDrive(googleLoginHint, updateGui); //throw SysError, X
- accessUserSession(accessInfo.userInfo.email, [&](std::optional<UserSession>& userSession) //throw FileError
+ accessUserSession(accessInfo.userInfo.email, [&](std::optional<UserSession>& userSession) //throw SysError
{
if (userSession)
userSession->accessBuf.ref().update(accessInfo); //redundant?
else
{
auto accessBuf = makeSharedRef<GoogleAccessBuffer>(accessInfo);
- auto fileState = makeSharedRef<GoogleFileState >(accessBuf.ref()); //throw FileError
+ auto fileState = makeSharedRef<GoogleFileState >(accessBuf.ref()); //throw SysError
userSession = { accessBuf, fileState };
}
});
+
return accessInfo.userInfo.email;
}
- void removeUserSession(const Zstring& googleUserEmail) //throw FileError
+ void removeUserSession(const Zstring& googleUserEmail) //throw SysError
{
try
{
- accessUserSession(googleUserEmail, [&](std::optional<UserSession>& userSession) //throw FileError
+ accessUserSession(googleUserEmail, [&](std::optional<UserSession>& userSession) //throw SysError
{
if (userSession)
- revokeAccessToGoogleDrive(userSession->accessBuf.ref().getAccessToken(), googleUserEmail); //throw FileError
+ revokeAccessToGoogleDrive(userSession->accessBuf.ref().getAccessToken(), googleUserEmail); //throw SysError
});
}
- catch (FileError&) { assert(false); } //best effort: try to invalidate the access token
+ catch (SysError&) { assert(false); } //best effort: try to invalidate the access token
//=> expected to fail if offline => not worse than removing FFS via "Uninstall Programs"
- //start with deleting the DB file (1. maybe it's corrupted? 2. skip unnecessary lazy-load)
- const Zstring dbFilePath = getDbFilePath(googleUserEmail);
try
{
- removeFilePlain(dbFilePath); //throw FileError
- }
- catch (FileError&)
- {
- if (itemStillExists(dbFilePath)) //throw FileError
- throw;
+ //start with deleting the DB file (1. maybe it's corrupted? 2. skip unnecessary lazy-load)
+ const Zstring dbFilePath = getDbFilePath(googleUserEmail);
+ try
+ {
+ removeFilePlain(dbFilePath); //throw FileError
+ }
+ catch (FileError&)
+ {
+ if (itemStillExists(dbFilePath)) //throw FileError
+ throw;
+ }
}
+ catch (const FileError& e) { throw SysError(e.toString()); } //file access errors should be further enriched by context info => SysError
- accessUserSession(googleUserEmail, [&](std::optional<UserSession>& userSession) //throw FileError
+
+ accessUserSession(googleUserEmail, [&](std::optional<UserSession>& userSession) //throw SysError
{
userSession.reset();
});
}
- std::vector<Zstring> /*Google user email*/ listUserSessions() //throw FileError
+ std::vector<Zstring> /*Google user email*/ listUserSessions() //throw SysError
{
std::vector<Zstring> emails;
@@ -2215,8 +2161,12 @@ public:
[&](const SymlinkInfo& si) {},
[&](const std::wstring& errorMsg)
{
- if (itemStillExists(configDirPath_)) //throw FileError
- throw FileError(errorMsg);
+ try
+ {
+ if (itemStillExists(configDirPath_)) //throw FileError
+ throw FileError(errorMsg);
+ }
+ catch (const FileError& e) { throw SysError(e.toString()); } //file access errors should be further enriched by context info => SysError
});
removeDuplicates(emails, LessAsciiNoCase());
@@ -2229,22 +2179,21 @@ public:
GoogleFileState::FileStateDelta stateDelta;
};
//perf: amortized fully buffered!
- AsyncAccessInfo accessGlobalFileState(const Zstring& googleUserEmail, const std::function<void(GoogleFileState& fileState)>& useFileState /*throw X*/) //throw FileError, X
+ AsyncAccessInfo accessGlobalFileState(const Zstring& googleUserEmail, const std::function<void(GoogleFileState& fileState)>& useFileState /*throw X*/) //throw SysError, X
{
std::string accessToken;
GoogleFileState::FileStateDelta stateDelta;
- accessUserSession(googleUserEmail, [&](std::optional<UserSession>& userSession) //throw FileError
+ accessUserSession(googleUserEmail, [&](std::optional<UserSession>& userSession) //throw SysError
{
if (!userSession)
- throw FileError(replaceCpy(_("Unable to access %x."), L"%x", fmtPath(getGoogleDisplayPath({ googleUserEmail, AfsPath() }))),
- replaceCpy(_("Please authorize access to user account %x."), L"%x", fmtPath(googleUserEmail)));
+ throw SysError(replaceCpy(_("Please authorize access to user account %x."), L"%x", fmtPath(googleUserEmail)));
//manage last sync time here rather than in GoogleFileState, so that "lastSyncToken" remains stable while accessing GoogleFileState in the callback
if (userSession->fileState.ref().syncIsDue())
- userSession->fileState.ref().syncWithGoogle(); //throw FileError
+ userSession->fileState.ref().syncWithGoogle(); //throw SysError
- accessToken = userSession->accessBuf.ref().getAccessToken(); //throw FileError
+ accessToken = userSession->accessBuf.ref().getAccessToken(); //throw SysError
stateDelta = userSession->fileState.ref().registerFileStateDelta();
useFileState(userSession->fileState.ref()); //throw X
@@ -2266,26 +2215,30 @@ private:
return appendSeparator(configDirPath_) + googleUserEmail + Zstr(".db");
}
- void accessUserSession(const Zstring& googleUserEmail, const std::function<void(std::optional<UserSession>& userSession)>& useSession) //throw FileError
+ void accessUserSession(const Zstring& googleUserEmail, const std::function<void(std::optional<UserSession>& userSession)>& useSession) //throw SysError
{
Protected<SessionHolder>* protectedSession = nullptr; //pointers remain stable, thanks to std::map<>
globalSessions_.access([&](GlobalSessions& sessions) { protectedSession = &sessions[googleUserEmail]; });
- protectedSession->access([&](SessionHolder& holder)
+ try
{
- if (!holder.dbWasLoaded) //let's NOT load the DB files under the globalSessions_ lock, but the session-specific one!
- try
- {
- holder.session = loadSession(getDbFilePath(googleUserEmail)); //throw FileError
- }
- catch (FileError&)
- {
- if (itemStillExists(getDbFilePath(googleUserEmail))) //throw FileError
- throw;
- }
- holder.dbWasLoaded = true;
- useSession(holder.session);
- });
+ protectedSession->access([&](SessionHolder& holder)
+ {
+ if (!holder.dbWasLoaded) //let's NOT load the DB files under the globalSessions_ lock, but the session-specific one!
+ try
+ {
+ holder.session = loadSession(getDbFilePath(googleUserEmail)); //throw FileError
+ }
+ catch (FileError&)
+ {
+ if (itemStillExists(getDbFilePath(googleUserEmail))) //throw FileError
+ throw;
+ }
+ holder.dbWasLoaded = true;
+ useSession(holder.session);
+ });
+ }
+ catch (const FileError& e) { throw SysError(e.toString()); } //GooglePersistentSessions errors should be further enriched by context info => SysError
}
static void saveSession(const Zstring& dbFilePath, const UserSession& userSession) //throw FileError
@@ -2333,16 +2286,13 @@ private:
auto fileState = makeSharedRef<GoogleFileState >(streamIn, accessBuf.ref()); //throw UnexpectedEndOfStreamError
return { accessBuf, fileState };
}
- catch (UnexpectedEndOfStreamError&)
- {
- throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(dbFilePath)), L"Unexpected end of stream.");
- }
+ catch (UnexpectedEndOfStreamError&) { throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(dbFilePath)), L"Unexpected end of stream."); }
}
struct UserSession
{
- SharedRef<GoogleAccessBuffer> accessBuf;
- SharedRef<GoogleFileState> fileState;
+ SharedRef<GoogleAccessBuffer> accessBuf;
+ SharedRef<GoogleFileState> fileState;
};
struct SessionHolder
@@ -2359,15 +2309,12 @@ private:
Global<GooglePersistentSessions> globalGoogleSessions;
//==========================================================================================
-
-GooglePersistentSessions::AsyncAccessInfo accessGlobalFileState(const Zstring& googleUserEmail, const std::function<void(GoogleFileState& fileState)>& useFileState /*throw X*/) //throw FileError, X
+GooglePersistentSessions::AsyncAccessInfo accessGlobalFileState(const Zstring& googleUserEmail, const std::function<void(GoogleFileState& fileState)>& useFileState /*throw X*/) //throw SysError, X
{
- const std::shared_ptr<GooglePersistentSessions> gps = globalGoogleSessions.get();
- if (!gps)
- throw FileError(replaceCpy(_("Unable to access %x."), L"%x", fmtPath(getGoogleDisplayPath({ googleUserEmail, AfsPath() }))),
- L"Function call not allowed during process init/shutdown.");
+ if (const std::shared_ptr<GooglePersistentSessions> gps = globalGoogleSessions.get())
+ return gps->accessGlobalFileState(googleUserEmail, useFileState); //throw SysError, X
- return gps->accessGlobalFileState(googleUserEmail, useFileState); //throw FileError, X
+ throw SysError(L"accessGlobalFileState() function call not allowed during init/shutdown.");
}
//==========================================================================================
@@ -2388,7 +2335,7 @@ struct GetDirDetails
{
std::string folderId;
std::optional<std::vector<GoogleFileItem>> childItemsBuffered;
- const GooglePersistentSessions::AsyncAccessInfo aai = accessGlobalFileState(gdriveFolderPath_.userEmail, [&](GoogleFileState& fileState) //throw FileError
+ const GooglePersistentSessions::AsyncAccessInfo aai = accessGlobalFileState(gdriveFolderPath_.userEmail, [&](GoogleFileState& fileState) //throw SysError
{
folderId = fileState.getItemId(gdriveFolderPath_.itemPath); //throw SysError
childItemsBuffered = fileState.tryGetBufferedFolderContent(folderId);
@@ -2399,10 +2346,10 @@ struct GetDirDetails
childItems = std::move(*childItemsBuffered);
else
{
- childItems = readFolderContent(folderId, aai.accessToken, gdriveFolderPath_); //throw FileError
+ childItems = readFolderContent(folderId, aai.accessToken); //throw SysError
//buffer new file state ASAP => make sure accessGlobalFileState() has amortized constant access (despite the occasional internal readFolderContent() on non-leaf folders)
- accessGlobalFileState(gdriveFolderPath_.userEmail, [&](GoogleFileState& fileState) //throw FileError
+ accessGlobalFileState(gdriveFolderPath_.userEmail, [&](GoogleFileState& fileState) //throw SysError
{
fileState.notifyFolderContent(aai.stateDelta, folderId, childItems);
});
@@ -2425,7 +2372,7 @@ class SingleFolderTraverser
{
public:
SingleFolderTraverser(const Zstring& googleUserEmail, const std::vector<std::pair<AfsPath, std::shared_ptr<AFS::TraverserCallback>>>& workload /*throw X*/) :
- workload_(workload), googleUserEmail_(googleUserEmail)
+ workload_(workload), googleUserEmail_(googleUserEmail)
{
while (!workload_.empty())
{
@@ -2446,34 +2393,34 @@ private:
void traverseWithException(const AfsPath& folderPath, AFS::TraverserCallback& cb) //throw FileError, X
{
- const GetDirDetails::Result r = GetDirDetails({ googleUserEmail_, folderPath })(); //throw FileError
+ const GetDirDetails::Result r = GetDirDetails({ googleUserEmail_, folderPath })(); //throw FileError
- for (const GoogleFileItem& item : r.childItems)
- {
- const Zstring itemName = utfTo<Zstring>(item.details.itemName);
- if (item.details.isFolder)
+ for (const GoogleFileItem& item : r.childItems)
{
- const AfsPath afsItemPath(nativeAppendPaths(r.gdriveFolderPath.itemPath.value, itemName));
+ const Zstring itemName = utfTo<Zstring>(item.details.itemName);
+ if (item.details.isFolder)
+ {
+ const AfsPath afsItemPath(nativeAppendPaths(r.gdriveFolderPath.itemPath.value, itemName));
- if (std::shared_ptr<AFS::TraverserCallback> cbSub = cb.onFolder({ itemName, nullptr /*symlinkInfo*/ })) //throw X
- workload_.push_back({ afsItemPath, std::move(cbSub) });
- }
- else
- {
- AFS::FileId fileId = copyStringTo<AFS::FileId>(item.itemId);
- cb.onFile({ itemName, item.details.fileSize, item.details.modTime, fileId, nullptr /*symlinkInfo*/ }); //throw X
+ if (std::shared_ptr<AFS::TraverserCallback> cbSub = cb.onFolder({ itemName, nullptr /*symlinkInfo*/ })) //throw X
+ workload_.push_back({ afsItemPath, std::move(cbSub) });
+ }
+ else
+ {
+ AFS::FileId fileId = copyStringTo<AFS::FileId>(item.itemId);
+ cb.onFile({ itemName, item.details.fileSize, item.details.modTime, fileId, nullptr /*symlinkInfo*/ }); //throw X
+ }
}
}
- }
std::vector<std::pair<AfsPath, std::shared_ptr<AFS::TraverserCallback>>> workload_;
- const Zstring googleUserEmail_;
+ const Zstring googleUserEmail_;
};
void gdriveTraverseFolderRecursive(const Zstring& googleUserEmail, const std::vector<std::pair<AfsPath, std::shared_ptr<AFS::TraverserCallback>>>& workload /*throw X*/, size_t) //throw X
{
- SingleFolderTraverser dummy(googleUserEmail, workload); //throw X
+ SingleFolderTraverser dummy(googleUserEmail, workload); //throw X
}
//==========================================================================================
//==========================================================================================
@@ -2493,19 +2440,23 @@ struct InputStreamGdrive : public AbstractFileSystem::InputStream
std::string fileId;
try
{
- accessToken = accessGlobalFileState(gdrivePath.userEmail, [&](GoogleFileState& fileState) //throw FileError
+ accessToken = accessGlobalFileState(gdrivePath.userEmail, [&](GoogleFileState& fileState) //throw SysError
{
fileId = fileState.getItemId(gdrivePath.itemPath); //throw SysError
}).accessToken;
}
catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot open file %x."), L"%x", fmtPath(getGoogleDisplayPath(gdrivePath))), e.toString()); }
- auto writeBlock = [&](const void* buffer, size_t bytesToWrite)
+ try
{
- return asyncStreamOut->write(buffer, bytesToWrite); //throw ThreadInterruption
- };
+ auto writeBlock = [&](const void* buffer, size_t bytesToWrite)
+ {
+ return asyncStreamOut->write(buffer, bytesToWrite); //throw ThreadInterruption
+ };
- gdriveDownloadFile(fileId, writeBlock, accessToken, gdrivePath); //throw FileError, ThreadInterruption
+ gdriveDownloadFile(fileId, writeBlock, accessToken); //throw SysError, ThreadInterruption
+ }
+ catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot read file %x."), L"%x", fmtPath(getGoogleDisplayPath(gdrivePath))), e.toString()); }
asyncStreamOut->closeStream();
}
@@ -2534,7 +2485,7 @@ struct InputStreamGdrive : public AbstractFileSystem::InputStream
AFS::StreamAttributes attr = {};
try
{
- accessGlobalFileState(gdrivePath_.userEmail, [&](GoogleFileState& fileState) //throw FileError
+ accessGlobalFileState(gdrivePath_.userEmail, [&](GoogleFileState& fileState) //throw SysError
{
std::pair<std::string /*itemId*/, GoogleItemDetails> gdriveAttr = fileState.getFileAttributes(gdrivePath_.itemPath); //throw SysError
attr.modTime = gdriveAttr.second.modTime;
@@ -2585,18 +2536,20 @@ struct OutputStreamGdrive : public AbstractFileSystem::OutputStreamImpl
{
try
{
+ const Zstring fileName = AFS::getItemName(gdrivePath.itemPath);
+
GoogleFileState::PathStatus ps;
- GooglePersistentSessions::AsyncAccessInfo aai = accessGlobalFileState(gdrivePath.userEmail, [&](GoogleFileState& fileState) //throw FileError
+ GooglePersistentSessions::AsyncAccessInfo aai = accessGlobalFileState(gdrivePath.userEmail, [&](GoogleFileState& fileState) //throw SysError
{
ps = fileState.getPathStatus(gdrivePath.itemPath); //throw SysError
});
if (ps.relPath.empty())
- throw SysError(formatSystemErrorRaw(EEXIST));
+ throw SysError(replaceCpy(_("The name %x is already used by another item."), L"%x", fmtPath(fileName)));
+
if (ps.relPath.size() > 1) //parent folder missing
throw SysError(replaceCpy(_("Cannot find %x."), L"%x",
fmtPath(getGoogleDisplayPath({ gdrivePath.userEmail, AfsPath(nativeAppendPaths(ps.existingPath.value, ps.relPath.front()))}))));
- const Zstring fileName = AFS::getItemName(gdrivePath.itemPath);
const std::string& parentFolderId = ps.existingItemId;
auto readBlock = [&](void* buffer, size_t bytesToRead)
@@ -2608,8 +2561,8 @@ struct OutputStreamGdrive : public AbstractFileSystem::OutputStreamImpl
//for whatever reason, gdriveUploadFile() is equally-fast or faster than gdriveUploadSmallFile(), despite its two roundtrips, even when the file sizes are 0!!
//=> issue likely on Google's side
const std::string fileIdNew = //streamSize && *streamSize < 5 * 1024 * 1024 ?
- //gdriveUploadSmallFile(fileName, parentFolderId, *streamSize, modTime, readBlock, aai.accessToken, gdrivePath) : //throw FileError, ThreadInterruption
- gdriveUploadFile (fileName, parentFolderId, modTime, readBlock, aai.accessToken, gdrivePath); //throw FileError, ThreadInterruption
+ //gdriveUploadSmallFile(fileName, parentFolderId, *streamSize, modTime, readBlock, aai.accessToken) : //throw SysError, ThreadInterruption
+ gdriveUploadFile (fileName, parentFolderId, modTime, readBlock, aai.accessToken); //throw SysError, ThreadInterruption
assert(asyncStreamIn->getTotalBytesRead() == asyncStreamIn->getTotalBytesWritten());
(void)streamSize;
@@ -2623,7 +2576,7 @@ struct OutputStreamGdrive : public AbstractFileSystem::OutputStreamImpl
newFileItem.details.modTime = *modTime;
newFileItem.details.parentIds.push_back(parentFolderId);
- accessGlobalFileState(gdrivePath.userEmail, [&](GoogleFileState& fileState) //throw FileError
+ accessGlobalFileState(gdrivePath.userEmail, [&](GoogleFileState& fileState) //throw SysError
{
fileState.notifyItemCreated(aai.stateDelta, newFileItem);
});
@@ -2718,7 +2671,7 @@ private:
try
{
GoogleFileState::PathStatus ps;
- accessGlobalFileState(googleUserEmail_, [&](GoogleFileState& fileState) //throw FileError
+ accessGlobalFileState(googleUserEmail_, [&](GoogleFileState& fileState) //throw SysError
{
ps = fileState.getPathStatus(afsPath); //throw SysError
});
@@ -2739,39 +2692,38 @@ private:
//avoid duplicate Google Drive item creation by multiple threads
PathAccessLock pal(getGdrivePath(afsPath)); //throw SysError
+ const Zstring folderName = getItemName(afsPath);
+
GoogleFileState::PathStatus ps;
- const GooglePersistentSessions::AsyncAccessInfo aai = accessGlobalFileState(googleUserEmail_, [&](GoogleFileState& fileState) //throw FileError
+ const GooglePersistentSessions::AsyncAccessInfo aai = accessGlobalFileState(googleUserEmail_, [&](GoogleFileState& fileState) //throw SysError
{
ps = fileState.getPathStatus(afsPath); //throw SysError
});
if (ps.relPath.empty())
- throw SysError(formatSystemErrorRaw(EEXIST));
+ throw SysError(replaceCpy(_("The name %x is already used by another item."), L"%x", fmtPath(folderName)));
+
if (ps.relPath.size() > 1) //parent folder missing
throw SysError(replaceCpy(_("Cannot find %x."), L"%x", fmtPath(getDisplayPath(AfsPath(nativeAppendPaths(ps.existingPath.value, ps.relPath.front()))))));
- const Zstring folderName = getItemName(afsPath);
const std::string& parentFolderId = ps.existingItemId;
- const std::string folderIdNew = gdriveCreateFolderPlain(folderName, parentFolderId, aai.accessToken); //throw FileError, SysError
+ const std::string folderIdNew = gdriveCreateFolderPlain(folderName, parentFolderId, aai.accessToken); //throw SysError
//buffer new file state ASAP (don't wait GOOGLE_DRIVE_SYNC_INTERVAL)
- accessGlobalFileState(googleUserEmail_, [&](GoogleFileState& fileState) //throw FileError
+ accessGlobalFileState(googleUserEmail_, [&](GoogleFileState& fileState) //throw SysError
{
fileState.notifyFolderCreated(aai.stateDelta, folderIdNew, folderName, parentFolderId);
});
}
- catch (const SysError& e)
- {
- throw FileError(replaceCpy(_("Cannot create directory %x."), L"%x", fmtPath(getDisplayPath(afsPath))), e.toString());
- }
+ catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot create directory %x."), L"%x", fmtPath(getDisplayPath(afsPath))), e.toString()); }
}
- void removeItemPlainImpl(const AfsPath& afsPath) const //throw FileError, SysError
+ void removeItemPlainImpl(const AfsPath& afsPath) const //throw SysError
{
std::string itemId;
std::optional<std::string> parentIdToUnlink;
- const GooglePersistentSessions::AsyncAccessInfo aai = accessGlobalFileState(googleUserEmail_, [&](GoogleFileState& fileState) //throw FileError
+ const GooglePersistentSessions::AsyncAccessInfo aai = accessGlobalFileState(googleUserEmail_, [&](GoogleFileState& fileState) //throw SysError
{
const std::optional<AfsPath> parentPath = getParentPath(afsPath);
if (!parentPath) throw SysError(L"Item is device root");
@@ -2787,20 +2739,20 @@ private:
if (parentIdToUnlink)
{
- gdriveUnlinkParent(itemId, *parentIdToUnlink, aai.accessToken); //throw FileError, SysError
+ gdriveUnlinkParent(itemId, *parentIdToUnlink, aai.accessToken); //throw SysError
//buffer new file state ASAP (don't wait GOOGLE_DRIVE_SYNC_INTERVAL)
- accessGlobalFileState(googleUserEmail_, [&](GoogleFileState& fileState) //throw FileError
+ accessGlobalFileState(googleUserEmail_, [&](GoogleFileState& fileState) //throw SysError
{
fileState.notifyParentRemoved(aai.stateDelta, itemId, *parentIdToUnlink);
});
}
else
{
- gdriveDeleteItem(itemId, aai.accessToken); //throw FileError, SysError
+ gdriveDeleteItem(itemId, aai.accessToken); //throw SysError
//buffer new file state ASAP (don't wait GOOGLE_DRIVE_SYNC_INTERVAL)
- accessGlobalFileState(googleUserEmail_, [&](GoogleFileState& fileState) //throw FileError
+ accessGlobalFileState(googleUserEmail_, [&](GoogleFileState& fileState) //throw SysError
{
fileState.notifyItemDeleted(aai.stateDelta, itemId);
});
@@ -2811,21 +2763,21 @@ private:
{
try
{
- removeItemPlainImpl(afsPath); //throw FileError, SysError
+ removeItemPlainImpl(afsPath); //throw SysError
}
catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot delete file %x."), L"%x", fmtPath(getDisplayPath(afsPath))), e.toString()); }
}
void removeSymlinkPlain(const AfsPath& afsPath) const override //throw FileError
{
- throw FileError(replaceCpy(_("Cannot delete symbolic link %x."), L"%x", fmtPath(getDisplayPath(afsPath))), L"Symlinks not supported for Google Drive.");
+ throw FileError(replaceCpy(_("Cannot delete symbolic link %x."), L"%x", fmtPath(getDisplayPath(afsPath))), _("Operation not supported by device."));
}
void removeFolderPlain(const AfsPath& afsPath) const override //throw FileError
{
try
{
- removeItemPlainImpl(afsPath); //throw FileError, SysError
+ removeItemPlainImpl(afsPath); //throw SysError
}
catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot delete directory %x."), L"%x", fmtPath(getDisplayPath(afsPath))), e.toString()); }
}
@@ -2851,12 +2803,12 @@ private:
//----------------------------------------------------------------------------------------------------------------
AbstractPath getSymlinkResolvedPath(const AfsPath& afsPath) const override //throw FileError
{
- throw FileError(replaceCpy(_("Cannot determine final path for %x."), L"%x", fmtPath(getDisplayPath(afsPath))), L"Symlinks not supported for Google Drive.");
+ throw FileError(replaceCpy(_("Cannot determine final path for %x."), L"%x", fmtPath(getDisplayPath(afsPath))), _("Operation not supported by device."));
}
std::string getSymlinkBinaryContent(const AfsPath& afsPath) const override //throw FileError
{
- throw FileError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtPath(getDisplayPath(afsPath))), L"Symlinks not supported for Google Drive.");
+ throw FileError(replaceCpy(_("Cannot resolve symbolic link %x."), L"%x", fmtPath(getDisplayPath(afsPath))), _("Operation not supported by device."));
}
//----------------------------------------------------------------------------------------------------------------
@@ -2892,8 +2844,7 @@ private:
{
//no native Google Drive file copy => use stream-based file copy:
if (copyFilePermissions)
- throw FileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(AFS::getDisplayPath(apTarget))),
- L"Permissions not supported for Google Drive.");
+ throw FileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(AFS::getDisplayPath(apTarget))), _("Operation not supported by device."));
//target existing: undefined behavior! (fail/overwrite/auto-rename)
return copyFileAsStream(afsPathSource, attrSource, apTarget, notifyUnbufferedIO); //throw FileError, (ErrorFileLocked), X
@@ -2904,8 +2855,7 @@ private:
void copyNewFolderForSameAfsType(const AfsPath& afsPathSource, const AbstractPath& apTarget, bool copyFilePermissions) const override //throw FileError
{
if (copyFilePermissions)
- throw FileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(AFS::getDisplayPath(apTarget))),
- L"Permissions not supported for Google Drive.");
+ throw FileError(replaceCpy(_("Cannot write permissions of %x."), L"%x", fmtPath(AFS::getDisplayPath(apTarget))), _("Operation not supported by device."));
//already existing: fail/ignore
AFS::createFolderPlain(apTarget); //throw FileError
@@ -2915,72 +2865,72 @@ private:
{
throw FileError(replaceCpy(replaceCpy(_("Cannot copy symbolic link %x to %y."),
L"%x", L"\n" + fmtPath(getDisplayPath(afsPathSource))),
- L"%y", L"\n" + fmtPath(AFS::getDisplayPath(apTarget))),
- L"Symlinks not supported for Google Drive.");
+ L"%y", L"\n" + fmtPath(AFS::getDisplayPath(apTarget))), _("Operation not supported by device."));
}
//target existing: undefined behavior! (fail/overwrite/auto-rename)
//=> actual behavior: fails with "already existing
- void moveAndRenameItemForSameAfsType(const AfsPath& afsPathSource, const AbstractPath& apTarget) const override //throw FileError, ErrorDifferentVolume
+ void moveAndRenameItemForSameAfsType(const AfsPath& pathFrom, const AbstractPath& pathTo) const override //throw FileError, ErrorMoveUnsupported
{
auto generateErrorMsg = [&] { return replaceCpy(replaceCpy(_("Cannot move file %x to %y."),
- L"%x", L"\n" + fmtPath(getDisplayPath(afsPathSource))),
- L"%y", L"\n" + fmtPath(AFS::getDisplayPath(apTarget)));
+ L"%x", L"\n" + fmtPath(getDisplayPath(pathFrom))),
+ L"%y", L"\n" + fmtPath(AFS::getDisplayPath(pathTo)));
};
- if (compareDeviceSameAfsType(apTarget.afsDevice.ref()) != 0)
- throw ErrorDifferentVolume(generateErrorMsg(), L"Different Google Drive volume.");
+ if (compareDeviceSameAfsType(pathTo.afsDevice.ref()) != 0)
+ throw ErrorMoveUnsupported(generateErrorMsg(), _("Operation not supported between different devices."));
try
{
//avoid duplicate Google Drive item creation by multiple threads
- PathAccessLock pal(getGdrivePath(apTarget.afsPath)); //throw SysError
+ PathAccessLock pal(getGdrivePath(pathTo.afsPath)); //throw SysError
- const Zstring itemNameOld = getItemName(afsPathSource);
- const Zstring itemNameNew = AFS::getItemName(apTarget);
+ const Zstring itemNameOld = getItemName(pathFrom);
+ const Zstring itemNameNew = AFS::getItemName(pathTo);
- const std::optional<AfsPath> parentAfsPathSource = getParentPath(afsPathSource);
- const std::optional<AfsPath> parentAfsPathTarget = getParentPath(apTarget.afsPath);
- if (!parentAfsPathSource) throw SysError(L"Source is device root");
- if (!parentAfsPathTarget) throw SysError(L"Target is device root");
+ const std::optional<AfsPath> parentPathFrom = getParentPath(pathFrom);
+ const std::optional<AfsPath> parentPathTo = getParentPath(pathTo.afsPath);
+ if (!parentPathFrom) throw SysError(L"Source is device root");
+ if (!parentPathTo ) throw SysError(L"Target is device root");
- std::string itemIdSource;
- time_t modTimeSource = 0;
- std::string parentIdSource;
- std::string parentIdTarget;
- const GooglePersistentSessions::AsyncAccessInfo aai = accessGlobalFileState(googleUserEmail_, [&](GoogleFileState& fileState) //throw FileError
+ std::string itemIdFrom;
+ time_t modTimeFrom = 0;
+ std::string parentIdFrom;
+ std::string parentIdTo;
+ const GooglePersistentSessions::AsyncAccessInfo aai = accessGlobalFileState(googleUserEmail_, [&](GoogleFileState& fileState) //throw SysError
{
- std::pair<std::string /*itemId*/, GoogleItemDetails> gdriveAttr = fileState.getFileAttributes(afsPathSource); //throw SysError
- itemIdSource = gdriveAttr.first;
- modTimeSource = gdriveAttr.second.modTime;
- parentIdSource = fileState.getItemId(*parentAfsPathSource); //throw SysError
- GoogleFileState::PathStatus psTarget = fileState.getPathStatus(apTarget.afsPath); //throw SysError
+ std::pair<std::string /*itemId*/, GoogleItemDetails> gdriveAttr = fileState.getFileAttributes(pathFrom); //throw SysError
+ itemIdFrom = gdriveAttr.first;
+ modTimeFrom = gdriveAttr.second.modTime;
+ parentIdFrom = fileState.getItemId(*parentPathFrom); //throw SysError
+ GoogleFileState::PathStatus psTo = fileState.getPathStatus(pathTo.afsPath); //throw SysError
//e.g. changing file name case only => this is not an "already exists" situation!
//also: hardlink referenced by two different paths, the source one will be unlinked
- if (psTarget.relPath.empty() && psTarget.existingItemId == itemIdSource)
- parentIdTarget = fileState.getItemId(*parentAfsPathTarget); //throw SysError
+ if (psTo.relPath.empty() && psTo.existingItemId == itemIdFrom)
+ parentIdTo = fileState.getItemId(*parentPathTo); //throw SysError
else
{
- if (psTarget.relPath.empty())
- throw SysError(formatSystemErrorRaw(EEXIST));
- if (psTarget.relPath.size() > 1) //parent folder missing
+ if (psTo.relPath.empty())
+ throw SysError(replaceCpy(_("The name %x is already used by another item."), L"%x", fmtPath(itemNameNew)));
+
+ if (psTo.relPath.size() > 1) //parent folder missing
throw SysError(replaceCpy(_("Cannot find %x."), L"%x",
- fmtPath(getDisplayPath(AfsPath(nativeAppendPaths(psTarget.existingPath.value, psTarget.relPath.front()))))));
- parentIdTarget = psTarget.existingItemId;
+ fmtPath(getDisplayPath(AfsPath(nativeAppendPaths(psTo.existingPath.value, psTo.relPath.front()))))));
+ parentIdTo = psTo.existingItemId;
}
});
- if (parentIdSource == parentIdTarget && itemNameOld == itemNameNew)
+ if (parentIdFrom == parentIdTo && itemNameOld == itemNameNew)
return; //nothing to do
//target name already existing? will (happily) create duplicate items
- gdriveMoveAndRenameItem(itemIdSource, parentIdSource, parentIdTarget, itemNameNew, modTimeSource, aai.accessToken); //throw FileError, SysError
+ gdriveMoveAndRenameItem(itemIdFrom, parentIdFrom, parentIdTo, itemNameNew, modTimeFrom, aai.accessToken); //throw SysError
//buffer new file state ASAP (don't wait GOOGLE_DRIVE_SYNC_INTERVAL)
- accessGlobalFileState(googleUserEmail_, [&](GoogleFileState& fileState) //throw FileError
+ accessGlobalFileState(googleUserEmail_, [&](GoogleFileState& fileState) //throw SysError
{
- fileState.notifyMoveAndRename(aai.stateDelta, itemIdSource, parentIdSource, parentIdTarget, itemNameNew);
+ fileState.notifyMoveAndRename(aai.stateDelta, itemIdFrom, parentIdFrom, parentIdTo, itemNameNew);
});
}
catch (const SysError& e) { throw FileError(generateErrorMsg(), e.toString()); }
@@ -2999,12 +2949,12 @@ private:
{
const std::shared_ptr<GooglePersistentSessions> gps = globalGoogleSessions.get();
if (!gps)
- throw SysError(L"Function call not allowed during process init/shutdown.");
+ throw SysError(L"GdriveFileSystem::authenticateAccess() function call not allowed during init/shutdown.");
- for (const Zstring& email : gps->listUserSessions()) //throw FileError
+ for (const Zstring& email : gps->listUserSessions()) //throw SysError
if (equalAsciiNoCase(email, googleUserEmail_))
return;
- gps->addUserSession(googleUserEmail_ /*googleLoginHint*/, nullptr /*updateGui*/); //throw FileError
+ gps->addUserSession(googleUserEmail_ /*googleLoginHint*/, nullptr /*updateGui*/); //throw SysError
//error messages will be lost after time out in dir_exist_async.h! However:
//The most-likely-to-fail parts (web access) are reported by authorizeAccessToGoogleDrive() via the browser!
}
@@ -3020,8 +2970,8 @@ private:
{
try
{
- const std::string& accessToken = accessGlobalFileState(googleUserEmail_, [](GoogleFileState& fileState) {}).accessToken; //throw FileError
- return gdriveGetFreeDiskSpace(accessToken); //throw FileError, SysError; returns 0 if not available
+ const std::string& accessToken = accessGlobalFileState(googleUserEmail_, [](GoogleFileState& fileState) {}).accessToken; //throw SysError
+ return gdriveGetFreeDiskSpace(accessToken); //throw SysError; returns 0 if not available
}
catch (const SysError& e) { throw FileError(replaceCpy(_("Cannot determine free disk space for %x."), L"%x", fmtPath(getDisplayPath(afsPath))), e.toString()); }
}
@@ -3043,16 +2993,16 @@ private:
try
{
GoogleFileState::PathStatus ps;
- const GooglePersistentSessions::AsyncAccessInfo aai = accessGlobalFileState(googleUserEmail_, [&](GoogleFileState& fileState) //throw FileError
+ const GooglePersistentSessions::AsyncAccessInfo aai = accessGlobalFileState(googleUserEmail_, [&](GoogleFileState& fileState) //throw SysError
{
ps = fileState.getPathStatus(afsPath); //throw SysError
});
if (ps.relPath.empty())
{
- gdriveMoveToTrash(ps.existingItemId, aai.accessToken); //throw FileError, SysError
+ gdriveMoveToTrash(ps.existingItemId, aai.accessToken); //throw SysError
//buffer new file state ASAP (don't wait GOOGLE_DRIVE_SYNC_INTERVAL)
- accessGlobalFileState(googleUserEmail_, [&](GoogleFileState& fileState) //throw FileError
+ accessGlobalFileState(googleUserEmail_, [&](GoogleFileState& fileState) //throw SysError
{
//a hardlink with multiple parents will be not be accessible anymore via any of its path aliases!
fileState.notifyItemDeleted(aai.stateDelta, ps.existingItemId);
@@ -3097,29 +3047,40 @@ void fff::googleDriveTeardown()
Zstring fff::googleAddUser(const std::function<void()>& updateGui /*throw X*/) //throw FileError, X
{
- if (const std::shared_ptr<GooglePersistentSessions> gps = globalGoogleSessions.get())
- return gps->addUserSession(Zstr("") /*googleLoginHint*/, updateGui); //throw FileError, X
+ try
+ {
+ if (const std::shared_ptr<GooglePersistentSessions> gps = globalGoogleSessions.get())
+ return gps->addUserSession(Zstr("") /*googleLoginHint*/, updateGui); //throw SysError, X
- throw FileError(replaceCpy(_("Unable to access %x."), L"%x", L"Google Drive"), L"Function call not allowed during process init/shutdown.");
+ throw SysError(L"googleAddUser() function call not allowed during init/shutdown.");
+ }
+ catch (const SysError& e) { throw FileError(replaceCpy(_("Unable to connect to %x."), L"%x", L"Google Drive"), e.toString()); }
}
void fff::googleRemoveUser(const Zstring& googleUserEmail) //throw FileError
{
- if (const std::shared_ptr<GooglePersistentSessions> gps = globalGoogleSessions.get())
- return gps->removeUserSession(googleUserEmail); //throw FileError
+ try
+ {
+ if (const std::shared_ptr<GooglePersistentSessions> gps = globalGoogleSessions.get())
+ return gps->removeUserSession(googleUserEmail); //throw SysError
- throw FileError(replaceCpy(_("Unable to access %x."), L"%x", fmtPath(getGoogleDisplayPath({ googleUserEmail, AfsPath() }))),
- L"Function call not allowed during process init/shutdown.");
+ throw SysError(L"googleRemoveUser() function call not allowed during init/shutdown.");
+ }
+ catch (const SysError& e) { throw FileError(replaceCpy(_("Unable to disconnect from %x."), L"%x", fmtPath(getGoogleDisplayPath({ googleUserEmail, AfsPath() }))), e.toString()); }
}
std::vector<Zstring> /*Google user email*/ fff::googleListConnectedUsers() //throw FileError
{
- if (const std::shared_ptr<GooglePersistentSessions> gps = globalGoogleSessions.get())
- return gps->listUserSessions(); //throw FileError
+ try
+ {
+ if (const std::shared_ptr<GooglePersistentSessions> gps = globalGoogleSessions.get())
+ return gps->listUserSessions(); //throw SysError
- throw FileError(replaceCpy(_("Unable to access %x."), L"%x", L"Google Drive"), L"Function call not allowed during process init/shutdown.");
+ throw SysError(L"googleListConnectedUsers() function call not allowed during init/shutdown.");
+ }
+ catch (const SysError& e) { throw FileError(replaceCpy(_("Unable to access %x."), L"%x", L"Google Drive"), e.toString()); }
}
bgstack15