mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-26 03:35:33 +00:00
Bug 817972 - Make Gecko Bluetooth capable of queueing file-sending requests, r=gyeh
This commit is contained in:
parent
6bbda083e4
commit
15a3719abc
@ -230,6 +230,7 @@ BluetoothOppManager::BluetoothOppManager() : mConnected(false)
|
|||||||
, mSendTransferCompleteFlag(false)
|
, mSendTransferCompleteFlag(false)
|
||||||
, mSuccessFlag(false)
|
, mSuccessFlag(false)
|
||||||
, mWaitingForConfirmationFlag(false)
|
, mWaitingForConfirmationFlag(false)
|
||||||
|
, mCurrentBlobIndex(-1)
|
||||||
{
|
{
|
||||||
mConnectedDeviceAddress.AssignLiteral(BLUETOOTH_ADDRESS_NONE);
|
mConnectedDeviceAddress.AssignLiteral(BLUETOOTH_ADDRESS_NONE);
|
||||||
Listen();
|
Listen();
|
||||||
@ -275,10 +276,7 @@ BluetoothOppManager::Connect(const nsAString& aDeviceObjectPath,
|
|||||||
}
|
}
|
||||||
|
|
||||||
BluetoothService* bs = BluetoothService::Get();
|
BluetoothService* bs = BluetoothService::Get();
|
||||||
if (!bs) {
|
NS_ENSURE_TRUE(bs, false);
|
||||||
NS_WARNING("BluetoothService not available!");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
nsString uuid;
|
nsString uuid;
|
||||||
BluetoothUuidHelper::GetString(BluetoothServiceClass::OBJECT_PUSH, uuid);
|
BluetoothUuidHelper::GetString(BluetoothServiceClass::OBJECT_PUSH, uuid);
|
||||||
@ -354,69 +352,50 @@ BluetoothOppManager::Listen()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool
|
void
|
||||||
BluetoothOppManager::SendFile(BlobParent* aActor)
|
BluetoothOppManager::StartSendingNextFile()
|
||||||
{
|
{
|
||||||
if (mBlob) {
|
MOZ_ASSERT(NS_IsMainThread());
|
||||||
// Means there's a sending process. Reply error.
|
MOZ_ASSERT(!IsTransferring());
|
||||||
return false;
|
MOZ_ASSERT(mBlobs.Length() > mCurrentBlobIndex + 1);
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
mBlob = mBlobs[++mCurrentBlobIndex];
|
||||||
* Process of sending a file:
|
|
||||||
* - Keep blob because OPP connection has not been established yet.
|
|
||||||
* - Try to retrieve file name from the blob or assign one if failed to get.
|
|
||||||
* - Create an OPP connection by SendConnectRequest()
|
|
||||||
* - After receiving the response, start to read file and send.
|
|
||||||
*/
|
|
||||||
mBlob = aActor->GetBlob();
|
|
||||||
|
|
||||||
sFileName.Truncate();
|
// Before sending content, we have to send a header including
|
||||||
|
// information such as file name, file length and content type.
|
||||||
nsCOMPtr<nsIDOMFile> file = do_QueryInterface(mBlob);
|
ExtractBlobHeaders();
|
||||||
if (file) {
|
|
||||||
file->GetName(sFileName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* We try our best to get the file extention to avoid interoperability issues.
|
|
||||||
* However, once we found that we are unable to get suitable extension or
|
|
||||||
* information about the content type, sending a pre-defined file name without
|
|
||||||
* extension would be fine.
|
|
||||||
*/
|
|
||||||
if (sFileName.IsEmpty()) {
|
|
||||||
sFileName.AssignLiteral("Unknown");
|
|
||||||
}
|
|
||||||
|
|
||||||
int32_t offset = sFileName.RFindChar('/');
|
|
||||||
if (offset != kNotFound) {
|
|
||||||
sFileName = Substring(sFileName, offset + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
offset = sFileName.RFindChar('.');
|
|
||||||
if (offset == kNotFound) {
|
|
||||||
nsCOMPtr<nsIMIMEService> mimeSvc = do_GetService(NS_MIMESERVICE_CONTRACTID);
|
|
||||||
|
|
||||||
if (mimeSvc) {
|
|
||||||
nsString mimeType;
|
|
||||||
mBlob->GetType(mimeType);
|
|
||||||
|
|
||||||
nsCString extension;
|
|
||||||
nsresult rv =
|
|
||||||
mimeSvc->GetPrimaryExtension(NS_LossyConvertUTF16toASCII(mimeType),
|
|
||||||
EmptyCString(),
|
|
||||||
extension);
|
|
||||||
if (NS_SUCCEEDED(rv)) {
|
|
||||||
sFileName.AppendLiteral(".");
|
|
||||||
AppendUTF8toUTF16(extension, sFileName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
SendConnectRequest();
|
|
||||||
mTransferMode = false;
|
|
||||||
StartFileTransfer();
|
StartFileTransfer();
|
||||||
|
|
||||||
|
if (mCurrentBlobIndex == 0) {
|
||||||
|
// We may have more than one file waiting for transferring, but only one
|
||||||
|
// CONNECT request would be sent. Therefore check if this is the very first
|
||||||
|
// file at the head of queue.
|
||||||
|
SendConnectRequest();
|
||||||
|
} else {
|
||||||
|
SendPutHeaderRequest(sFileName, sFileLength);
|
||||||
|
AfterFirstPut();
|
||||||
|
}
|
||||||
|
|
||||||
|
mTransferMode = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
BluetoothOppManager::SendFile(const nsAString& aDeviceAddress,
|
||||||
|
BlobParent* aActor)
|
||||||
|
{
|
||||||
|
MOZ_ASSERT(NS_IsMainThread());
|
||||||
|
|
||||||
|
if (mCurrentBlobIndex >= 0) {
|
||||||
|
if (mConnectedDeviceAddress != aDeviceAddress) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
mBlobs.AppendElement(aActor->GetBlob().get());
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
mBlobs.AppendElement(aActor->GetBlob().get());
|
||||||
|
StartSendingNextFile();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -431,17 +410,13 @@ BluetoothOppManager::StopSendingFile()
|
|||||||
bool
|
bool
|
||||||
BluetoothOppManager::ConfirmReceivingFile(bool aConfirm)
|
BluetoothOppManager::ConfirmReceivingFile(bool aConfirm)
|
||||||
{
|
{
|
||||||
if (!mConnected) return false;
|
NS_ENSURE_TRUE(mConnected, false);
|
||||||
|
NS_ENSURE_TRUE(mWaitingForConfirmationFlag, false);
|
||||||
|
|
||||||
|
MOZ_ASSERT(mPacketLeftLength == 0);
|
||||||
|
|
||||||
if (!mWaitingForConfirmationFlag) {
|
|
||||||
NS_WARNING("We are not waiting for a confirmation now.");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
mWaitingForConfirmationFlag = false;
|
mWaitingForConfirmationFlag = false;
|
||||||
|
|
||||||
NS_ASSERTION(mPacketLeftLength == 0,
|
|
||||||
"Should not be in the middle of receiving a PUT packet.");
|
|
||||||
|
|
||||||
// For the first packet of first file
|
// For the first packet of first file
|
||||||
bool success = false;
|
bool success = false;
|
||||||
if (aConfirm) {
|
if (aConfirm) {
|
||||||
@ -490,9 +465,10 @@ BluetoothOppManager::AfterOppDisconnected()
|
|||||||
|
|
||||||
mConnected = false;
|
mConnected = false;
|
||||||
mLastCommand = 0;
|
mLastCommand = 0;
|
||||||
mBlob = nullptr;
|
|
||||||
mPacketLeftLength = 0;
|
mPacketLeftLength = 0;
|
||||||
|
|
||||||
|
ClearQueue();
|
||||||
|
|
||||||
// We can't reset mSuccessFlag here since this function may be called
|
// We can't reset mSuccessFlag here since this function may be called
|
||||||
// before we send system message of transfer complete
|
// before we send system message of transfer complete
|
||||||
// mSuccessFlag = false;
|
// mSuccessFlag = false;
|
||||||
@ -537,11 +513,11 @@ BluetoothOppManager::DeleteReceivedFile()
|
|||||||
bool
|
bool
|
||||||
BluetoothOppManager::CreateFile()
|
BluetoothOppManager::CreateFile()
|
||||||
{
|
{
|
||||||
|
MOZ_ASSERT(mPacketLeftLength == 0);
|
||||||
|
|
||||||
nsString path;
|
nsString path;
|
||||||
path.AssignLiteral(TARGET_FOLDER);
|
path.AssignLiteral(TARGET_FOLDER);
|
||||||
|
|
||||||
MOZ_ASSERT(mPacketLeftLength == 0);
|
|
||||||
|
|
||||||
nsCOMPtr<nsIFile> f;
|
nsCOMPtr<nsIFile> f;
|
||||||
nsresult rv;
|
nsresult rv;
|
||||||
rv = NS_NewLocalFile(path + sFileName, false, getter_AddRefs(f));
|
rv = NS_NewLocalFile(path + sFileName, false, getter_AddRefs(f));
|
||||||
@ -600,17 +576,11 @@ BluetoothOppManager::CreateFile()
|
|||||||
bool
|
bool
|
||||||
BluetoothOppManager::WriteToFile(const uint8_t* aData, int aDataLength)
|
BluetoothOppManager::WriteToFile(const uint8_t* aData, int aDataLength)
|
||||||
{
|
{
|
||||||
if (!mOutputStream) {
|
NS_ENSURE_TRUE(mOutputStream, false);
|
||||||
NS_WARNING("No available output stream");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
uint32_t wrote = 0;
|
uint32_t wrote = 0;
|
||||||
mOutputStream->Write((const char*)aData, aDataLength, &wrote);
|
mOutputStream->Write((const char*)aData, aDataLength, &wrote);
|
||||||
if (aDataLength != wrote) {
|
NS_ENSURE_TRUE(aDataLength == wrote, false);
|
||||||
NS_WARNING("Writing to the file failed");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -644,6 +614,8 @@ BluetoothOppManager::ExtractPacketHeaders(const ObexHeaderSet& aHeader)
|
|||||||
bool
|
bool
|
||||||
BluetoothOppManager::ExtractBlobHeaders()
|
BluetoothOppManager::ExtractBlobHeaders()
|
||||||
{
|
{
|
||||||
|
RetrieveSentFileName();
|
||||||
|
|
||||||
nsresult rv = mBlob->GetType(sContentType);
|
nsresult rv = mBlob->GetType(sContentType);
|
||||||
if (NS_FAILED(rv)) {
|
if (NS_FAILED(rv)) {
|
||||||
NS_WARNING("Can't get content type");
|
NS_WARNING("Can't get content type");
|
||||||
@ -681,6 +653,52 @@ BluetoothOppManager::ExtractBlobHeaders()
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
BluetoothOppManager::RetrieveSentFileName()
|
||||||
|
{
|
||||||
|
sFileName.Truncate();
|
||||||
|
|
||||||
|
nsCOMPtr<nsIDOMFile> file = do_QueryInterface(mBlob);
|
||||||
|
if (file) {
|
||||||
|
file->GetName(sFileName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We try our best to get the file extention to avoid interoperability issues.
|
||||||
|
* However, once we found that we are unable to get suitable extension or
|
||||||
|
* information about the content type, sending a pre-defined file name without
|
||||||
|
* extension would be fine.
|
||||||
|
*/
|
||||||
|
if (sFileName.IsEmpty()) {
|
||||||
|
sFileName.AssignLiteral("Unknown");
|
||||||
|
}
|
||||||
|
|
||||||
|
int32_t offset = sFileName.RFindChar('/');
|
||||||
|
if (offset != kNotFound) {
|
||||||
|
sFileName = Substring(sFileName, offset + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
offset = sFileName.RFindChar('.');
|
||||||
|
if (offset == kNotFound) {
|
||||||
|
nsCOMPtr<nsIMIMEService> mimeSvc = do_GetService(NS_MIMESERVICE_CONTRACTID);
|
||||||
|
|
||||||
|
if (mimeSvc) {
|
||||||
|
nsString mimeType;
|
||||||
|
mBlob->GetType(mimeType);
|
||||||
|
|
||||||
|
nsCString extension;
|
||||||
|
nsresult rv =
|
||||||
|
mimeSvc->GetPrimaryExtension(NS_LossyConvertUTF16toASCII(mimeType),
|
||||||
|
EmptyCString(),
|
||||||
|
extension);
|
||||||
|
if (NS_SUCCEEDED(rv)) {
|
||||||
|
sFileName.AppendLiteral(".");
|
||||||
|
AppendUTF8toUTF16(extension, sFileName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bool
|
bool
|
||||||
BluetoothOppManager::IsReservedChar(PRUnichar c)
|
BluetoothOppManager::IsReservedChar(PRUnichar c)
|
||||||
{
|
{
|
||||||
@ -845,6 +863,17 @@ BluetoothOppManager::ServerDataHandler(UnixSocketRawData* aMessage)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
BluetoothOppManager::ClearQueue()
|
||||||
|
{
|
||||||
|
mCurrentBlobIndex = -1;
|
||||||
|
mBlob = nullptr;
|
||||||
|
|
||||||
|
while (!mBlobs.IsEmpty()) {
|
||||||
|
mBlobs.RemoveElement(mBlobs[0]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
BluetoothOppManager::ClientDataHandler(UnixSocketRawData* aMessage)
|
BluetoothOppManager::ClientDataHandler(UnixSocketRawData* aMessage)
|
||||||
{
|
{
|
||||||
@ -884,7 +913,17 @@ BluetoothOppManager::ClientDataHandler(UnixSocketRawData* aMessage)
|
|||||||
if (mLastCommand == ObexRequestCode::PutFinal) {
|
if (mLastCommand == ObexRequestCode::PutFinal) {
|
||||||
mSuccessFlag = true;
|
mSuccessFlag = true;
|
||||||
FileTransferComplete();
|
FileTransferComplete();
|
||||||
SendDisconnectRequest();
|
|
||||||
|
if (mInputStream) {
|
||||||
|
mInputStream->Close();
|
||||||
|
mInputStream = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mCurrentBlobIndex + 1 == mBlobs.Length()) {
|
||||||
|
SendDisconnectRequest();
|
||||||
|
} else {
|
||||||
|
StartSendingNextFile();
|
||||||
|
}
|
||||||
} else if (mLastCommand == ObexRequestCode::Abort) {
|
} else if (mLastCommand == ObexRequestCode::Abort) {
|
||||||
SendDisconnectRequest();
|
SendDisconnectRequest();
|
||||||
FileTransferComplete();
|
FileTransferComplete();
|
||||||
@ -909,16 +948,8 @@ BluetoothOppManager::ClientDataHandler(UnixSocketRawData* aMessage)
|
|||||||
mRemoteMaxPacketLength =
|
mRemoteMaxPacketLength =
|
||||||
(((int)(aMessage->mData[5]) << 8) | aMessage->mData[6]);
|
(((int)(aMessage->mData[5]) << 8) | aMessage->mData[6]);
|
||||||
|
|
||||||
/*
|
sInstance->SendPutHeaderRequest(sFileName, sFileLength);
|
||||||
* Before sending content, we have to send a header including
|
|
||||||
* information such as file name, file length and content type.
|
|
||||||
*/
|
|
||||||
if (ExtractBlobHeaders()) {
|
|
||||||
sInstance->SendPutHeaderRequest(sFileName, sFileLength);
|
|
||||||
}
|
|
||||||
} else if (mLastCommand == ObexRequestCode::Put) {
|
} else if (mLastCommand == ObexRequestCode::Put) {
|
||||||
|
|
||||||
// Send PutFinal packet when we get response
|
|
||||||
if (sWaitingToSendPutFinal) {
|
if (sWaitingToSendPutFinal) {
|
||||||
SendPutFinalRequest();
|
SendPutFinalRequest();
|
||||||
return;
|
return;
|
||||||
@ -997,6 +1028,8 @@ void
|
|||||||
BluetoothOppManager::SendPutHeaderRequest(const nsAString& aFileName,
|
BluetoothOppManager::SendPutHeaderRequest(const nsAString& aFileName,
|
||||||
int aFileSize)
|
int aFileSize)
|
||||||
{
|
{
|
||||||
|
if (!mConnected) return;
|
||||||
|
|
||||||
uint8_t* req = new uint8_t[mRemoteMaxPacketLength];
|
uint8_t* req = new uint8_t[mRemoteMaxPacketLength];
|
||||||
|
|
||||||
int len = aFileName.Length();
|
int len = aFileName.Length();
|
||||||
@ -1086,6 +1119,8 @@ BluetoothOppManager::SendPutFinalRequest()
|
|||||||
void
|
void
|
||||||
BluetoothOppManager::SendDisconnectRequest()
|
BluetoothOppManager::SendDisconnectRequest()
|
||||||
{
|
{
|
||||||
|
if (!mConnected) return;
|
||||||
|
|
||||||
// Section 3.3.2 "Disconnect", IrOBEX 1.2
|
// Section 3.3.2 "Disconnect", IrOBEX 1.2
|
||||||
// [opcode:1][length:2][Headers:var]
|
// [opcode:1][length:2][Headers:var]
|
||||||
uint8_t req[255];
|
uint8_t req[255];
|
||||||
@ -1102,6 +1137,8 @@ BluetoothOppManager::SendDisconnectRequest()
|
|||||||
void
|
void
|
||||||
BluetoothOppManager::SendAbortRequest()
|
BluetoothOppManager::SendAbortRequest()
|
||||||
{
|
{
|
||||||
|
if (!mConnected) return;
|
||||||
|
|
||||||
// Section 3.3.5 "Abort", IrOBEX 1.2
|
// Section 3.3.5 "Abort", IrOBEX 1.2
|
||||||
// [opcode:1][length:2][Headers:var]
|
// [opcode:1][length:2][Headers:var]
|
||||||
uint8_t req[255];
|
uint8_t req[255];
|
||||||
@ -1125,7 +1162,6 @@ void
|
|||||||
BluetoothOppManager::ReplyToConnect()
|
BluetoothOppManager::ReplyToConnect()
|
||||||
{
|
{
|
||||||
if (mConnected) return;
|
if (mConnected) return;
|
||||||
mConnected = true;
|
|
||||||
|
|
||||||
// Section 3.3.1 "Connect", IrOBEX 1.2
|
// Section 3.3.1 "Connect", IrOBEX 1.2
|
||||||
// [opcode:1][length:2][version:1][flags:1][MaxPktSizeWeCanReceive:2]
|
// [opcode:1][length:2][version:1][flags:1][MaxPktSizeWeCanReceive:2]
|
||||||
@ -1149,7 +1185,6 @@ void
|
|||||||
BluetoothOppManager::ReplyToDisconnect()
|
BluetoothOppManager::ReplyToDisconnect()
|
||||||
{
|
{
|
||||||
if (!mConnected) return;
|
if (!mConnected) return;
|
||||||
mConnected = false;
|
|
||||||
|
|
||||||
// Section 3.3.2 "Disconnect", IrOBEX 1.2
|
// Section 3.3.2 "Disconnect", IrOBEX 1.2
|
||||||
// [opcode:1][length:2][Headers:var]
|
// [opcode:1][length:2][Headers:var]
|
||||||
|
@ -9,9 +9,10 @@
|
|||||||
|
|
||||||
#include "BluetoothCommon.h"
|
#include "BluetoothCommon.h"
|
||||||
#include "BluetoothSocketObserver.h"
|
#include "BluetoothSocketObserver.h"
|
||||||
|
#include "DeviceStorage.h"
|
||||||
#include "mozilla/dom/ipc/Blob.h"
|
#include "mozilla/dom/ipc/Blob.h"
|
||||||
#include "mozilla/ipc/UnixSocket.h"
|
#include "mozilla/ipc/UnixSocket.h"
|
||||||
#include "DeviceStorage.h"
|
#include "nsCOMArray.h"
|
||||||
|
|
||||||
class nsIOutputStream;
|
class nsIOutputStream;
|
||||||
class nsIInputStream;
|
class nsIInputStream;
|
||||||
@ -54,7 +55,7 @@ public:
|
|||||||
void Disconnect();
|
void Disconnect();
|
||||||
bool Listen();
|
bool Listen();
|
||||||
|
|
||||||
bool SendFile(BlobParent* aBlob);
|
bool SendFile(const nsAString& aDeviceAddress, BlobParent* aBlob);
|
||||||
bool StopSendingFile();
|
bool StopSendingFile();
|
||||||
bool ConfirmReceivingFile(bool aConfirm);
|
bool ConfirmReceivingFile(bool aConfirm);
|
||||||
|
|
||||||
@ -88,6 +89,7 @@ public:
|
|||||||
private:
|
private:
|
||||||
BluetoothOppManager();
|
BluetoothOppManager();
|
||||||
void StartFileTransfer();
|
void StartFileTransfer();
|
||||||
|
void StartSendingNextFile();
|
||||||
void FileTransferComplete();
|
void FileTransferComplete();
|
||||||
void UpdateProgress();
|
void UpdateProgress();
|
||||||
void ReceivingFileConfirmation();
|
void ReceivingFileConfirmation();
|
||||||
@ -102,6 +104,8 @@ private:
|
|||||||
void AfterOppDisconnected();
|
void AfterOppDisconnected();
|
||||||
void ValidateFileName();
|
void ValidateFileName();
|
||||||
bool IsReservedChar(PRUnichar c);
|
bool IsReservedChar(PRUnichar c);
|
||||||
|
void ClearQueue();
|
||||||
|
void RetrieveSentFileName();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OBEX session status.
|
* OBEX session status.
|
||||||
@ -170,7 +174,9 @@ private:
|
|||||||
nsAutoArrayPtr<uint8_t> mBodySegment;
|
nsAutoArrayPtr<uint8_t> mBodySegment;
|
||||||
nsAutoArrayPtr<uint8_t> mReceivedDataBuffer;
|
nsAutoArrayPtr<uint8_t> mReceivedDataBuffer;
|
||||||
|
|
||||||
|
int mCurrentBlobIndex;
|
||||||
nsCOMPtr<nsIDOMBlob> mBlob;
|
nsCOMPtr<nsIDOMBlob> mBlob;
|
||||||
|
nsCOMArray<nsIDOMBlob> mBlobs;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A seperate member thread is required because our read calls can block
|
* A seperate member thread is required because our read calls can block
|
||||||
|
@ -2719,7 +2719,7 @@ BluetoothDBusService::SendFile(const nsAString& aDeviceAddress,
|
|||||||
BluetoothValue v = true;
|
BluetoothValue v = true;
|
||||||
nsAutoString errorStr;
|
nsAutoString errorStr;
|
||||||
|
|
||||||
if (!opp->SendFile(aBlobParent)) {
|
if (!opp->SendFile(aDeviceAddress, aBlobParent)) {
|
||||||
errorStr.AssignLiteral("Calling SendFile() failed");
|
errorStr.AssignLiteral("Calling SendFile() failed");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user