From d9cdbf7eba8b496b7325c736fdc7dbd2afc55a29 Mon Sep 17 00:00:00 2001
From: George Hazan <george.hazan@gmail.com>
Date: Mon, 10 Jul 2023 20:11:11 +0300
Subject: fixes #3567 (ICQ: implement offline files download)

---
 protocols/ICQ-WIM/src/file.cpp   |  17 ------
 protocols/ICQ-WIM/src/proto.cpp  | 115 +++++++++++++++++++----------------
 protocols/ICQ-WIM/src/proto.h    |  16 ++---
 protocols/ICQ-WIM/src/server.cpp | 125 ++++++++++++++-------------------------
 4 files changed, 116 insertions(+), 157 deletions(-)

(limited to 'protocols/ICQ-WIM')

diff --git a/protocols/ICQ-WIM/src/file.cpp b/protocols/ICQ-WIM/src/file.cpp
index 76b55026f9..1dc58ba06b 100644
--- a/protocols/ICQ-WIM/src/file.cpp
+++ b/protocols/ICQ-WIM/src/file.cpp
@@ -17,23 +17,6 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
 
 #include "stdafx.h"
 
-// create an object for receiving
-IcqFileTransfer::IcqFileTransfer(MCONTACT hContact, const char *pszUrl) :
-	m_szHost(pszUrl)
-{
-	pfts.hContact = hContact;
-	pfts.totalFiles = 1;
-	pfts.flags = PFTS_UNICODE | PFTS_RECEIVING;
-
-	ptrW pwszFileName(mir_utf8decodeW(pszUrl));
-	if (pwszFileName == nullptr)
-		pwszFileName = mir_a2u(pszUrl);
-
-	const wchar_t *p = wcsrchr(pwszFileName, '/');
-	m_wszFileName = (p == nullptr) ? pwszFileName : p + 1;
-	m_wszShortName = m_wszFileName;
-}
-
 // create an object for sending
 IcqFileTransfer::IcqFileTransfer(MCONTACT hContact, const wchar_t *pwszFileName) :
 	m_wszFileName(pwszFileName)
diff --git a/protocols/ICQ-WIM/src/proto.cpp b/protocols/ICQ-WIM/src/proto.cpp
index 9cade4f8d9..09496b46c4 100644
--- a/protocols/ICQ-WIM/src/proto.cpp
+++ b/protocols/ICQ-WIM/src/proto.cpp
@@ -74,6 +74,9 @@ CIcqProto::CIcqProto(const char *aProtoName, const wchar_t *aUserName) :
 	CreateProtoService(PS_GETUNREADEMAILCOUNT, &CIcqProto::GetEmailCount);
 	CreateProtoService(PS_GOTO_INBOX, &CIcqProto::GotoInbox);
 
+	// offline file transfer
+	CreateProtoService(PS_OFFLINEFILE, &CIcqProto::SvcOfflineFile);
+
 	// events
 	HookProtoEvent(ME_CLIST_GROUPCHANGE, &CIcqProto::OnGroupChange);
 	HookProtoEvent(ME_GC_EVENT, &CIcqProto::GroupchatEventHook);
@@ -165,11 +168,73 @@ void CIcqProto::OnContactDeleted(MCONTACT hContact)
 		<< AIMSID(this) << WCHAR_PARAM("buddy", szId) << INT_PARAM("allGroups", 1));
 }
 
