diff options
36 files changed, 2785 insertions, 0 deletions
diff --git a/plugins/CloudFile/CloudFile.vcxproj b/plugins/CloudFile/CloudFile.vcxproj new file mode 100644 index 0000000000..c16966ec41 --- /dev/null +++ b/plugins/CloudFile/CloudFile.vcxproj @@ -0,0 +1,47 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup Label="ProjectConfigurations"> + <ProjectConfiguration Include="Debug|Win32"> + <Configuration>Debug</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Debug|x64"> + <Configuration>Debug</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|Win32"> + <Configuration>Release</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|x64"> + <Configuration>Release</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + </ItemGroup> + <PropertyGroup Label="Globals"> + <ProjectName>CloudFile</ProjectName> + <ProjectGuid>{E876FE63-0701-4CDA-BED5-7C73A379C1D1}</ProjectGuid> + </PropertyGroup> + <ImportGroup Label="PropertySheets"> + <Import Project="$(ProjectDir)..\..\build\vc.common\plugin.props" /> + </ImportGroup> + <ItemGroup> + <ClInclude Include="src\Dropbox\*.h" /> + <ClCompile Include="src\Dropbox\*.cpp"> + <PrecompiledHeader>NotUsing</PrecompiledHeader> + </ClCompile> + <ClInclude Include="src\Services\*.h" /> + <ClCompile Include="src\Services\*.cpp"> + <PrecompiledHeader>NotUsing</PrecompiledHeader> + </ClCompile> + <None Include="res\*.ico" /> + </ItemGroup> + <ItemDefinitionGroup> + <Link> + <AdditionalDependencies>comctl32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies> + </Link> + <ClCompile> + <ExceptionHandling>Sync</ExceptionHandling> + </ClCompile> + </ItemDefinitionGroup> +</Project> diff --git a/plugins/CloudFile/CloudFile.vcxproj.filters b/plugins/CloudFile/CloudFile.vcxproj.filters new file mode 100644 index 0000000000..8f90aeb3d5 --- /dev/null +++ b/plugins/CloudFile/CloudFile.vcxproj.filters @@ -0,0 +1,4 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <Import Project="$(ProjectDir)..\..\build\vc.common\common.filters" /> +</Project> diff --git a/plugins/CloudFile/res/dropbox.ico b/plugins/CloudFile/res/dropbox.ico Binary files differnew file mode 100644 index 0000000000..8a286d7f32 --- /dev/null +++ b/plugins/CloudFile/res/dropbox.ico diff --git a/plugins/CloudFile/res/gdrive.ico b/plugins/CloudFile/res/gdrive.ico Binary files differnew file mode 100644 index 0000000000..a34123e95b --- /dev/null +++ b/plugins/CloudFile/res/gdrive.ico diff --git a/plugins/CloudFile/res/resource.rc b/plugins/CloudFile/res/resource.rc new file mode 100644 index 0000000000..bf1cd8940d --- /dev/null +++ b/plugins/CloudFile/res/resource.rc @@ -0,0 +1,163 @@ +// Microsoft Visual C++ generated resource script. +// +#include "..\src\resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "winres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// Russian (Russia) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_RUS) +LANGUAGE LANG_RUSSIAN, SUBLANG_DEFAULT +#pragma code_page(1251) + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "..\\src\\resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""winres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + +#endif // Russian (Russia) resources +///////////////////////////////////////////////////////////////////////////// + + +///////////////////////////////////////////////////////////////////////////// +// English resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +LANGUAGE LANG_ENGLISH, SUBLANG_NEUTRAL +#pragma code_page(1252) + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_UPLOAD ICON "upload.ico" + +IDI_DROPBOX ICON "dropbox.ico" + +IDI_GDRIVE ICON "gdrive.ico" + + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_OPTIONS_MAIN DIALOGEX 0, 0, 307, 234 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD +EXSTYLE WS_EX_CONTROLPARENT +FONT 8, "MS Shell Dlg", 0, 0, 0x1 +BEGIN + GROUPBOX "Download link",-1,5,5,297,57 + CONTROL "Autosend download link to contact",IDC_URL_AUTOSEND, + "Button",BS_AUTORADIOBUTTON,15,19,282,10 + CONTROL "Paste download link into message input area",IDC_URL_COPYTOMIA, + "Button",BS_AUTORADIOBUTTON,15,32,282,10 + CONTROL "Copy download link to clipboard",IDC_URL_COPYTOCB, + "Button",BS_AUTOCHECKBOX | WS_TABSTOP,15,45,282,10 +END + +IDD_OAUTH DIALOGEX 0, 0, 193, 83 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU +EXSTYLE WS_EX_TOPMOST +CAPTION "Authorization" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + EDITTEXT IDC_OAUTH_CODE,7,41,179,14,ES_AUTOHSCROLL + DEFPUSHBUTTON "OK",IDOK,75,62,50,14 + PUSHBUTTON "Cancel",IDCANCEL,136,62,50,14 + LTEXT "Enter authorization code:",IDC_STATIC,7,31,179,8 + LTEXT "Allow access to team",IDC_STATIC,7,7,68,8 + CONTROL "Go to this link",IDC_OAUTH_AUTHORIZE,"Hyperlink",WS_GROUP | WS_TABSTOP | 0x1,7,17,179,8 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// AFX_DIALOG_LAYOUT +// + +IDD_OPTIONS_MAIN AFX_DIALOG_LAYOUT +BEGIN + 0 +END + +IDD_OAUTH AFX_DIALOG_LAYOUT +BEGIN + 0 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_OPTIONS_MAIN, DIALOG + BEGIN + LEFTMARGIN, 5 + RIGHTMARGIN, 302 + VERTGUIDE, 15 + TOPMARGIN, 5 + BOTTOMMARGIN, 229 + END + + IDD_OAUTH, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 186 + TOPMARGIN, 7 + BOTTOMMARGIN, 76 + END +END +#endif // APSTUDIO_INVOKED + +#endif // English resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/plugins/CloudFile/res/upload.ico b/plugins/CloudFile/res/upload.ico Binary files differnew file mode 100644 index 0000000000..b51e87ed35 --- /dev/null +++ b/plugins/CloudFile/res/upload.ico diff --git a/plugins/CloudFile/res/version.rc b/plugins/CloudFile/res/version.rc new file mode 100644 index 0000000000..675d852de7 --- /dev/null +++ b/plugins/CloudFile/res/version.rc @@ -0,0 +1,38 @@ +// Microsoft Visual C++ generated resource script. +// +#ifdef APSTUDIO_INVOKED +#error this file is not editable by Microsoft Visual C++ +#endif //APSTUDIO_INVOKED + +#include "afxres.h" +#include "..\src\version.h" + +VS_VERSION_INFO VERSIONINFO + FILEVERSION __FILEVERSION_STRING + PRODUCTVERSION __FILEVERSION_STRING + FILEFLAGSMASK 0x17L +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x0L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "000004b0" + BEGIN + VALUE "FileDescription", __DESCRIPTION + VALUE "InternalName", __PLUGIN_NAME + VALUE "LegalCopyright", __COPYRIGHT + VALUE "OriginalFilename", __FILENAME + VALUE "ProductName", __PLUGIN_NAME + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0, 1200 + END +END diff --git a/plugins/CloudFile/src/Services/dropbox_api.h b/plugins/CloudFile/src/Services/dropbox_api.h new file mode 100644 index 0000000000..3db69250e8 --- /dev/null +++ b/plugins/CloudFile/src/Services/dropbox_api.h @@ -0,0 +1,200 @@ +#ifndef _DROPBOXSERVICE_API_H_ +#define _DROPBOXSERVICE_API_H_ + +namespace DropboxAPI +{ +#define DROPBOX_API_VER "/2" +#define DROPBOX_WWW_URL "https://www.dropbox.com" +#define DROPBOX_API "https://api.dropboxapi.com" +#define DROPBOX_API_RPC DROPBOX_API DROPBOX_API_VER +#define DROPBOX_CONTENT "https://content.dropboxapi.com" +#define DROPBOX_API_CU DROPBOX_CONTENT DROPBOX_API_VER + +#define DROPBOX_APP_KEY "fa8du7gkf2q8xzg" +#include "../../../miranda-private-keys/Dropbox/secret_key.h" + + class GetAccessTokenRequest : public HttpRequest + { + public: + GetAccessTokenRequest(const char *requestToken) : + HttpRequest(REQUEST_POST, DROPBOX_API "/oauth2/token") + { + AddHeader("Content-Type", "application/x-www-form-urlencoded"); + + CMStringA data(CMStringDataFormat::FORMAT, + "client_id=%s&client_secret=%s&grant_type=authorization_code&code=%s", + DROPBOX_APP_KEY, DROPBOX_API_SECRET, requestToken); + SetData(data.GetBuffer(), data.GetLength()); + } + }; + + class RevokeAccessTokenRequest : public HttpRequest + { + public: + RevokeAccessTokenRequest(const char *token) : + HttpRequest(REQUEST_POST, DROPBOX_API "/oauth2/token/revoke") + { + AddBearerAuthHeader(token); + } + }; + + class UploadFileRequest : public HttpRequest + { + public: + UploadFileRequest(const char *token, const char *path, const char *data, size_t size) : + HttpRequest(REQUEST_POST, DROPBOX_API_CU "/files/upload") + { + AddBearerAuthHeader(token); + AddHeader("Content-Type", "application/octet-stream"); + + JSONNode params(JSON_NODE); + params + << JSONNode("path", path) + << JSONNode("mode", "overwrite"); + + AddHeader("Dropbox-API-Arg", params.write().c_str()); + + SetData(data, size); + } + }; + + class StartUploadSessionRequest : public HttpRequest + { + public: + StartUploadSessionRequest(const char *token, const char *data, size_t size) : + HttpRequest(REQUEST_POST, DROPBOX_API_CU "/files/upload_session/start") + { + AddBearerAuthHeader(token); + AddHeader("Content-Type", "application/octet-stream"); + + SetData(data, size); + } + }; + + class AppendToUploadSessionRequest : public HttpRequest + { + public: + AppendToUploadSessionRequest(const char *token, const char *sessionId, size_t offset, const char *data, size_t size) : + HttpRequest(REQUEST_POST, DROPBOX_API_CU "/files/upload_session/append_v2") + { + AddBearerAuthHeader(token); + AddHeader("Content-Type", "application/octet-stream"); + + + JSONNode cursor; + cursor.set_name("cursor"); + cursor + << JSONNode("session_id", sessionId) + << JSONNode("offset", (unsigned long)offset); + + JSONNode param; + param << cursor; + + AddHeader("Dropbox-API-Arg", param.write().c_str()); + + SetData(data, size); + } + }; + + class FinishUploadSessionRequest : public HttpRequest + { + public: + FinishUploadSessionRequest(const char *token, const char *sessionId, size_t offset, const char *path, const char *data, size_t size) : + HttpRequest(REQUEST_POST, DROPBOX_API_CU "/files/upload_session/finish") + { + AddBearerAuthHeader(token); + AddHeader("Content-Type", "application/octet-stream"); + + JSONNode cursor(JSON_NODE); + cursor.set_name("cursor"); + cursor + << JSONNode("session_id", sessionId) + << JSONNode("offset", (unsigned long)offset); + + JSONNode commit(JSON_NODE); + commit.set_name("commit"); + commit + << JSONNode("path", path) + << JSONNode("mode", "overwrite"); + + JSONNode params(JSON_NODE); + params + << cursor + << commit; + + AddHeader("Dropbox-API-Arg", params.write().c_str()); + + SetData(data, size); + } + }; + + class CreateFolderRequest : public HttpRequest + { + public: + CreateFolderRequest(const char *token, const char *path) : + HttpRequest(REQUEST_POST, DROPBOX_API_RPC "/files/create_folder") + { + AddBearerAuthHeader(token); + AddHeader("Content-Type", "application/json"); + + JSONNode root(JSON_NODE); + root << JSONNode("path", path); + + json_string data = root.write(); + SetData(data.c_str(), data.length()); + } + }; + + class GetTemporaryLinkRequest : public HttpRequest + { + public: + GetTemporaryLinkRequest(const char *token, const char *path) : + HttpRequest(REQUEST_POST, DROPBOX_API_RPC "/files/get_temporary_link") + { + AddBearerAuthHeader(token); + AddHeader("Content-Type", "application/json"); + + JSONNode root(JSON_NODE); + root << JSONNode("path", path); + + json_string data = root.write(); + SetData(data.c_str(), data.length()); + } + }; + + class CreateSharedLinkRequest : public HttpRequest + { + public: + CreateSharedLinkRequest(const char *token, const char *path) : + HttpRequest(REQUEST_POST, DROPBOX_API_RPC "/sharing/create_shared_link_with_settings") + { + AddBearerAuthHeader(token); + AddHeader("Content-Type", "application/json"); + + JSONNode root(JSON_NODE); + root << JSONNode("path", path); + + json_string data = root.write(); + SetData(data.c_str(), data.length()); + } + }; + + class GetSharedLinkRequest : public HttpRequest + { + public: + GetSharedLinkRequest(const char *token, const char *path) : + HttpRequest(REQUEST_POST, DROPBOX_API_RPC "/sharing/list_shared_links") + { + AddBearerAuthHeader(token); + AddHeader("Content-Type", "application/json"); + + JSONNode root(JSON_NODE); + root << JSONNode("path", path); + + json_string data = root.write(); + SetData(data.c_str(), data.length()); + } + }; +}; + +#endif //_DROPBOXSERVICE_API_H_ diff --git a/plugins/CloudFile/src/Services/dropbox_service.cpp b/plugins/CloudFile/src/Services/dropbox_service.cpp new file mode 100644 index 0000000000..26c0ff6af3 --- /dev/null +++ b/plugins/CloudFile/src/Services/dropbox_service.cpp @@ -0,0 +1,277 @@ +#include "..\stdafx.h" +#include "dropbox_api.h" + +CDropboxService::CDropboxService(HNETLIBUSER hConnection) + : CCloudService(hConnection) +{ +} + +const char* CDropboxService::GetModule() const +{ + return "Dropbox"; +} + +const wchar_t* CDropboxService::GetText() const +{ + return L"Dropbox"; +} + +HANDLE CDropboxService::GetIcon() const +{ + return GetIconHandle(IDI_DROPBOX); +} + +bool CDropboxService::IsLoggedIn() +{ + ptrA token(db_get_sa(NULL, GetModule(), "TokenSecret")); + return token != NULL; +} + +void CDropboxService::Login() +{ + COAuthDlg(this, DROPBOX_WWW_URL "/oauth2/authorize?response_type=code&client_id=" DROPBOX_APP_KEY, RequestAccessTokenThread).DoModal(); +} + +void CDropboxService::Logout() +{ + mir_forkthreadex(RevokeAccessTokenThread, this); +} + +unsigned CDropboxService::RequestAccessTokenThread(void *owner, void *param) +{ + HWND hwndDlg = (HWND)param; + CDropboxService *service = (CDropboxService*)owner; + + if (service->IsLoggedIn()) + service->Logout(); + + char requestToken[128]; + GetDlgItemTextA(hwndDlg, IDC_OAUTH_CODE, requestToken, _countof(requestToken)); + + DropboxAPI::GetAccessTokenRequest request(requestToken); + NLHR_PTR response(request.Send(service->hConnection)); + + if (response == NULL || response->resultCode != HTTP_CODE_OK) { + Netlib_Logf(service->hConnection, "%s: %s", service->GetModule(), service->HttpStatusToError()); + //ShowNotification(TranslateT("server does not respond"), MB_ICONERROR); + return 0; + } + + JSONNode root = JSONNode::parse(response->pData); + if (root.empty()) { + Netlib_Logf(service->hConnection, "%s: %s", service->GetModule(), service->HttpStatusToError(response->resultCode)); + //ShowNotification((wchar_t*)error_description, MB_ICONERROR); + return 0; + } + + JSONNode node = root.at("error_description"); + if (!node.isnull()) { + ptrW error_description(mir_a2u_cp(node.as_string().c_str(), CP_UTF8)); + Netlib_Logf(service->hConnection, "%s: %s", service->GetModule(), service->HttpStatusToError(response->resultCode)); + //ShowNotification((wchar_t*)error_description, MB_ICONERROR); + return 0; + } + + node = root.at("access_token"); + db_set_s(NULL, service->GetModule(), "TokenSecret", node.as_string().c_str()); + ProtoBroadcastAck(MODULE, NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)ID_STATUS_OFFLINE, (WPARAM)ID_STATUS_ONLINE); + + SetDlgItemTextA(hwndDlg, IDC_OAUTH_CODE, ""); + + EndDialog(hwndDlg, 1); + + return 0; +} + +unsigned CDropboxService::RevokeAccessTokenThread(void *param) +{ + CDropboxService *service = (CDropboxService*)param; + + ptrA token(db_get_sa(NULL, service->GetModule(), "TokenSecret")); + DropboxAPI::RevokeAccessTokenRequest request(token); + NLHR_PTR response(request.Send(service->hConnection)); + + return 0; +} + +void CDropboxService::HandleJsonError(JSONNode &node) +{ + JSONNode error = node.at("error"); + if (!error.isnull()) { + json_string tag = error.at(".tag").as_string(); + throw Exception(tag.c_str()); + } +} + +char* CDropboxService::UploadFile(const char *data, size_t size, char *path) +{ + ptrA token(db_get_sa(NULL, GetModule(), "TokenSecret")); + ptrA encodedPath(mir_utf8encode(path)); + DropboxAPI::UploadFileRequest request(token, encodedPath, data, size); + NLHR_PTR response(request.Send(hConnection)); + + JSONNode root = GetJsonResponse(response); + JSONNode node = root.at("path_lower"); + mir_strcpy(path, node.as_string().c_str()); + + return path; +} + +void CDropboxService::StartUploadSession(const char *data, size_t size, char *sessionId) +{ + ptrA token(db_get_sa(NULL, GetModule(), "TokenSecret")); + DropboxAPI::StartUploadSessionRequest request(token, data, size); + NLHR_PTR response(request.Send(hConnection)); + + JSONNode root = GetJsonResponse(response); + JSONNode node = root.at("session_id"); + mir_strcpy(sessionId, node.as_string().c_str()); +} + +void CDropboxService::AppendToUploadSession(const char *data, size_t size, const char *sessionId, size_t offset) +{ + ptrA token(db_get_sa(NULL, GetModule(), "TokenSecret")); + DropboxAPI::AppendToUploadSessionRequest request(token, sessionId, offset, data, size); + NLHR_PTR response(request.Send(hConnection)); + + GetJsonResponse(response); +} + +char* CDropboxService::FinishUploadSession(const char *data, size_t size, const char *sessionId, size_t offset, char *path) +{ + ptrA token(db_get_sa(NULL, GetModule(), "TokenSecret")); + DropboxAPI::FinishUploadSessionRequest request(token, sessionId, offset, path, data, size); + NLHR_PTR response(request.Send(hConnection)); + + JSONNode root = GetJsonResponse(response); + JSONNode node = root.at("path_lower"); + mir_strcpy(path, node.as_string().c_str()); + + return path; +} + +void CDropboxService::CreateFolder(const char *path) +{ + ptrA token(db_get_sa(NULL, GetModule(), "TokenSecret")); + DropboxAPI::CreateFolderRequest request(token, path); + NLHR_PTR response(request.Send(hConnection)); + + HandleHttpError(response); + + // forder exists on server + if (response->resultCode == HTTP_CODE_FORBIDDEN) + return; + + GetJsonResponse(response); +} + +void CDropboxService::CreateSharedLink(const char *path, char *url) +{ + ptrA token(db_get_sa(NULL, GetModule(), "TokenSecret")); + DropboxAPI::CreateSharedLinkRequest shareRequest(token, path); + NLHR_PTR response(shareRequest.Send(hConnection)); + + HandleHttpError(response); + + JSONNode root = JSONNode::parse(response->pData); + if (root.isnull()) + throw Exception(HttpStatusToError()); + + JSONNode error = root.at("error"); + if (error.isnull()) { + JSONNode link = root.at("link"); + mir_strcpy(url, link.as_string().c_str()); + return; + } + + json_string tag = error.at(".tag").as_string(); + if (tag != "shared_link_already_exists") + throw Exception(tag.c_str()); + + DropboxAPI::GetSharedLinkRequest getRequest(token, path); + response = getRequest.Send(hConnection); + + root = GetJsonResponse(response); + + JSONNode links = root.at("links").as_array(); + JSONNode link = links[0u].at("url"); + mir_strcpy(url, link.as_string().c_str()); +} + +UINT CDropboxService::Upload(FileTransferParam *ftp) +{ + if (!IsLoggedIn()) + Login(); + + try { + const wchar_t *folderName = ftp->GetFolderName(); + if (folderName) { + char path[MAX_PATH], url[MAX_PATH]; + PreparePath(folderName, path); + CreateFolder(path); + CreateSharedLink(path, url); + ftp->AppendFormatData(L"%s\r\n", ptrW(mir_utf8decodeW(url))); + } + + ftp->FirstFile(); + do + { + const wchar_t *fileName = ftp->GetCurrentRelativeFilePath(); + uint64_t fileSize = ftp->GetCurrentFileSize(); + + int chunkSize = ftp->GetCurrentFileChunkSize(); + mir_ptr<char>data((char*)mir_calloc(chunkSize)); + size_t size = ftp->ReadCurrentFile(data, chunkSize); + + size_t offset = 0; + char sessionId[64]; + StartUploadSession(data, size, sessionId); + + offset += size; + ftp->Progress(size); + + for (size_t chunk = 0; chunk < (fileSize / chunkSize) - 1; chunk++) + { + ftp->CheckCurrentFile(); + + size = ftp->ReadCurrentFile(data, chunkSize); + AppendToUploadSession(data, size, sessionId, offset); + + offset += size; + ftp->Progress(size); + } + + if (offset < fileSize) + size = ftp->ReadCurrentFile(data, fileSize - offset); + else + size = 0; + + char path[MAX_PATH]; + const wchar_t *serverFolder = ftp->GetServerFolder(); + if (serverFolder) { + wchar_t serverPath[MAX_PATH] = { 0 }; + mir_snwprintf(serverPath, L"%s\\%s", serverFolder, fileName); + PreparePath(serverPath, path); + } + else + PreparePath(fileName, path); + FinishUploadSession(data, size, sessionId, offset, path); + + ftp->Progress(size); + + if (!wcschr(fileName, L'\\')) { + char url[MAX_PATH]; + CreateSharedLink(path, url); + ftp->AppendFormatData(L"%s\r\n", ptrW(mir_utf8decodeW(url))); + } + } while (ftp->NextFile()); + } + catch (Exception &ex) { + Netlib_Logf(hConnection, "%s: %s", MODULE, ex.what()); + ftp->SetStatus(ACKRESULT_FAILED); + return ACKRESULT_FAILED; + } + + ftp->SetStatus(ACKRESULT_SUCCESS); + return ACKRESULT_SUCCESS; +} diff --git a/plugins/CloudFile/src/Services/dropbox_service.h b/plugins/CloudFile/src/Services/dropbox_service.h new file mode 100644 index 0000000000..93b2fb53d3 --- /dev/null +++ b/plugins/CloudFile/src/Services/dropbox_service.h @@ -0,0 +1,33 @@ +#ifndef _CLOUDSERVICE_DROPBOX_H_ +#define _CLOUDSERVICE_DROPBOX_H_ + +class CDropboxService : public CCloudService +{ +private: + static unsigned RequestAccessTokenThread(void *owner, void *param); + static unsigned __stdcall RevokeAccessTokenThread(void *param); + + void HandleJsonError(JSONNode &node); + + char* UploadFile(const char *data, size_t size, char *path); + void StartUploadSession(const char *data, size_t size, char *sessionId); + void AppendToUploadSession(const char *data, size_t size, const char *sessionId, size_t offset); + char* FinishUploadSession(const char *data, size_t size, const char *sessionId, size_t offset, char *path); + void CreateFolder(const char *path); + void CreateSharedLink(const char *path, char *url); + +public: + CDropboxService(HNETLIBUSER hConnection); + + const char* GetModule() const; + const wchar_t* GetText() const; + HANDLE GetIcon() const; + + bool IsLoggedIn(); + void Login(); + void Logout(); + + UINT Upload(FileTransferParam *ftp); +}; + +#endif //_CLOUDSERVICE_DROPBOX_H_
\ No newline at end of file diff --git a/plugins/CloudFile/src/Services/google_api.h b/plugins/CloudFile/src/Services/google_api.h new file mode 100644 index 0000000000..7df5446065 --- /dev/null +++ b/plugins/CloudFile/src/Services/google_api.h @@ -0,0 +1,101 @@ +#ifndef _GDRIVESERVICE_API_H_ +#define _GDRIVESERVICE_API_H_ + +namespace GDriveAPI +{ +#define GOOGLE_OAUTH "https://accounts.google.com/o/oauth2/v2" +#define GDRIVE_API "https://www.googleapis.com/drive/v2/files" + +#define GOOGLE_APP_ID "271668553802-3sd3tubkf165ibgrqnrhe3id8mcgnaf7.apps.googleusercontent.com" +#include "../../../miranda-private-keys/Google/client_secret.h" + + class GetAccessTokenRequest : public HttpRequest + { + public: + GetAccessTokenRequest(const char *code) : + HttpRequest(REQUEST_POST, "https://www.googleapis.com/oauth2/v4/token") + { + AddHeader("Content-Type", "application/x-www-form-urlencoded"); + + CMStringA data(CMStringDataFormat::FORMAT, + "redirect_uri=urn:ietf:wg:oauth:2.0:oob&client_id=%s&client_secret=%s&grant_type=authorization_code&code=%s", + GOOGLE_APP_ID, GOOGLE_CLIENT_SECRET, code); + SetData(data.GetBuffer(), data.GetLength()); + } + }; + + class RevokeAccessTokenRequest : public HttpRequest + { + public: + RevokeAccessTokenRequest(const char *token) : + HttpRequest(REQUEST_POST, GOOGLE_OAUTH "/revoke") + { + AddUrlParameter("token=%s", token); + } + }; + + class StartUploadFileRequest : public HttpRequest + { + public: + StartUploadFileRequest(const char *token) : + HttpRequest(REQUEST_POST, GDRIVE_API) + { + AddBearerAuthHeader(token); + AddHeader("Content-Type", "application/json"); + AddUrlParameter("uploadType=resumable"); + } + }; + + class UploadFileRequest : public HttpRequest + { + public: + UploadFileRequest(const char *token, const char *data, size_t size) : + HttpRequest(REQUEST_POST, GDRIVE_API) + { + AddBearerAuthHeader(token); + AddUrlParameter("uploadType=resumable"); + + SetData(data, size); + } + }; + + class CreateFolderRequest : public HttpRequest + { + public: + CreateFolderRequest(const char *token, const char *path) : + HttpRequest(REQUEST_PUT, GDRIVE_API) + { + AddBearerAuthHeader(token); + AddHeader("Content-Type", "application/json"); + + JSONNode params(JSON_NODE); + params + << JSONNode("name", path) + << JSONNode("mimeType", "pplication/vnd.google-apps.folder"); + + json_string data = params.write(); + SetData(data.c_str(), data.length()); + } + }; + + class ShareRequest : public HttpRequest + { + public: + ShareRequest(const char *token, const char *fileId) : + HttpRequest(REQUEST_PUT, FORMAT, GDRIVE_API "/%s/permissions", fileId) + { + AddBearerAuthHeader(token); + AddHeader("Content-Type", "application/json"); + + JSONNode params(JSON_NODE); + params + << JSONNode("role", "reader") + << JSONNode("type", "anyone"); + + json_string data = params.write(); + SetData(data.c_str(), data.length()); + } + }; +}; + +#endif //_GDRIVESERVICE_API_H_ diff --git a/plugins/CloudFile/src/Services/google_service.cpp b/plugins/CloudFile/src/Services/google_service.cpp new file mode 100644 index 0000000000..f0185bfbb8 --- /dev/null +++ b/plugins/CloudFile/src/Services/google_service.cpp @@ -0,0 +1,216 @@ +#include "..\stdafx.h" +#include "google_api.h" + +CGDriveService::CGDriveService(HNETLIBUSER hConnection) + : CCloudService(hConnection) +{ +} + +const char* CGDriveService::GetModule() const +{ + return "Google"; +} + +const wchar_t* CGDriveService::GetText() const +{ + return L"GDrive"; +} + +HANDLE CGDriveService::GetIcon() const +{ + return GetIconHandle(IDI_GDRIVE); +} + +bool CGDriveService::IsLoggedIn() +{ + ptrA token(db_get_sa(NULL, GetModule(), "TokenSecret")); + return token != NULL; +} + +void CGDriveService::Login() +{ + COAuthDlg(this, GOOGLE_OAUTH "/auth?response_type=code&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive.file&redirect_uri=urn:ietf:wg:oauth:2.0:oob&client_id=" GOOGLE_APP_ID, RequestAccessTokenThread).DoModal(); +} + +void CGDriveService::Logout() +{ + mir_forkthreadex(RevokeAccessTokenThread, this); +} + +unsigned CGDriveService::RequestAccessTokenThread(void *owner, void *param) +{ + HWND hwndDlg = (HWND)param; + CGDriveService *service = (CGDriveService*)owner; + + if (service->IsLoggedIn()) + service->Logout(); + + char requestToken[128]; + GetDlgItemTextA(hwndDlg, IDC_OAUTH_CODE, requestToken, _countof(requestToken)); + + GDriveAPI::GetAccessTokenRequest request(requestToken); + NLHR_PTR response(request.Send(service->hConnection)); + + if (response == NULL || response->resultCode != HTTP_CODE_OK) { + const char *error = response->dataLength + ? response->pData + : service->HttpStatusToError(response->resultCode); + + Netlib_Logf(service->hConnection, "%s: %s", service->GetModule(), error); + ShowNotification(TranslateT("server does not respond"), MB_ICONERROR); + return 0; + } + + JSONNode root = JSONNode::parse(response->pData); + if (root.empty()) { + Netlib_Logf(service->hConnection, "%s: %s", service->GetModule(), service->HttpStatusToError(response->resultCode)); + ShowNotification(TranslateT("server does not respond"), MB_ICONERROR); + return 0; + } + + JSONNode node = root.at("error_description"); + if (!node.isnull()) { + ptrW error_description(mir_a2u_cp(node.as_string().c_str(), CP_UTF8)); + Netlib_Logf(service->hConnection, "%s: %s", service->GetModule(), service->HttpStatusToError(response->resultCode)); + ShowNotification((wchar_t*)error_description, MB_ICONERROR); + return 0; + } + + node = root.at("access_token"); + db_set_s(NULL, service->GetModule(), "TokenSecret", node.as_string().c_str()); + ProtoBroadcastAck(MODULE, NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)ID_STATUS_OFFLINE, (WPARAM)ID_STATUS_ONLINE); + + SetDlgItemTextA(hwndDlg, IDC_OAUTH_CODE, ""); + + EndDialog(hwndDlg, 1); + + return 0; +} + +unsigned CGDriveService::RevokeAccessTokenThread(void *param) +{ + CGDriveService *service = (CGDriveService*)param; + + ptrA token(db_get_sa(NULL, service->GetModule(), "TokenSecret")); + GDriveAPI::RevokeAccessTokenRequest request(token); + NLHR_PTR response(request.Send(service->hConnection)); + + return 0; +} + +void CGDriveService::HandleJsonError(JSONNode &node) +{ + JSONNode error = node.at("error"); + if (!error.isnull()) { + json_string tag = error.at(".tag").as_string(); + throw Exception(tag.c_str()); + } +} + +void CGDriveService::StartUploadFile() +{ + ptrA token(db_get_sa(NULL, GetModule(), "TokenSecret")); + GDriveAPI::StartUploadFileRequest request(token); + NLHR_PTR response(request.Send(hConnection)); + + GetJsonResponse(response); +} + +void CGDriveService::UploadFile(const char *url, const char *data, size_t size) +{ + ptrA token(db_get_sa(NULL, GetModule(), "TokenSecret")); + GDriveAPI::UploadFileRequest request(token, data, size); + NLHR_PTR response(request.Send(hConnection)); + + if (response == NULL) + throw Exception(HttpStatusToError()); + + if (response->resultCode >= HTTP_CODE_OK && + response->resultCode <= HTTP_CODE_MULTIPLE_CHOICES) { + return; + } + + if (response->dataLength) + throw Exception(response->pData); + throw Exception(HttpStatusToError(response->resultCode)); +} + +void CGDriveService::CreateFolder(const char *path) +{ + ptrA token(db_get_sa(NULL, GetModule(), "TokenSecret")); + GDriveAPI::CreateFolderRequest request(token, path); + NLHR_PTR response(request.Send(hConnection)); + + GetJsonResponse(response); +} + +void CGDriveService::CreateSharedLink(const char *path, char *url) +{ + ptrA token(db_get_sa(NULL, GetModule(), "TokenSecret")); + GDriveAPI::ShareRequest request(token, path); + NLHR_PTR response(request.Send(hConnection)); + + JSONNode root = GetJsonResponse(response); + JSONNode link = root.at("href"); + mir_strcpy(url, link.as_string().c_str()); +} + +UINT CGDriveService::Upload(FileTransferParam *ftp) +{ + if (!IsLoggedIn()) + Login(); + + if (!IsLoggedIn()) { + ftp->SetStatus(ACKRESULT_FAILED); + return ACKRESULT_FAILED; + } + + try { + const wchar_t *folderName = ftp->GetFolderName(); + if (folderName) { + char path[MAX_PATH], link[MAX_PATH]; + PreparePath(folderName, path); + CreateFolder(path); + CreateSharedLink(path, link); + ftp->AppendFormatData(L"%s\r\n", ptrW(mir_utf8decodeW(link))); + } + + ftp->FirstFile(); + do + { + const wchar_t *fileName = ftp->GetCurrentRelativeFilePath(); + uint64_t fileSize = ftp->GetCurrentFileSize(); + + char path[MAX_PATH]; + const wchar_t *serverFolder = ftp->GetServerFolder(); + if (serverFolder) { + wchar_t serverPath[MAX_PATH] = { 0 }; + mir_snwprintf(serverPath, L"%s\\%s", serverFolder, fileName); + PreparePath(serverPath, path); + } + else + PreparePath(fileName, path); + StartUploadFile(); + + mir_ptr<char>data((char*)mir_calloc(fileSize)); + size_t size = ftp->ReadCurrentFile(data, fileSize); + UploadFile("", data, size); + + ftp->Progress(size); + + if (!wcschr(fileName, L'\\')) { + char url[MAX_PATH]; + CreateSharedLink(path, url); + ftp->AppendFormatData(L"%s\r\n", ptrW(mir_utf8decodeW(url))); + } + } while (ftp->NextFile()); + } + catch (Exception &ex) { + Netlib_Logf(hConnection, "%s: %s", MODULE, ex.what()); + ftp->SetStatus(ACKRESULT_FAILED); + return ACKRESULT_FAILED; + } + + ftp->SetStatus(ACKRESULT_SUCCESS); + return ACKRESULT_SUCCESS; +} diff --git a/plugins/CloudFile/src/Services/google_service.h b/plugins/CloudFile/src/Services/google_service.h new file mode 100644 index 0000000000..db72ef9dee --- /dev/null +++ b/plugins/CloudFile/src/Services/google_service.h @@ -0,0 +1,31 @@ +#ifndef _CLOUDFILE_GDRIVE_H_ +#define _CLOUDFILE_GDRIVE_H_ + +class CGDriveService : public CCloudService +{ +private: + static unsigned RequestAccessTokenThread(void *owner, void *param); + static unsigned __stdcall RevokeAccessTokenThread(void *param); + + void HandleJsonError(JSONNode &node); + + void StartUploadFile(); + void UploadFile(const char *url, const char *data, size_t size); + void CreateFolder(const char *path); + void CreateSharedLink(const char *path, char *url); + +public: + CGDriveService(HNETLIBUSER hConnection); + + const char* GetModule() const; + const wchar_t* GetText() const; + HANDLE GetIcon() const; + + bool IsLoggedIn(); + void Login(); + void Logout(); + + UINT Upload(FileTransferParam *ftp); +}; + +#endif //_CLOUDFILE_GDRIVE_H_
\ No newline at end of file diff --git a/plugins/CloudFile/src/Services/yandex_api.h b/plugins/CloudFile/src/Services/yandex_api.h new file mode 100644 index 0000000000..7548f6bd14 --- /dev/null +++ b/plugins/CloudFile/src/Services/yandex_api.h @@ -0,0 +1,84 @@ +#ifndef _YANDEXSERVICE_API_H_ +#define _YANDEXSERVICE_API_H_ + +namespace YandexAPI +{ +#define YANDEX_OAUTH "https://oauth.yandex.ru" +#define YADISK_API "https://cloud-api.yandex.net/v1/disk/resources" + +#define YANDEX_APP_ID "c311a5967cae4efa88d1af97d01ea0e8" +#include "../../../miranda-private-keys/Yandex/client_secret.h" + + class GetAccessTokenRequest : public HttpRequest + { + public: + GetAccessTokenRequest(const char *code) : + HttpRequest(REQUEST_POST, YANDEX_OAUTH "/token") + { + AddHeader("Content-Type", "application/x-www-form-urlencoded"); + + CMStringA data(CMStringDataFormat::FORMAT, + "client_id=%s&client_secret=%s&grant_type=authorization_code&code=%s", + YANDEX_APP_ID, YADISK_CLIENT_SECRET, code); + SetData(data.GetBuffer(), data.GetLength()); + } + }; + + class RevokeAccessTokenRequest : public HttpRequest + { + public: + RevokeAccessTokenRequest(const char *token) : + HttpRequest(REQUEST_POST, YANDEX_OAUTH "/token/revoke") + { + AddOAuthHeader(token); + } + }; + + class GetUploadUrlRequest : public HttpRequest + { + public: + GetUploadUrlRequest(const char *token, const char *path) : + HttpRequest(REQUEST_GET, YADISK_API "/upload") + { + AddOAuthHeader(token); + AddUrlParameter("path=%s", ptrA(mir_urlEncode(path))); + AddUrlParameter("overwrite=true"); + } + }; + + class UploadFileRequest : public HttpRequest + { + public: + UploadFileRequest(const char *token, const char *url, const char *data, size_t size) : + HttpRequest(REQUEST_PUT, url) + { + AddOAuthHeader(token); + + SetData(data, size); + } + }; + + class CreateFolderRequest : public HttpRequest + { + public: + CreateFolderRequest(const char *token, const char *path) : + HttpRequest(REQUEST_PUT, YADISK_API) + { + AddOAuthHeader(token); + AddUrlParameter("path=%s", ptrA(mir_urlEncode(path))); + } + }; + + class PublishRequest : public HttpRequest + { + public: + PublishRequest(const char *token, const char *path) : + HttpRequest(REQUEST_PUT, YADISK_API "/publish") + { + AddOAuthHeader(token); + AddUrlParameter("path=%s", ptrA(mir_urlEncode(path))); + } + }; +}; + +#endif //_YANDEXSERVICE_API_H_ diff --git a/plugins/CloudFile/src/Services/yandex_service.cpp b/plugins/CloudFile/src/Services/yandex_service.cpp new file mode 100644 index 0000000000..03f34a432e --- /dev/null +++ b/plugins/CloudFile/src/Services/yandex_service.cpp @@ -0,0 +1,219 @@ +#include "..\stdafx.h" +#include "yandex_api.h" + +CYandexService::CYandexService(HNETLIBUSER hConnection) + : CCloudService(hConnection) +{ +} + +const char* CYandexService::GetModule() const +{ + return "Yandex"; +} + +const wchar_t* CYandexService::GetText() const +{ + return L"ßíäåêñ.Äèñê"; +} + +HANDLE CYandexService::GetIcon() const +{ + return NULL; +} + +bool CYandexService::IsLoggedIn() +{ + ptrA token(db_get_sa(NULL, GetModule(), "TokenSecret")); + return token != NULL; +} + +void CYandexService::Login() +{ + COAuthDlg(this, YANDEX_OAUTH "/authorize?response_type=code&client_id=" YANDEX_APP_ID, RequestAccessTokenThread).DoModal(); +} + +void CYandexService::Logout() +{ + mir_forkthreadex(RevokeAccessTokenThread, this); +} + +unsigned CYandexService::RequestAccessTokenThread(void *owner, void *param) +{ + HWND hwndDlg = (HWND)param; + CYandexService *service = (CYandexService*)owner; + + if (service->IsLoggedIn()) + service->Logout(); + + char requestToken[128]; + GetDlgItemTextA(hwndDlg, IDC_OAUTH_CODE, requestToken, _countof(requestToken)); + + YandexAPI::GetAccessTokenRequest request(requestToken); + NLHR_PTR response(request.Send(service->hConnection)); + + if (response == NULL || response->resultCode != HTTP_CODE_OK) { + const char *error = response->dataLength + ? response->pData + : service->HttpStatusToError(response->resultCode); + + Netlib_Logf(service->hConnection, "%s: %s", service->GetModule(), error); + ShowNotification(TranslateT("server does not respond"), MB_ICONERROR); + return 0; + } + + JSONNode root = JSONNode::parse(response->pData); + if (root.empty()) { + Netlib_Logf(service->hConnection, "%s: %s", service->GetModule(), service->HttpStatusToError(response->resultCode)); + ShowNotification(TranslateT("server does not respond"), MB_ICONERROR); + return 0; + } + + JSONNode node = root.at("error_description"); + if (!node.isnull()) { + ptrW error_description(mir_a2u_cp(node.as_string().c_str(), CP_UTF8)); + Netlib_Logf(service->hConnection, "%s: %s", service->GetModule(), service->HttpStatusToError(response->resultCode)); + ShowNotification((wchar_t*)error_description, MB_ICONERROR); + return 0; + } + + node = root.at("access_token"); + db_set_s(NULL, service->GetModule(), "TokenSecret", node.as_string().c_str()); + ProtoBroadcastAck(MODULE, NULL, ACKTYPE_STATUS, ACKRESULT_SUCCESS, (HANDLE)ID_STATUS_OFFLINE, (WPARAM)ID_STATUS_ONLINE); + + SetDlgItemTextA(hwndDlg, IDC_OAUTH_CODE, ""); + + EndDialog(hwndDlg, 1); + + return 0; +} + +unsigned CYandexService::RevokeAccessTokenThread(void *param) +{ + CYandexService *service = (CYandexService*)param; + + ptrA token(db_get_sa(NULL, service->GetModule(), "TokenSecret")); + YandexAPI::RevokeAccessTokenRequest request(token); + NLHR_PTR response(request.Send(service->hConnection)); + + return 0; +} + +void CYandexService::HandleJsonError(JSONNode &node) +{ + JSONNode error = node.at("error"); + if (!error.isnull()) { + json_string tag = error.at(".tag").as_string(); + throw Exception(tag.c_str()); + } +} + +void CYandexService::GetUploadUrl(char *path, char *url) +{ + ptrA token(db_get_sa(NULL, GetModule(), "TokenSecret")); + YandexAPI::GetUploadUrlRequest request(token, path); + NLHR_PTR response(request.Send(hConnection)); + + JSONNode root = GetJsonResponse(response); + JSONNode node = root.at("href"); + mir_strcpy(url, node.as_string().c_str()); +} + +void CYandexService::UploadFile(const char *url, const char *data, size_t size) +{ + ptrA token(db_get_sa(NULL, GetModule(), "TokenSecret")); + YandexAPI::UploadFileRequest request(token, url, data, size); + NLHR_PTR response(request.Send(hConnection)); + + if (response == NULL) + throw Exception(HttpStatusToError()); + + if (response->resultCode >= HTTP_CODE_OK && + response->resultCode <= HTTP_CODE_MULTIPLE_CHOICES) { + return; + } + + if (response->dataLength) + throw Exception(response->pData); + throw Exception(HttpStatusToError(response->resultCode)); +} + +void CYandexService::CreateFolder(const char *path) +{ + ptrA token(db_get_sa(NULL, GetModule(), "TokenSecret")); + YandexAPI::CreateFolderRequest request(token, path); + NLHR_PTR response(request.Send(hConnection)); + + GetJsonResponse(response); +} + +void CYandexService::CreateSharedLink(const char *path, char *url) +{ + ptrA token(db_get_sa(NULL, GetModule(), "TokenSecret")); + YandexAPI::PublishRequest request(token, path); + NLHR_PTR response(request.Send(hConnection)); + + JSONNode root = GetJsonResponse(response); + JSONNode link = root.at("href"); + mir_strcpy(url, link.as_string().c_str()); +} + +UINT CYandexService::Upload(FileTransferParam *ftp) +{ + if (!IsLoggedIn()) + Login(); + + if (!IsLoggedIn()) { + ftp->SetStatus(ACKRESULT_FAILED); + return ACKRESULT_FAILED; + } + + try { + const wchar_t *folderName = ftp->GetFolderName(); + if (folderName) { + char path[MAX_PATH], link[MAX_PATH]; + PreparePath(folderName, path); + CreateFolder(path); + CreateSharedLink(path, link); + ftp->AppendFormatData(L"%s\r\n", ptrW(mir_utf8decodeW(link))); + } + + ftp->FirstFile(); + do + { + const wchar_t *fileName = ftp->GetCurrentRelativeFilePath(); + uint64_t fileSize = ftp->GetCurrentFileSize(); + + char path[MAX_PATH]; + const wchar_t *serverFolder = ftp->GetServerFolder(); + if (serverFolder) { + wchar_t serverPath[MAX_PATH] = { 0 }; + mir_snwprintf(serverPath, L"%s\\%s", serverFolder, fileName); + PreparePath(serverPath, path); + } + else + PreparePath(fileName, path); + char url[MAX_PATH]; + GetUploadUrl(path, url); + + mir_ptr<char>data((char*)mir_calloc(fileSize)); + size_t size = ftp->ReadCurrentFile(data, fileSize); + UploadFile(url, data, size); + + ftp->Progress(size); + + if (!wcschr(fileName, L'\\')) { + char url[MAX_PATH]; + CreateSharedLink(path, url); + ftp->AppendFormatData(L"%s\r\n", ptrW(mir_utf8decodeW(url))); + } + } while (ftp->NextFile()); + } + catch (Exception &ex) { + Netlib_Logf(hConnection, "%s: %s", MODULE, ex.what()); + ftp->SetStatus(ACKRESULT_FAILED); + return ACKRESULT_FAILED; + } + + ftp->SetStatus(ACKRESULT_SUCCESS); + return ACKRESULT_SUCCESS; +} diff --git a/plugins/CloudFile/src/Services/yandex_service.h b/plugins/CloudFile/src/Services/yandex_service.h new file mode 100644 index 0000000000..a3a84735c4 --- /dev/null +++ b/plugins/CloudFile/src/Services/yandex_service.h @@ -0,0 +1,31 @@ +#ifndef _CLOUDFILE_YANDEX_H_ +#define _CLOUDFILE_YANDEX_H_ + +class CYandexService : public CCloudService +{ +private: + static unsigned RequestAccessTokenThread(void *owner, void *param); + static unsigned __stdcall RevokeAccessTokenThread(void *param); + + void HandleJsonError(JSONNode &node); + + void GetUploadUrl(char *path, char *url); + void UploadFile(const char *url, const char *data, size_t size); + void CreateFolder(const char *path); + void CreateSharedLink(const char *path, char *url); + +public: + CYandexService(HNETLIBUSER hConnection); + + const char* GetModule() const; + const wchar_t* GetText() const; + HANDLE GetIcon() const; + + bool IsLoggedIn(); + void Login(); + void Logout(); + + UINT Upload(FileTransferParam *ftp); +}; + +#endif //_CLOUDFILE_YANDEX_H_
\ No newline at end of file diff --git a/plugins/CloudFile/src/cloud_service.cpp b/plugins/CloudFile/src/cloud_service.cpp new file mode 100644 index 0000000000..9e57dc14aa --- /dev/null +++ b/plugins/CloudFile/src/cloud_service.cpp @@ -0,0 +1,164 @@ +#include "stdafx.h" + +static int CompareServices(const CCloudService *p1, const CCloudService *p2) +{ + return mir_strcmp(p1->GetModule(), p2->GetModule()); +} + +LIST<CCloudService> Services(10, CompareServices); + +void InitServices() +{ + Services.insert(new CDropboxService(hNetlibConnection)); + //Services.insert(new CGDriveService(hNetlibConnection)); + Services.insert(new CYandexService(hNetlibConnection)); + + PROTOCOLDESCRIPTOR pd = { sizeof(pd) }; + + size_t count = Services.getCount(); + for (size_t i = 0; i < count; i++) { + CCloudService *service = Services[i]; + + CMStringA moduleName(CMStringDataFormat::FORMAT, "%s/%s", MODULE, service->GetModule()); + pd.type = PROTOTYPE_VIRTUAL; + pd.szName = moduleName.GetBuffer(); + Proto_RegisterModule(&pd); + + CMStringA serviceName(CMStringDataFormat::FORMAT, "%s%s", moduleName, PSS_FILE); + CreateServiceFunctionObj(serviceName, ProtoSendFile, service); + + moduleName = CMStringA(CMStringDataFormat::FORMAT, "%s/%s/Interceptor", MODULE, service->GetModule()); + pd.szName = moduleName.GetBuffer(); + pd.type = PROTOTYPE_FILTER; + Proto_RegisterModule(&pd); + + serviceName = CMStringA(CMStringDataFormat::FORMAT, "%s%s", moduleName, PSS_FILE); + CreateServiceFunctionObj(serviceName, ProtoSendFileInterceptor, service); + } +} + +CCloudService::CCloudService(HNETLIBUSER hConnection) + : hConnection(hConnection) +{ +} + +void CCloudService::SendToContact(MCONTACT hContact, const wchar_t *data) +{ + const char *szProto = GetContactProto(hContact); + if (db_get_b(hContact, szProto, "ChatRoom", 0) == TRUE) { + ptrW tszChatRoom(db_get_wsa(hContact, szProto, "ChatRoomID")); + Chat_SendUserMessage(szProto, tszChatRoom, data); + return; + } + + char *message = mir_utf8encodeW(data); + if (ProtoChainSend(hContact, PSS_MESSAGE, 0, (LPARAM)message) != ACKRESULT_FAILED) + AddEventToDb(hContact, EVENTTYPE_MESSAGE, DBEF_UTF | DBEF_SENT, (DWORD)mir_strlen(message), (PBYTE)message); +} + +void CCloudService::PasteToInputArea(MCONTACT hContact, const wchar_t *data) +{ + CallService(MS_MSG_SENDMESSAGEW, hContact, (LPARAM)data); +} + +void CCloudService::PasteToClipboard(const wchar_t *data) +{ + if (OpenClipboard(NULL)) { + EmptyClipboard(); + + size_t size = sizeof(wchar_t) * (mir_wstrlen(data) + 1); + HGLOBAL hClipboardData = GlobalAlloc(NULL, size); + if (hClipboardData) { + wchar_t *pchData = (wchar_t*)GlobalLock(hClipboardData); + if (pchData) { + memcpy(pchData, (wchar_t*)data, size); + GlobalUnlock(hClipboardData); + SetClipboardData(CF_UNICODETEXT, hClipboardData); + } + } + CloseClipboard(); + } +} + +void CCloudService::Report(MCONTACT hContact, const wchar_t *data) +{ + if (db_get_b(NULL, MODULE, "UrlAutoSend", 1)) + SendToContact(hContact, data); + + if (db_get_b(NULL, MODULE, "UrlPasteToMessageInputArea", 0)) + PasteToInputArea(hContact, data); + + if (db_get_b(NULL, MODULE, "UrlCopyToClipboard", 0)) + PasteToClipboard(data); +} + +char* CCloudService::PreparePath(const char *oldPath, char *newPath) +{ + if (oldPath == NULL) + mir_strcpy(newPath, ""); + else if (*oldPath != '/') + { + CMStringA result("/"); + result.Append(oldPath); + result.Replace("\\", "/"); + mir_strcpy(newPath, result); + } + else + mir_strcpy(newPath, oldPath); + return newPath; +} + +char* CCloudService::PreparePath(const wchar_t *oldPath, char *newPath) +{ + return PreparePath(ptrA(mir_utf8encodeW(oldPath)), newPath); +} + +char* CCloudService::HttpStatusToError(int status) +{ + switch (status) { + case HTTP_CODE_OK: + return "OK"; + case HTTP_CODE_BAD_REQUEST: + return "Bad input parameter. Error message should indicate which one and why"; + case HTTP_CODE_UNAUTHORIZED: + return "Bad or expired token. This can happen if the user or Dropbox revoked or expired an access token. To fix, you should re-authenticate the user"; + case HTTP_CODE_FORBIDDEN: + return "Bad OAuth request (wrong consumer key, bad nonce, expired timestamp...). Unfortunately, re-authenticating the user won't help here"; + case HTTP_CODE_NOT_FOUND: + return "File or folder not found at the specified path"; + case HTTP_CODE_METHOD_NOT_ALLOWED: + return "Request method not expected (generally should be GET or POST)"; + case HTTP_CODE_TOO_MANY_REQUESTS: + return "Your app is making too many requests and is being rate limited. 429s can trigger on a per-app or per-user basis"; + case HTTP_CODE_SERVICE_UNAVAILABLE: + return "If the response includes the Retry-After header, this means your OAuth 1.0 app is being rate limited. Otherwise, this indicates a transient server error, and your app should retry its request."; + } + + return "Unknown error"; +} + +void CCloudService::HandleHttpError(NETLIBHTTPREQUEST *response) +{ + if (response == NULL) + throw Exception(HttpStatusToError()); + + if (response->resultCode != HTTP_CODE_OK && + response->resultCode != HTTP_CODE_CONFLICT) { + if (response->dataLength) + throw Exception(response->pData); + throw Exception(HttpStatusToError(response->resultCode)); + } +} + +JSONNode CCloudService::GetJsonResponse(NETLIBHTTPREQUEST *response) +{ + HandleHttpError(response); + + JSONNode root = JSONNode::parse(response->pData); + if (root.isnull()) + throw Exception(HttpStatusToError()); + + HandleJsonError(root); + + return root; +}
\ No newline at end of file diff --git a/plugins/CloudFile/src/cloud_service.h b/plugins/CloudFile/src/cloud_service.h new file mode 100644 index 0000000000..8d52e4cea0 --- /dev/null +++ b/plugins/CloudFile/src/cloud_service.h @@ -0,0 +1,41 @@ +#ifndef _CLOUD_SERVICE_H_ +#define _CLOUD_SERVICE_H_ + +class CCloudService +{ +protected: + HNETLIBUSER hConnection; + + // utils + char* PreparePath(const char *oldPath, char *newPath); + char* PreparePath(const wchar_t *oldPath, char *newPath); + + virtual char* HttpStatusToError(int status = 0); + virtual void HandleHttpError(NETLIBHTTPREQUEST *response); + virtual void HandleJsonError(JSONNode &node) = 0; + + JSONNode GetJsonResponse(NETLIBHTTPREQUEST *response); + +public: + std::map<MCONTACT, HWND> InterceptedContacts; + + CCloudService(HNETLIBUSER hConnection); + + virtual const char* GetModule() const = 0; + virtual const wchar_t* GetText() const = 0; + virtual HANDLE GetIcon() const = 0; + + virtual bool IsLoggedIn() = 0; + virtual void Login() = 0; + virtual void Logout() = 0; + + virtual UINT Upload(FileTransferParam *ftp) = 0; + + void SendToContact(MCONTACT hContact, const wchar_t *data); + void PasteToInputArea(MCONTACT hContact, const wchar_t *data); + void PasteToClipboard(const wchar_t *data); + + void Report(MCONTACT hContact, const wchar_t *data); +}; + +#endif //_CLOUD_SERVICE_H_
\ No newline at end of file diff --git a/plugins/CloudFile/src/events.cpp b/plugins/CloudFile/src/events.cpp new file mode 100644 index 0000000000..815a5f0b96 --- /dev/null +++ b/plugins/CloudFile/src/events.cpp @@ -0,0 +1,62 @@ +#include "stdafx.h" + +int OnModulesLoaded(WPARAM, LPARAM) +{ + HookEvent(ME_OPT_INITIALISE, OnOptionsInitialized); + // srfile + size_t count = Services.getCount(); + for (size_t i = 0; i < count; i++) { + CCloudService *service = Services[i]; + + HookEventObj(ME_FILEDLG_CANCELED, OnFileDialogCanceled, service); + } + // menus + HookEvent(ME_CLIST_PREBUILDCONTACTMENU, OnPrebuildContactMenu); + // srmm + HookEvent(ME_MSG_TOOLBARLOADED, OnSrmmToolbarLoaded); + HookEvent(ME_MSG_WINDOWEVENT, OnSrmmWindowOpened); + HookEvent(ME_MSG_BUTTONPRESSED, OnSrmmButtonPressed); + + return 0; +} + +int OnProtoAck(WPARAM, LPARAM lParam) +{ + ACKDATA *ack = (ACKDATA*)lParam; + + if (!mir_strcmp(ack->szModule, MODULE)) + return 0; // don't rebroadcast our own acks + + if (ack->type == ACKTYPE_STATUS) { + WORD status = ack->lParam; + bool canSendOffline = (CallProtoService(ack->szModule, PS_GETCAPS, PFLAGNUM_4, 0) & PF4_IMSENDOFFLINE) > 0; + + for (MCONTACT hContact = db_find_first(ack->szModule); hContact; hContact = db_find_next(hContact, ack->szModule)) { + MessageWindowData msgw; + if (!Srmm_GetWindowData(hContact, msgw) && msgw.uState & MSG_WINDOW_STATE_EXISTS) { + BBButton bbd = {}; + bbd.pszModuleName = MODULE; + bbd.dwButtonID = BBB_ID_FILE_SEND; + bbd.bbbFlags = BBSF_RELEASED; + + if (status == ID_STATUS_OFFLINE && !canSendOffline) + bbd.bbbFlags = BBSF_DISABLED; + + Srmm_SetButtonState(hContact, &bbd); + } + } + } + + return 0; +} + +int OnFileDialogCanceled(void* obj, WPARAM hContact, LPARAM) +{ + CCloudService *service = (CCloudService*)obj; + + auto it = service->InterceptedContacts.find(hContact); + if (it != service->InterceptedContacts.end()) + service->InterceptedContacts.erase(it); + + return 0; +} diff --git a/plugins/CloudFile/src/file_transfer.h b/plugins/CloudFile/src/file_transfer.h new file mode 100644 index 0000000000..16f515f856 --- /dev/null +++ b/plugins/CloudFile/src/file_transfer.h @@ -0,0 +1,245 @@ +#ifndef _FILE_TRANSFER_H_ +#define _FILE_TRANSFER_H_ + +class FileTransferParam +{ +private: + static ULONG hFileProcess; + + ULONG id; + FILE *hFile; + PROTOFILETRANSFERSTATUS pfts; + + bool isTerminated; + + const wchar_t* folderName; + int relativePathStart; + + CMStringW serverFolder; + + CMStringW data; + +public: + FileTransferParam(MCONTACT hContact) + { + hFile = NULL; + id = InterlockedIncrement(&hFileProcess); + + isTerminated = false; + + folderName = NULL; + relativePathStart = 0; + + pfts.cbSize = sizeof(this->pfts); + pfts.flags = PFTS_UNICODE | PFTS_SENDING; + pfts.hContact = hContact; + pfts.currentFileNumber = -1; + pfts.currentFileProgress = 0; + pfts.currentFileSize = 0; + pfts.currentFileTime = 0; + pfts.totalBytes = 0; + pfts.totalFiles = 0; + pfts.totalProgress = 0; + pfts.ptszFiles = (wchar_t**)mir_alloc(sizeof(wchar_t*) * (pfts.totalFiles + 1)); + pfts.ptszFiles[pfts.totalFiles] = NULL; + pfts.tszWorkingDir = NULL; + pfts.tszCurrentFile = NULL; + + ProtoBroadcastAck(MODULE, pfts.hContact, ACKTYPE_FILE, ACKRESULT_INITIALISING, (HANDLE)id, 0); + } + + ~FileTransferParam() + { + CloseCurrentFile(); + + if (pfts.tszWorkingDir) + mir_free(pfts.tszWorkingDir); + + if (pfts.pszFiles) + { + for (int i = 0; pfts.pszFiles[i]; i++) + { + if (pfts.pszFiles[i]) mir_free(pfts.pszFiles[i]); + } + mir_free(pfts.pszFiles); + } + } + + ULONG GetId() const + { + return id; + } + + MCONTACT GetHContact() const + { + return pfts.hContact; + } + + const wchar_t* GetData() const + { + if (data.IsEmpty()) + return NULL; + return data; + } + + void Terminate() + { + isTerminated = true; + } + + void SetWorkingDirectory(const wchar_t *path) + { + relativePathStart = wcsrchr(path, '\\') - path + 1; + pfts.tszWorkingDir = (wchar_t*)mir_calloc(sizeof(wchar_t) * relativePathStart); + mir_wstrncpy(pfts.tszWorkingDir, path, relativePathStart); + if (PathIsDirectory(path)) + folderName = wcsrchr(path, '\\') + 1; + } + + void SetServerFolder(const wchar_t *path) + { + if (path) + serverFolder = path; + } + + const wchar_t* GetServerFolder() const + { + if (serverFolder.IsEmpty()) + return NULL; + return serverFolder; + } + + const wchar_t* GetFolderName() const + { + return folderName; + } + + void AddFile(const wchar_t *path) + { + pfts.ptszFiles = (wchar_t**)mir_realloc(pfts.ptszFiles, sizeof(wchar_t*) * (pfts.totalFiles + 2)); + pfts.ptszFiles[pfts.totalFiles++] = mir_wstrdup(path); + pfts.ptszFiles[pfts.totalFiles] = NULL; + + FILE *file = _wfopen(path, L"rb"); + if (file != NULL) { + _fseeki64(file, 0, SEEK_END); + pfts.totalBytes += _ftelli64(file); + fclose(file); + } + } + + void AppendFormatData(const wchar_t *format, ...) + { + va_list args; + va_start(args, format); + data.AppendFormatV(format, args); + va_end(args); + } + + const wchar_t* GetCurrentFilePath() const + { + return pfts.ptszFiles[pfts.currentFileNumber]; + } + + const wchar_t* GetCurrentRelativeFilePath() const + { + return &GetCurrentFilePath()[relativePathStart]; + } + + const wchar_t* GetCurrentFileName() const + { + return wcsrchr(pfts.ptszFiles[pfts.currentFileNumber], '\\') + 1; + } + + void OpenCurrentFile() + { + hFile = _wfopen(GetCurrentFilePath(), L"rb"); + if (!hFile) + throw Exception("Unable to open file"); + _fseeki64(hFile, 0, SEEK_END); + pfts.currentFileSize = _ftelli64(hFile); + rewind(hFile); + } + + size_t ReadCurrentFile(void *buffer, size_t count) + { + return fread(buffer, sizeof(char), count, hFile); + } + + void CheckCurrentFile() + { + if (ferror(hFile)) + throw Exception("Error while file sending"); + + if (isTerminated) + throw Exception("Transfer was terminated"); + } + + void CloseCurrentFile() + { + if (hFile != NULL) + { + fclose(hFile); + hFile = NULL; + } + } + + const uint64_t GetCurrentFileSize() const + { + return pfts.currentFileSize; + } + + const uint64_t GetCurrentFileChunkSize() const + { + int chunkSize = 1024 * 1024; + if (pfts.currentFileSize < chunkSize) + chunkSize = min(pfts.currentFileSize, chunkSize / 4); + else if (pfts.currentFileSize > 20 * chunkSize) + chunkSize = chunkSize * 4; + return chunkSize; + } + + void Progress(size_t count) + { + pfts.currentFileProgress += count; + pfts.totalProgress += count; + ProtoBroadcastAck(MODULE, pfts.hContact, ACKTYPE_FILE, ACKRESULT_DATA, (HANDLE)id, (LPARAM)&pfts); + } + + void FirstFile() + { + CloseCurrentFile(); + + pfts.currentFileNumber = 0; + pfts.currentFileProgress = 0; + pfts.tszCurrentFile = wcsrchr(pfts.ptszFiles[pfts.currentFileNumber], '\\') + 1; + ProtoBroadcastAck(MODULE, pfts.hContact, ACKTYPE_FILE, ACKRESULT_DATA, (HANDLE)id, (LPARAM)&pfts); + + OpenCurrentFile(); + CheckCurrentFile(); + } + + bool NextFile() + { + CloseCurrentFile(); + + if (++pfts.currentFileNumber == pfts.totalFiles) + return false; + + pfts.currentFileProgress = 0; + pfts.tszCurrentFile = wcsrchr(pfts.ptszFiles[pfts.currentFileNumber], '\\') + 1; + ProtoBroadcastAck(MODULE, pfts.hContact, ACKTYPE_FILE, ACKRESULT_NEXTFILE, (HANDLE)id, 0); + + OpenCurrentFile(); + CheckCurrentFile(); + + return true; + } + + void SetStatus(int status, LPARAM param = 0) + { + ProtoBroadcastAck(MODULE, pfts.hContact, ACKTYPE_FILE, status, (HANDLE)id, param); + } +}; + +#endif //_FILE_TRANSFER_H_
\ No newline at end of file diff --git a/plugins/CloudFile/src/http_request.h b/plugins/CloudFile/src/http_request.h new file mode 100644 index 0000000000..1308ae2291 --- /dev/null +++ b/plugins/CloudFile/src/http_request.h @@ -0,0 +1,166 @@ +#ifndef _HTTP_REQUEST_H_ +#define _HTTP_REQUEST_H_ + +class HttpRequestException +{ + CMStringA message; + +public: + HttpRequestException(const char *message) : + message(message) + { + } + + const char* what() const throw() + { + return message.c_str(); + } +}; + +class HttpRequest : protected NETLIBHTTPREQUEST +{ +private: + CMStringA m_szUrl; + + void Init(int type) + { + cbSize = sizeof(NETLIBHTTPREQUEST); + requestType = type; + flags = NLHRF_HTTP11 | NLHRF_SSL | NLHRF_NODUMPSEND | NLHRF_DUMPASTEXT; + szUrl = NULL; + headers = NULL; + headersCount = 0; + pData = NULL; + dataLength = 0; + resultCode = 0; + szResultDescr = NULL; + nlc = NULL; + timeout = 0; + } + +protected: + enum HttpRequestUrlFormat { FORMAT }; + + void AddHeader(LPCSTR szName, LPCSTR szValue) + { + headers = (NETLIBHTTPHEADER*)mir_realloc(headers, sizeof(NETLIBHTTPHEADER) * (headersCount + 1)); + headers[headersCount].szName = mir_strdup(szName); + headers[headersCount].szValue = mir_strdup(szValue); + headersCount++; + } + + void AddBasicAuthHeader(LPCSTR szLogin, LPCSTR szPassword) + { + char cPair[128]; + mir_snprintf( + cPair, + _countof(cPair), + "%s:%s", + szLogin, + szPassword); + + char *ePair = (char *)mir_base64_encode((BYTE*)cPair, (UINT)mir_strlen(cPair)); + + char value[128]; + mir_snprintf( + value, + _countof(value), + "Basic %s", + ePair); + + mir_free(ePair); + + headers = (NETLIBHTTPHEADER*)mir_realloc(headers, sizeof(NETLIBHTTPHEADER)*(headersCount + 1)); + headers[headersCount].szName = mir_strdup("Authorization"); + headers[headersCount].szValue = mir_strdup(value); + headersCount++; + } + + void AddBearerAuthHeader(LPCSTR szValue) + { + char value[128]; + mir_snprintf( + value, + _countof(value), + "Bearer %s", + szValue); + + headers = (NETLIBHTTPHEADER*)mir_realloc(headers, sizeof(NETLIBHTTPHEADER)*(headersCount + 1)); + headers[headersCount].szName = mir_strdup("Authorization"); + headers[headersCount].szValue = mir_strdup(value); + headersCount++; + } + + void AddOAuthHeader(LPCSTR szValue) + { + char value[128]; + mir_snprintf( + value, + _countof(value), + "OAuth %s", + szValue); + + headers = (NETLIBHTTPHEADER*)mir_realloc(headers, sizeof(NETLIBHTTPHEADER)*(headersCount + 1)); + headers[headersCount].szName = mir_strdup("Authorization"); + headers[headersCount].szValue = mir_strdup(value); + headersCount++; + } + + void AddUrlParameter(const char *urlFormat, ...) + { + va_list urlArgs; + va_start(urlArgs, urlFormat); + m_szUrl += m_szUrl.Find('?') == -1 ? '?' : '&'; + m_szUrl.AppendFormatV(urlFormat, urlArgs); + va_end(urlArgs); + } + + void SetData(const char *data, size_t size) + { + if (pData != NULL) + mir_free(pData); + + dataLength = (int)size; + pData = (char*)mir_alloc(size); + memcpy(pData, data, size); + } + +public: + HttpRequest(int type, LPCSTR url) + { + Init(type); + + m_szUrl = url; + } + + HttpRequest(int type, HttpRequestUrlFormat, LPCSTR urlFormat, ...) + { + Init(type); + + va_list formatArgs; + va_start(formatArgs, urlFormat); + m_szUrl.AppendFormatV(urlFormat, formatArgs); + va_end(formatArgs); + } + + ~HttpRequest() + { + for (int i = 0; i < headersCount; i++) + { + mir_free(headers[i].szName); + mir_free(headers[i].szValue); + } + mir_free(headers); + if (pData) + mir_free(pData); + } + + NETLIBHTTPREQUEST* Send(HNETLIBUSER hConnection) + { + m_szUrl.Replace('\\', '/'); + szUrl = m_szUrl.GetBuffer(); + return Netlib_HttpTransaction(hConnection, this); + } +}; + +#endif //_HTTP_REQUEST_H_
\ No newline at end of file diff --git a/plugins/CloudFile/src/icons.cpp b/plugins/CloudFile/src/icons.cpp new file mode 100644 index 0000000000..5a3d562e18 --- /dev/null +++ b/plugins/CloudFile/src/icons.cpp @@ -0,0 +1,40 @@ +#include "stdafx.h" + +static IconItem iconList[] = +{ + { LPGEN("Upload file(s)"), "upload", IDI_UPLOAD }, + { LPGEN("Dropbox"), "dropbox", IDI_DROPBOX }, + { LPGEN("GDrive"), "gdrive", IDI_GDRIVE } +}; + +void InitializeIcons() +{ + Icon_Register(hInstance, "Protocols/" MODULE, iconList, _countof(iconList), MODULE); +} + +HANDLE GetIconHandle(int iconId) +{ + for (int i = 0; i < _countof(iconList); i++) + if (iconList[i].defIconID == iconId) + return iconList[i].hIcolib; + + return NULL; +} + +HANDLE GetIconHandle(const char *name) +{ + for (size_t i = 0; i < _countof(iconList); i++) + if (mir_strcmpi(iconList[i].szName, name) == 0) + return iconList[i].hIcolib; + + return NULL; +} + +HICON LoadIconEx(int iconId, bool big) +{ + for (int i = 0; i < _countof(iconList); i++) + if (iconList[i].defIconID == iconId) + return IcoLib_GetIconByHandle(iconList[i].hIcolib, big); + + return NULL; +}
\ No newline at end of file diff --git a/plugins/CloudFile/src/main.cpp b/plugins/CloudFile/src/main.cpp new file mode 100644 index 0000000000..abdc3ba4ec --- /dev/null +++ b/plugins/CloudFile/src/main.cpp @@ -0,0 +1,65 @@ +#include "stdafx.h" + +int hLangpack; +HINSTANCE hInstance; +HNETLIBUSER hNetlibConnection; + +PLUGININFOEX pluginInfo = +{ + sizeof(PLUGININFOEX), + __PLUGIN_NAME, + PLUGIN_MAKE_VERSION(__MAJOR_VERSION, __MINOR_VERSION, __RELEASE_NUM, __BUILD_NUM), + __DESCRIPTION, + __AUTHOR, + __AUTHOREMAIL, + __COPYRIGHT, + __AUTHORWEB, + UNICODE_AWARE, + // {E876FE63-0701-4CDA-BED5-7C73A379C1D1} + { 0xe876fe63, 0x701, 0x4cda, { 0xbe, 0xd5, 0x7c, 0x73, 0xa3, 0x79, 0xc1, 0xd1 }} +}; + +DWORD WINAPI DllMain(HINSTANCE hInst, DWORD, LPVOID) +{ + hInstance = hInst; + return TRUE; +} + +extern "C" __declspec(dllexport) PLUGININFOEX* MirandaPluginInfoEx(DWORD) +{ + return &pluginInfo; +} + +extern "C" int __declspec(dllexport) Load(void) +{ + mir_getLP(&pluginInfo); + + PROTOCOLDESCRIPTOR pd = { sizeof(pd) }; + pd.szName = MODULE; + pd.type = PROTOTYPE_VIRTUAL; + Proto_RegisterModule(&pd); + + CreateServiceFunction(MODULE PS_GETCAPS, ProtoGetCaps); + CreateServiceFunction(MODULE PS_GETNAME, ProtoGetName); + CreateServiceFunction(MODULE PS_LOADICON, ProtoLoadIcon); + + HookEvent(ME_PROTO_ACK, OnProtoAck); + HookEvent(ME_SYSTEM_MODULESLOADED, OnModulesLoaded); + + NETLIBUSER nlu = {}; + nlu.flags = NUF_INCOMING | NUF_OUTGOING | NUF_HTTPCONNS | NUF_UNICODE; + nlu.szSettingsModule = MODULE; + nlu.szDescriptiveName.w = _A2W("MODULE"); + hNetlibConnection = Netlib_RegisterUser(&nlu); + + InitServices(); + InitializeIcons(); + InitializeMenus(); + + return 0; +} + +extern "C" int __declspec(dllexport) Unload(void) +{ + return 0; +}
\ No newline at end of file diff --git a/plugins/CloudFile/src/menus.cpp b/plugins/CloudFile/src/menus.cpp new file mode 100644 index 0000000000..d9a6db9b3e --- /dev/null +++ b/plugins/CloudFile/src/menus.cpp @@ -0,0 +1,65 @@ +#include "stdafx.h" + +HGENMENU hContactMenu; + +INT_PTR UploadMenuCommand(void *obj, WPARAM hContact, LPARAM) +{ + CCloudService *service = (CCloudService*)obj; + + auto it = service->InterceptedContacts.find(hContact); + if (it == service->InterceptedContacts.end()) + { + HWND hwnd = (HWND)CallService(MS_FILE_SENDFILE, hContact, 0); + service->InterceptedContacts[hContact] = hwnd; + } + else + SetActiveWindow(it->second); + + return 0; +} + +void InitializeMenus() +{ + CMenuItem mi; + SET_UID(mi, 0x93d4495b, 0x259b, 0x4fba, 0xbc, 0x14, 0xf9, 0x46, 0x2c, 0xda, 0xfc, 0x6d); + mi.name.a = LPGEN("Upload files to ..."); + mi.position = -2000020001; + mi.hIcon = LoadIconEx(IDI_UPLOAD); + hContactMenu = Menu_AddContactMenuItem(&mi); + + UNSET_UID(mi); + mi.flags |= CMIF_SYSTEM | CMIF_UNICODE; + mi.root = hContactMenu; + + size_t count = Services.getCount(); + for (size_t i = 0; i < count; i++) { + CCloudService *service = Services[i]; + + CMStringA serviceName(CMStringDataFormat::FORMAT, "%s/%s/Upload", MODULE, service->GetModule()); + mi.pszService = serviceName.GetBuffer(); + mi.name.w = (wchar_t*)service->GetText(); + mi.position = i; + mi.hIcolibItem = Services[i]->GetIcon(); + Menu_AddContactMenuItem(&mi); + CreateServiceFunctionObj(mi.pszService, UploadMenuCommand, service); + } +} + +int OnPrebuildContactMenu(WPARAM hContact, LPARAM) +{ + bool bShow = false; + char *proto = GetContactProto(hContact); + if (proto) { + bool bHasIM = (CallProtoService(proto, PS_GETCAPS, PFLAGNUM_1, 0) & PF1_IMSEND) != 0; + if (bHasIM) { + bool isProtoOnline = CallProtoService(proto, PS_GETSTATUS, 0, 0) > ID_STATUS_OFFLINE; + WORD status = db_get_w(hContact, proto, "Status", ID_STATUS_OFFLINE); + bool canSendOffline = (CallProtoService(proto, PS_GETCAPS, PFLAGNUM_4, 0) & PF4_IMSENDOFFLINE) > 0; + if (isProtoOnline && (status != ID_STATUS_OFFLINE || canSendOffline)) + bShow = true; + } + } + Menu_ShowItem(hContactMenu, bShow); + + return 0; +}
\ No newline at end of file diff --git a/plugins/CloudFile/src/oauth.cpp b/plugins/CloudFile/src/oauth.cpp new file mode 100644 index 0000000000..7815c27771 --- /dev/null +++ b/plugins/CloudFile/src/oauth.cpp @@ -0,0 +1,23 @@ +#include "stdafx.h" + +COAuthDlg::COAuthDlg(CCloudService *service, const char *authUrl, pThreadFuncOwner requestAccessTokenThread) + : CDlgBase(hInstance, IDD_OAUTH), m_service(service), + m_requestAccessTokenThread(requestAccessTokenThread), + m_auth(this, IDC_OAUTH_AUTHORIZE, authUrl), + m_code(this, IDC_OAUTH_CODE), m_ok(this, IDOK) +{ + m_autoClose = CLOSE_ON_CANCEL; + m_code.OnChange = Callback(this, &COAuthDlg::Code_OnChange); + m_ok.OnClick = Callback(this, &COAuthDlg::Ok_OnClick); +} + +void COAuthDlg::Code_OnChange(CCtrlBase*) +{ + ptrA requestToken(m_code.GetTextA()); + m_ok.Enable(mir_strlen(requestToken) != 0); +} + +void COAuthDlg::Ok_OnClick(CCtrlButton*) +{ + mir_forkthreadowner(m_requestAccessTokenThread, m_service, m_hwnd); +}
\ No newline at end of file diff --git a/plugins/CloudFile/src/oauth.h b/plugins/CloudFile/src/oauth.h new file mode 100644 index 0000000000..434927f791 --- /dev/null +++ b/plugins/CloudFile/src/oauth.h @@ -0,0 +1,22 @@ +#ifndef _OAUTH_H_ +#define _OAUTH_H_ + +class COAuthDlg : public CDlgBase +{ +private: + CCloudService *m_service; + pThreadFuncOwner m_requestAccessTokenThread; + + CCtrlHyperlink m_auth; + CCtrlEdit m_code; + CCtrlButton m_ok; + +protected: + void Code_OnChange(CCtrlBase*); + void Ok_OnClick(CCtrlButton*); + +public: + COAuthDlg(CCloudService *service, const char *authUrl, pThreadFuncOwner requestAccessTokenThread); +}; + +#endif //_OAUTH_H_
\ No newline at end of file diff --git a/plugins/CloudFile/src/options.cpp b/plugins/CloudFile/src/options.cpp new file mode 100644 index 0000000000..48a0f3c995 --- /dev/null +++ b/plugins/CloudFile/src/options.cpp @@ -0,0 +1,33 @@ +#include "stdafx.h" + +COptionsMain::COptionsMain() + : CPluginDlgBase(hInstance, IDD_OPTIONS_MAIN, MODULE), + m_urlAutoSend(this, IDC_URL_AUTOSEND), + m_urlPasteToMessageInputArea(this, IDC_URL_COPYTOMIA), + m_urlCopyToClipboard(this, IDC_URL_COPYTOCB) +{ + CreateLink(m_urlAutoSend, "UrlAutoSend", DBVT_BYTE, 1); + CreateLink(m_urlPasteToMessageInputArea, "UrlPasteToMessageInputArea", DBVT_BYTE, 0); + CreateLink(m_urlCopyToClipboard, "UrlCopyToClipboard", DBVT_BYTE, 0); +} + +void COptionsMain::OnInitDialog() +{ + CDlgBase::OnInitDialog(); +} + +///////////////////////////////////////////////////////////////////////////////// + +int OnOptionsInitialized(WPARAM wParam, LPARAM) +{ + OPTIONSDIALOGPAGE odp = { 0 }; + odp.szTitle.w = _A2W(MODULE); + odp.flags = ODPF_BOLDGROUPS | ODPF_UNICODE | ODPF_DONTTRANSLATE; + odp.szGroup.w = LPGENW("Network"); + + odp.szTab.w = LPGENW("General"); + odp.pDialog = new COptionsMain(); + Options_AddPage(wParam, &odp); + + return 0; +} diff --git a/plugins/CloudFile/src/options.h b/plugins/CloudFile/src/options.h new file mode 100644 index 0000000000..3fb20157b1 --- /dev/null +++ b/plugins/CloudFile/src/options.h @@ -0,0 +1,18 @@ +#ifndef _OPTIONS_H_ +#define _OPTIONS_H_ + +class COptionsMain : public CPluginDlgBase +{ +private: + CCtrlCheck m_urlAutoSend; + CCtrlCheck m_urlPasteToMessageInputArea; + CCtrlCheck m_urlCopyToClipboard; + +protected: + void OnInitDialog(); + +public: + COptionsMain(); +}; + +#endif //_OPTIONS_H_
\ No newline at end of file diff --git a/plugins/CloudFile/src/proto.cpp b/plugins/CloudFile/src/proto.cpp new file mode 100644 index 0000000000..9698534c50 --- /dev/null +++ b/plugins/CloudFile/src/proto.cpp @@ -0,0 +1,32 @@ +#include "stdafx.h" + +INT_PTR ProtoGetCaps(WPARAM wParam, LPARAM) +{ + switch (wParam) { + case PFLAGNUM_1: + return PF1_IM | PF1_FILESEND; + case PFLAGNUM_2: + return PF2_ONLINE; + case PFLAGNUM_4: + return PF4_OFFLINEFILES; + } + + return 0; +} + +INT_PTR ProtoGetName(WPARAM wParam, LPARAM lParam) +{ + if (lParam) { + mir_strncpy((char *)lParam, MODULE, wParam); + return 0; + } + + return 1; +} + +INT_PTR ProtoLoadIcon(WPARAM wParam, LPARAM) +{ + return (LOWORD(wParam) == PLI_PROTOCOL) + ? (INT_PTR)CopyIcon(LoadIconEx(IDI_UPLOAD)) + : 0; +}
\ No newline at end of file diff --git a/plugins/CloudFile/src/resource.h b/plugins/CloudFile/src/resource.h new file mode 100644 index 0000000000..70a9fd5629 --- /dev/null +++ b/plugins/CloudFile/src/resource.h @@ -0,0 +1,30 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by c:\Users\unsane\Projects\c++\miranda-ng\plugins\CloudFile\res\resource.rc +// +#define IDOK 1 +#define IDCANCEL 2 +#define IDI_UPLOAD 105 +#define IDI_DROPBOX 106 +#define IDI_ICON1 108 +#define IDI_GDRIVE 108 +#define IDD_OPTIONS_MAIN 109 +#define IDD_OAUTH 120 +#define IDC_URL_ISTEMPORARY 1004 +#define IDC_URL_COPYTOCB 1009 +#define IDC_URL_COPYTOMIA 1029 +#define IDC_URL_AUTOSEND 1030 +#define IDC_OAUTH_CODE 1082 +#define IDC_OAUTH_AUTHORIZE 1200 +#define IDC_STATIC -1 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 109 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1013 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/plugins/CloudFile/src/srmm.cpp b/plugins/CloudFile/src/srmm.cpp new file mode 100644 index 0000000000..e903d90721 --- /dev/null +++ b/plugins/CloudFile/src/srmm.cpp @@ -0,0 +1,77 @@ +#include "stdafx.h" + +int OnSrmmToolbarLoaded(WPARAM, LPARAM) +{ + BBButton bbd = {}; + bbd.pszModuleName = MODULE; + bbd.bbbFlags = BBBF_ISIMBUTTON | BBBF_ISCHATBUTTON | BBBF_ISRSIDEBUTTON; + + CMStringW tooltip(CMStringDataFormat::FORMAT, TranslateT("Upload files to ...")); + bbd.pwszTooltip = tooltip; + bbd.hIcon = GetIconHandle(IDI_UPLOAD); + bbd.dwButtonID = BBB_ID_FILE_SEND; + bbd.dwDefPos = 100 + bbd.dwButtonID; + Srmm_AddButton(&bbd); + + return 0; +} + +int OnSrmmWindowOpened(WPARAM, LPARAM lParam) +{ + MessageWindowEventData *ev = (MessageWindowEventData*)lParam; + if (ev->uType == MSG_WINDOW_EVT_OPENING && ev->hContact) { + char *proto = GetContactProto(ev->hContact); + bool isProtoOnline = CallProtoService(proto, PS_GETSTATUS, 0, 0) > ID_STATUS_OFFLINE; + WORD status = db_get_w(ev->hContact, proto, "Status", ID_STATUS_OFFLINE); + bool canSendOffline = (CallProtoService(proto, PS_GETCAPS, PFLAGNUM_4, 0) & PF4_IMSENDOFFLINE) > 0; + + BBButton bbd = {}; + bbd.pszModuleName = MODULE; + bbd.dwButtonID = BBB_ID_FILE_SEND; + bbd.bbbFlags = BBSF_RELEASED; + if (!isProtoOnline || (status == ID_STATUS_OFFLINE && !canSendOffline)) + bbd.bbbFlags = BBSF_DISABLED; + + //Srmm_SetButtonState(ev->hContact, &bbd); + } + + return 0; +} + +int OnSrmmButtonPressed(WPARAM, LPARAM lParam) +{ + CustomButtonClickData *cbc = (CustomButtonClickData*)lParam; + + if (mir_strcmp(cbc->pszModule, MODULE)) + return 0; + + if (cbc->dwButtonId != BBB_ID_FILE_SEND) + return 0; + + HMENU hMenu = CreatePopupMenu(); + + size_t count = Services.getCount(); + for (size_t i = 0; i < count; i++) { + CCloudService *service = Services[i]; + + InsertMenu(hMenu, i, MF_STRING, i + 1, service->GetText()); + //HBITMAP hBitmap = (HBITMAP)LoadImage(hInstance, MAKEINTRESOURCE(IDI_UPLOAD), IMAGE_ICON, 16, 16, 0); + //SetMenuItemBitmaps(hMenu, i, MF_BITMAP, hBitmap, hBitmap); + } + + int ind = TrackPopupMenu(hMenu, TPM_RETURNCMD, cbc->pt.x, cbc->pt.y, 0, cbc->hwndFrom, NULL); + if (ind > 0) { + CCloudService *service = Services[ind - 1]; + + auto it = service->InterceptedContacts.find(cbc->hContact); + if (it == service->InterceptedContacts.end()) + { + HWND hwnd = (HWND)CallService(MS_FILE_SENDFILE, cbc->hContact, 0); + service->InterceptedContacts[cbc->hContact] = hwnd; + } + else + SetActiveWindow(it->second); + } + + return 0; +}
\ No newline at end of file diff --git a/plugins/CloudFile/src/stdafx.cxx b/plugins/CloudFile/src/stdafx.cxx new file mode 100644 index 0000000000..d8d6847175 --- /dev/null +++ b/plugins/CloudFile/src/stdafx.cxx @@ -0,0 +1,20 @@ +/* +Copyright (C) 2012-17 Miranda NG project (http://miranda-ng.org) + +This program is free software; you can redistribute it and/or +modify it under the terms of the GNU General Public License +as published by the Free Software Foundation version 2 +of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "stdafx.h" + +ULONG FileTransferParam::hFileProcess = 1;
\ No newline at end of file diff --git a/plugins/CloudFile/src/stdafx.h b/plugins/CloudFile/src/stdafx.h new file mode 100644 index 0000000000..4cbb6eace4 --- /dev/null +++ b/plugins/CloudFile/src/stdafx.h @@ -0,0 +1,115 @@ +#ifndef _COMMON_H_ +#define _COMMON_H_ + +#include <windows.h> +#include <shlwapi.h> +#include <commctrl.h> + +#include <malloc.h> +#include <time.h> + +#include <map> + +#include <newpluginapi.h> + +#include <m_options.h> +#include <m_database.h> +#include <m_netlib.h> +#include <m_clist.h> +#include <m_skin.h> +#include <m_icolib.h> +#include <m_popup.h> +#include <m_file.h> +#include <m_langpack.h> +#include <m_message.h> +#include <m_string.h> +#include <m_gui.h> +#include <m_chat.h> +#include <m_http.h> +#include <m_json.h> + +#include <m_protoint.h> +#include <m_protosvc.h> + +#include "version.h" +#include "resource.h" +#include "options.h" + +extern HINSTANCE hInstance; +extern HNETLIBUSER hNetlibConnection; + +class Exception +{ + CMStringA message; + +public: + Exception(const char *message) : + message(message) + { + } + + const char* what() const throw() + { + return message.c_str(); + } +}; + +#define MODULE "CloudFile" + +#define FILE_CHUNK_SIZE 1024 * 1024 //1 MB + +#include "http_request.h" +#include "file_transfer.h" + +// services +#include "cloud_service.h" +#include "oauth.h" +#include "Services\dropbox_service.h" +#include "Services\google_service.h" +#include "Services\yandex_service.h" +extern LIST<CCloudService> Services; +void InitServices(); + +// proto +INT_PTR ProtoGetCaps(WPARAM, LPARAM); +INT_PTR ProtoGetName(WPARAM, LPARAM); +INT_PTR ProtoLoadIcon(WPARAM, LPARAM); + +// events +int OnModulesLoaded(WPARAM, LPARAM); +int OnProtoAck(WPARAM, LPARAM); +int OnFileDialogCanceled(void* obj, WPARAM hContact, LPARAM); + +// icons +void InitializeIcons(); +HANDLE GetIconHandle(int iconId); +HANDLE GetIconHandle(const char *name); +HICON LoadIconEx(int iconId, bool big = false); + +// menus +extern HGENMENU hContactMenu; +void InitializeMenus(); +int OnPrebuildContactMenu(WPARAM, LPARAM); + +// srmm +#define BBB_ID_FILE_SEND 10001 +int OnSrmmToolbarLoaded(WPARAM, LPARAM); +int OnSrmmWindowOpened(WPARAM, LPARAM); +int OnSrmmButtonPressed(WPARAM, LPARAM); + +// options +int OnOptionsInitialized(WPARAM wParam, LPARAM); + +// transfers +extern LIST<FileTransferParam> Transfers; +INT_PTR ProtoSendFile(void *obj, WPARAM, LPARAM lParam); +INT_PTR ProtoSendFileInterceptor(void *obj, WPARAM wParam, LPARAM lParam); +INT_PTR ProtoCancelFile(WPARAM, LPARAM lParam); +UINT UploadAndReportProgressThread(void *owner, void *arg); + +// utils +void ShowNotification(const wchar_t *caption, const wchar_t *message, int flags, MCONTACT hContact = NULL); +void ShowNotification(const wchar_t *message, int flags, MCONTACT hContact = NULL); +MEVENT AddEventToDb(MCONTACT hContact, WORD type, DWORD flags, DWORD cbBlob, PBYTE pBlob); + +#endif //_COMMON_H_
\ No newline at end of file diff --git a/plugins/CloudFile/src/transfers.cpp b/plugins/CloudFile/src/transfers.cpp new file mode 100644 index 0000000000..0f79f970d9 --- /dev/null +++ b/plugins/CloudFile/src/transfers.cpp @@ -0,0 +1,69 @@ +#include "stdafx.h" + +LIST<FileTransferParam> Transfers(1, HandleKeySortT); + +INT_PTR ProtoSendFile(void *obj, WPARAM, LPARAM lParam) +{ + CCSDATA *pccsd = (CCSDATA*)lParam; + CCloudService *service = (CCloudService*)obj; + + FileTransferParam *ftp = new FileTransferParam(pccsd->hContact); + + const wchar_t *description = (wchar_t*)pccsd->wParam; + if (description && description[0]) + ftp->AppendFormatData(L"%s\r\n", (wchar_t*)pccsd->wParam); + + wchar_t **paths = (wchar_t**)pccsd->lParam; + ftp->SetWorkingDirectory(paths[0]); + for (int i = 0; paths[i]; i++) { + if (PathIsDirectory(paths[i])) + continue; + ftp->AddFile(paths[i]); + } + + Transfers.insert(ftp); + + mir_forkthreadowner(UploadAndReportProgressThread, service, ftp); + + return ftp->GetId(); +} + +INT_PTR ProtoSendFileInterceptor(void *obj, WPARAM wParam, LPARAM lParam) +{ + CCSDATA *pccsd = (CCSDATA*)lParam; + CCloudService *service = (CCloudService*)obj; + + auto it = service->InterceptedContacts.find(pccsd->hContact); + if (it == service->InterceptedContacts.end()) + return CALLSERVICE_NOTFOUND; + service->InterceptedContacts.erase(it); + + return ProtoSendFile(obj, wParam, lParam); +} + +INT_PTR ProtoCancelFile(WPARAM, LPARAM lParam) +{ + CCSDATA *pccsd = (CCSDATA*)lParam; + + HANDLE hTransfer = (HANDLE)pccsd->wParam; + FileTransferParam *ftp = Transfers.find((FileTransferParam*)&hTransfer); + if (ftp) + ftp->Terminate(); + + return 0; +} + +UINT UploadAndReportProgressThread(void *owner, void *arg) +{ + CCloudService *service = (CCloudService*)owner; + FileTransferParam *ftp = (FileTransferParam*)arg; + + int res = service->Upload(ftp); + if (res == ACKRESULT_SUCCESS) + service->Report(ftp->GetHContact(), ftp->GetData()); + + Transfers.remove(ftp); + delete ftp; + + return res; +}
\ No newline at end of file diff --git a/plugins/CloudFile/src/utils.cpp b/plugins/CloudFile/src/utils.cpp new file mode 100644 index 0000000000..cabcd7de97 --- /dev/null +++ b/plugins/CloudFile/src/utils.cpp @@ -0,0 +1,40 @@ +#include "stdafx.h" + +void ShowNotification(const wchar_t *caption, const wchar_t *message, int flags, MCONTACT hContact) +{ + if (Miranda_IsTerminated()) + { + return; + } + + if (ServiceExists(MS_POPUP_ADDPOPUPT) && db_get_b(NULL, "Popup", "ModuleIsEnabled", 1)) + { + POPUPDATAT ppd = { 0 }; + ppd.lchContact = hContact; + wcsncpy(ppd.lpwzContactName, caption, MAX_CONTACTNAME); + wcsncpy(ppd.lpwzText, message, MAX_SECONDLINE); + ppd.lchIcon = IcoLib_GetIcon("Slack_main"); + + if (!PUAddPopupT(&ppd)) + return; + } + + MessageBox(NULL, message, caption, MB_OK | flags); +} + +void ShowNotification(const wchar_t *message, int flags, MCONTACT hContact) +{ + ShowNotification(_A2W(MODULE), message, flags, hContact); +} + +MEVENT AddEventToDb(MCONTACT hContact, WORD type, DWORD flags, DWORD cbBlob, PBYTE pBlob) +{ + DBEVENTINFO dbei = {}; + dbei.szModule = MODULE; + dbei.timestamp = time(NULL); + dbei.eventType = type; + dbei.cbBlob = cbBlob; + dbei.pBlob = pBlob; + dbei.flags = flags; + return db_event_add(hContact, &dbei); +} diff --git a/plugins/CloudFile/src/version.h b/plugins/CloudFile/src/version.h new file mode 100644 index 0000000000..9351118eb8 --- /dev/null +++ b/plugins/CloudFile/src/version.h @@ -0,0 +1,14 @@ +#define __MAJOR_VERSION 0 +#define __MINOR_VERSION 11 +#define __RELEASE_NUM 0 +#define __BUILD_NUM 1 + +#include <stdver.h> + +#define __PLUGIN_NAME "CloudFile" +#define __FILENAME "CloudFile.dll" +#define __DESCRIPTION "Allows you to upload files via cloud services." +#define __AUTHOR "Miranda NG Team" +#define __AUTHOREMAIL "" +#define __AUTHORWEB "http://miranda-ng.org/p/CloudFile/" +#define __COPYRIGHT "© 2017 Miranda NG project" |