+void CIcqProto::OnCreateOfflineFile(DB::FILE_BLOB &blob, void *hTransfer)
+{
+	if (auto *pFileInfo = (IcqFileInfo *)hTransfer) {
+		blob.setUrl(pFileInfo->szUrl);
+		blob.setSize(pFileInfo->dwFileSize);
+	}
+}
+
 void CIcqProto::OnEventEdited(MCONTACT, MEVENT)
 {
 
 }
 
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void CIcqProto::OnFileRecv(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq)
+{
+	if (pReply->resultCode != 200)
+		return;
+
+	auto *ofd = (OFDTHREAD *)pReq->pUserInfo;
+	debugLogW(L"Saving to [%s]", ofd->wszPath.c_str());
+	int fileId = _wopen(ofd->wszPath, _O_BINARY | _O_CREAT | _O_TRUNC | _O_WRONLY, _S_IREAD | _S_IWRITE);
+	if (fileId == -1) {
+		debugLogW(L"Cannot open [%s] for writing", ofd->wszPath.c_str());
+		return;
+	}
+
+	int result = _write(fileId, pReply->pData, pReply->dataLength);
+	_close(fileId);
+	if (result != pReply->dataLength) {
+		debugLogW(L"Error writing data into [%s]", ofd->wszPath.c_str());
+		return;
+	}
+
+	delete ofd;
+}
+
+void __cdecl CIcqProto::OfflineFileThread(void *pParam)
+{
+	auto *ofd = (OFDTHREAD *)pParam;
+
+	DB::EventInfo dbei(ofd->hDbEvent);
+	if (dbei && !strcmp(dbei.szModule, m_szModuleName) && dbei.eventType == EVENTTYPE_FILE) {
+		JSONNode root = JSONNode::parse((const char *)dbei.pBlob);
+		if (m_bOnline && root) {
+			auto *pReq = new AsyncHttpRequest(CONN_NONE, REQUEST_GET, root["u"].as_string().c_str(), &CIcqProto::OnFileRecv);
+			pReq->pUserInfo = ofd;
+			pReq->AddHeader("Sec-Fetch-User", "?1");
+			pReq->AddHeader("Sec-Fetch-Site", "cross-site");
+			pReq->AddHeader("Sec-Fetch-Mode", "navigate");
+			Push(pReq);
+			return;
+		}
+	}
+
+	delete ofd;
+}
+
+INT_PTR __cdecl CIcqProto::SvcOfflineFile(WPARAM param, LPARAM)
+{
+	ForkThread((MyThreadFunc)&CIcqProto::OfflineFileThread, (void *)param);
+	return 0;
+}
+
+/////////////////////////////////////////////////////////////////////////////////////////
+
 INT_PTR CIcqProto::OnMenuLoadHistory(WPARAM hContact, LPARAM)
 {
 	delSetting(hContact, DB_KEY_LASTMSGID);
@@ -340,56 +405,6 @@ int CIcqProto::AuthRequest(MCONTACT hContact, const wchar_t* szMessage)
 	return 0;
 }
 
-////////////////////////////////////////////////////////////////////////////////////////
-// File operations
-
-HANDLE CIcqProto::FileAllow(MCONTACT, HANDLE hTransfer, const wchar_t *pwszSavePath)
-{
-	if (!m_bOnline)
-		return nullptr;
-
-	auto *ft = (IcqFileTransfer *)hTransfer;
-	ft->m_wszFileName.Insert(0, pwszSavePath);
-	ft->pfts.szCurrentFile.w = ft->m_wszFileName.GetBuffer();
-	ft->Acquire();
-
-	auto *pReq = new AsyncHttpRequest(CONN_NONE, REQUEST_GET, ft->m_szHost, &CIcqProto::OnFileRecv);
-	pReq->pUserInfo = ft;
-	pReq->AddHeader("Sec-Fetch-User", "?1");
-	pReq->AddHeader("Sec-Fetch-Site", "cross-site");
-	pReq->AddHeader("Sec-Fetch-Mode", "navigate");
-	Push(pReq);
-	
-	return hTransfer;
-}
-
-int CIcqProto::FileCancel(MCONTACT hContact, HANDLE hTransfer)
-{
-	ProtoBroadcastAck(hContact, ACKTYPE_FILE, ACKRESULT_FAILED, hTransfer);
-
-	auto *ft = (IcqFileTransfer *)hTransfer;
-	if (ft->pfts.currentFileTime != 0)
-		ft->m_bCanceled = true;
-	else
-		ft->Release();
-	return 0;
-}
-
-int CIcqProto::FileResume(HANDLE hTransfer, int, const wchar_t *szFilename)
-{
-	auto *ft = (IcqFileTransfer *)hTransfer;
-	if (!m_bOnline || ft == nullptr)
-		return 1;
-
-	if (szFilename != nullptr) {
-		ft->m_wszFileName = szFilename;
-		ft->pfts.szCurrentFile.w = ft->m_wszFileName.GetBuffer();
-	}
-
-	::SetEvent(ft->hWaitEvent);
-	return 0;
-}
-
 ////////////////////////////////////////////////////////////////////////////////////////
 // GetCaps - return protocol capabilities bits
 
diff --git a/protocols/ICQ-WIM/src/proto.h b/protocols/ICQ-WIM/src/proto.h
index b7cac8f5c4..018a4e1399 100644
--- a/protocols/ICQ-WIM/src/proto.h
+++ b/protocols/ICQ-WIM/src/proto.h
@@ -144,7 +144,7 @@ struct IcqConn
 	int lastTs, timeout;
 };
 
-struct IcqFileTransfer : public MZeroedObject, public MShareable
+struct IcqFileTransfer : public MZeroedObject
 {
 	bool m_bCanceled = false, m_bStarted = false;
 	int m_fileId = -1;
@@ -152,15 +152,11 @@ struct IcqFileTransfer : public MZeroedObject, public MShareable
 	CMStringW m_wszFileName, m_wszDescr;
 	const wchar_t *m_wszShortName;
 	PROTOFILETRANSFERSTATUS pfts;
-	HANDLE hWaitEvent;
-
-	// create an object for receiving
-	IcqFileTransfer(MCONTACT hContact, const char *pszUrl);
 
 	// create an object for sending
 	IcqFileTransfer(MCONTACT hContact, const wchar_t *pwszFileName);
 
-	~IcqFileTransfer() override;
+	~IcqFileTransfer();
 
 	void FillHeaders(AsyncHttpRequest *pReq);
 };
@@ -353,6 +349,7 @@ class CIcqProto : public PROTO<CIcqProto>
 	HANDLE    m_hWorkerThread;
 	void      __cdecl ServerThread(void*);
 	void      __cdecl PollThread(void*);
+	void      __cdecl OfflineFileThread(void*);
 
 	////////////////////////////////////////////////////////////////////////////////////////
 	// services
@@ -362,6 +359,8 @@ class CIcqProto : public PROTO<CIcqProto>
 	INT_PTR   __cdecl GetAvatarInfo(WPARAM, LPARAM);
 	INT_PTR   __cdecl SetAvatar(WPARAM, LPARAM);
 	
+	INT_PTR   __cdecl SvcOfflineFile(WPARAM, LPARAM);
+
 	INT_PTR   __cdecl EditGroups(WPARAM, LPARAM);
 	INT_PTR   __cdecl EditProfile(WPARAM, LPARAM);
 	INT_PTR   __cdecl GetEmailCount(WPARAM, LPARAM);
@@ -390,10 +389,6 @@ class CIcqProto : public PROTO<CIcqProto>
 			    
 	HANDLE    SearchBasic(const wchar_t *id) override;
 
-	HANDLE    FileAllow(MCONTACT hContact, HANDLE hTransfer, const wchar_t *szPath) override;
-	int       FileCancel(MCONTACT hContact, HANDLE hTransfer) override;
-	int       FileResume(HANDLE hTransfer, int action, const wchar_t *szFilename) override;
-
 	HANDLE    SendFile(MCONTACT hContact, const wchar_t *szDescription, wchar_t **ppszFiles) override;
 	int       SendMsg(MCONTACT hContact, int flags, const char *msg) override;
 			    
@@ -406,6 +401,7 @@ class CIcqProto : public PROTO<CIcqProto>
 	void      OnContactAdded(MCONTACT) override;
 	void      OnContactDeleted(MCONTACT) override;
 	MWindow   OnCreateAccMgrUI(MWindow) override;
+	void      OnCreateOfflineFile(DB::FILE_BLOB &blob, void *ft) override;
 	void      OnEventEdited(MCONTACT, MEVENT) override;
 	void      OnMarkRead(MCONTACT, MEVENT) override;
 	void      OnModulesLoaded() override;
diff --git a/protocols/ICQ-WIM/src/server.cpp b/protocols/ICQ-WIM/src/server.cpp
index e8f360144c..6a1eb0cbef 100644
--- a/protocols/ICQ-WIM/src/server.cpp
+++ b/protocols/ICQ-WIM/src/server.cpp
@@ -124,6 +124,42 @@ void CIcqProto::CheckPassword()
 	}
 }
 
+/////////////////////////////////////////////////////////////////////////////////////////
+
+void CIcqProto::OnFileInfo(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq)
+{
+	IcqFileInfo **res = (IcqFileInfo **)pReq->pUserInfo;
+	*res = nullptr;
+
+	RobustReply root(pReply);
+	if (root.error() != 200)
+		return;
+
+	auto &pData = root.result();
+	auto &pInfo = pData["info"];
+	std::string szUrl(pInfo["dlink"].as_string());
+	if (szUrl.empty())
+		return;
+
+	OnMarkRead(pReq->hContact, 0);
+
+	bool bIsSticker;
+	CMStringW wszDescr(pInfo["file_name"].as_mstring());
+	if (!mir_wstrncmp(wszDescr, L"dnld", 4)) {
+		bIsSticker = true;
+
+		std::string szPreview = pData["previews"]["192"].as_string();
+		if (!szPreview.empty())
+			szUrl = szPreview;
+	}
+	else bIsSticker = false;
+
+	mir_urlDecode(&*szUrl.begin());
+
+	*res = new IcqFileInfo(szUrl, wszDescr, pInfo["file_size"].as_int());
+	res[0]->bIsSticker = bIsSticker;
+}
+
 IcqFileInfo* CIcqProto::CheckFile(MCONTACT hContact, CMStringW &wszText, bool &bIsFile)
 {
 	CMStringW wszUrl(wszText.Mid(26));
@@ -522,17 +558,20 @@ void CIcqProto::ParseMessage(MCONTACT hContact, __int64 &lastMsgId, const JSONNo
 
 	// convert a file info into Miranda's file transfer
 	if (bIsFileTransfer) {
-		auto *ft = new IcqFileTransfer(hContact, pFileInfo->szUrl);
-		ft->pfts.totalBytes = ft->pfts.currentFileSize = pFileInfo->dwFileSize;
-		ft->pfts.szCurrentFile.w = ft->m_wszFileName.GetBuffer();
+		ptrW pwszFileName(mir_utf8decodeW(pFileInfo->szUrl));
+		if (pwszFileName == nullptr)
+			pwszFileName = mir_a2u(pFileInfo->szUrl);
+
+		const wchar_t *p = wcsrchr(pwszFileName, '/');
+		const wchar_t *m_wszShortName = (p == nullptr) ? pwszFileName : p + 1;
 
 		PROTORECVFILE pre = {};
-		pre.dwFlags = PRFF_UNICODE;
+		pre.dwFlags = PRFF_UNICODE | PRFF_SILENT;
 		pre.fileCount = 1;
 		pre.timestamp = iMsgTime;
-		pre.files.w = &ft->m_wszShortName;
+		pre.files.w = &m_wszShortName;
 		pre.descr.w = pFileInfo->wszDescr;
-		pre.lParam = (LPARAM)ft;
+		pre.lParam = (LPARAM)pFileInfo;
 		ProtoChainRecvFile(hContact, &pre);
 
 		delete pFileInfo;
@@ -943,80 +982,6 @@ void CIcqProto::OnGetSticker(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq)
 	CallService(MS_SMILEYADD_LOADCONTACTSMILEYS, 0, LPARAM(&cont));
 }
 
-/////////////////////////////////////////////////////////////////////////////////////////
-// File info request
-
-void CIcqProto::OnFileInfo(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq)
-{
-	IcqFileInfo **res = (IcqFileInfo **)pReq->pUserInfo;
-	*res = nullptr;
-
-	RobustReply root(pReply);
-	if (root.error() != 200)
-		return;
-
-	auto &pData = root.result();
-	auto &pInfo = pData["info"] ;
-	std::string szUrl(pInfo["dlink"].as_string());
-	if (szUrl.empty())
-		return;
-
-	OnMarkRead(pReq->hContact, 0);
-
-	bool bIsSticker;
-	CMStringW wszDescr(pInfo["file_name"].as_mstring());
-	if (!mir_wstrncmp(wszDescr, L"dnld", 4)) {
-		bIsSticker = true;
-
-		std::string szPreview = pData["previews"]["192"].as_string();
-		if (!szPreview.empty())
-			szUrl = szPreview;
-	}
-	else bIsSticker = false;
-
-	mir_urlDecode(&*szUrl.begin());
-
-	*res = new IcqFileInfo(szUrl, wszDescr, pInfo["file_size"].as_int());
-	res[0]->bIsSticker = bIsSticker;
-}
-
-void CIcqProto::OnFileRecv(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest *pReq)
-{
-	auto *ft = (IcqFileTransfer*)pReq->pUserInfo;
-
-	if (pReply->resultCode != 200) {
-LBL_Error:
-		FileCancel(pReq->hContact, ft);
-		return;
-	}
-
-	ft->hWaitEvent = CreateEvent(nullptr, FALSE, FALSE, nullptr);
-	if (ProtoBroadcastAck(ft->pfts.hContact, ACKTYPE_FILE, ACKRESULT_FILERESUME, ft, (LPARAM)&ft->pfts))
-		WaitForSingleObject(ft->hWaitEvent, INFINITE);
-	CloseHandle(ft->hWaitEvent);
-
-	debugLogW(L"Saving to [%s]", ft->pfts.szCurrentFile.w);
-	int fileId = _wopen(ft->pfts.szCurrentFile.w, _O_BINARY | _O_CREAT | _O_TRUNC | _O_WRONLY, _S_IREAD | _S_IWRITE);
-	if (fileId == -1) {
-		debugLogW(L"Cannot open [%s] for writing", ft->pfts.szCurrentFile.w);
-		goto LBL_Error;
-	}
-
-	int result = _write(fileId, pReply->pData, pReply->dataLength);
-	_close(fileId);
-	if (result != pReply->dataLength) {
-		debugLogW(L"Error writing data into [%s]", ft->pfts.szCurrentFile.w);
-		goto LBL_Error;
-	}
-
-	ft->pfts.totalProgress += pReply->dataLength;
-	ft->pfts.currentFileProgress += pReply->dataLength;
-	ProtoBroadcastAck(ft->pfts.hContact, ACKTYPE_FILE, ACKRESULT_DATA, ft, (LPARAM)&ft->pfts);
-
-	ProtoBroadcastAck(ft->pfts.hContact, ACKTYPE_FILE, ACKRESULT_SUCCESS, ft);
-	delete ft;
-}
-
 /////////////////////////////////////////////////////////////////////////////////////////
 
 void CIcqProto::OnGenToken(NETLIBHTTPREQUEST *pReply, AsyncHttpRequest*)
-- 
cgit v1.2.3