Bug 817972 - Make Gecko Bluetooth capable of queueing file-sending requests, r=gyeh, a=leo+
authorEric Chou <echou@mozilla.com>
Fri, 26 Apr 2013 18:09:00 +0800
changeset 119602 d85aa34a49f26986a323f7c7f9956bfc03fc3922
parent 119600 bc472d35730958963f4ee6131e36f1dbe2ef89de
child 119603 4ca769f0f1f21acae1121c4532dd6b140688bffc
push id23
push userryanvm@gmail.com
push dateThu, 13 Jun 2013 16:03:59 +0000
reviewersgyeh, leo
bugs817972
milestone18.1
Bug 817972 - Make Gecko Bluetooth capable of queueing file-sending requests, r=gyeh, a=leo+
dom/bluetooth/BluetoothOppManager.cpp
dom/bluetooth/BluetoothOppManager.h
dom/bluetooth/linux/BluetoothDBusService.cpp
--- a/dom/bluetooth/BluetoothOppManager.cpp
+++ b/dom/bluetooth/BluetoothOppManager.cpp
@@ -224,16 +224,17 @@ BluetoothOppManager::BluetoothOppManager
                                            , mBodySegmentLength(0)
                                            , mReceivedDataBufferOffset(0)
                                            , mAbortFlag(false)
                                            , mNewFileFlag(false)
                                            , mPutFinalFlag(false)
                                            , mSendTransferCompleteFlag(false)
                                            , mSuccessFlag(false)
                                            , mWaitingForConfirmationFlag(false)
+                                           , mCurrentBlobIndex(-1)
 {
   mConnectedDeviceAddress.AssignLiteral(BLUETOOTH_ADDRESS_NONE);
   Listen();
 }
 
 BluetoothOppManager::~BluetoothOppManager()
 {
 }
@@ -347,104 +348,81 @@ BluetoothOppManager::Listen()
       mL2capSocket = nullptr;
       return false;
     }
   }
 
   return true;
 }
 
-bool
-BluetoothOppManager::SendFile(BlobParent* aActor)
+void
+BluetoothOppManager::StartSendingNextFile()
 {
-  if (mBlob) {
-    // Means there's a sending process. Reply error.
-    return false;
-  }
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(!IsTransferring());
+  MOZ_ASSERT(mBlobs.Count() > 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();
+  // Before sending content, we have to send a header including
+  // information such as file name, file length and content type.
+  ExtractBlobHeaders();
+  StartFileTransfer();
 
-  sFileName.Truncate();
-
-  nsCOMPtr<nsIDOMFile> file = do_QueryInterface(mBlob);
-  if (file) {
-    file->GetName(sFileName);
+  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();
   }
 
-  /**
-   * 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");
-  }
+  mIsServer = false;
+}
 
-  int32_t offset = sFileName.RFindChar('/');
-  if (offset != kNotFound) {
-    sFileName = Substring(sFileName, offset + 1);
+bool
+BluetoothOppManager::SendFile(const nsAString& aDeviceAddress,
+                              BlobParent* aActor)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (mCurrentBlobIndex >= 0) {
+    if (mConnectedDeviceAddress != aDeviceAddress) {
+      return false;
+    }
+
+    mBlobs.AppendObject(aActor->GetBlob().get());
+    return true;
   }
 
-  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();
-  mIsServer = false;
-  StartFileTransfer();
-
+  mBlobs.AppendObject(aActor->GetBlob().get());
+  StartSendingNextFile();
   return true;
 }
 
 bool
 BluetoothOppManager::StopSendingFile()
 {
   mAbortFlag = true;
 
   return true;
 }
 
 bool
 BluetoothOppManager::ConfirmReceivingFile(bool aConfirm)
 {
-  if (!mConnected) return false;
+  NS_ENSURE_TRUE(mConnected, false);
+  NS_ENSURE_TRUE(mWaitingForConfirmationFlag, false);
 
-  if (!mWaitingForConfirmationFlag) {
-    NS_WARNING("We are not waiting for a confirmation now.");
-    return false;
-  }
+  MOZ_ASSERT(mPacketLeftLength == 0);
+
   mWaitingForConfirmationFlag = false;
 
-  NS_ASSERTION(mPacketLeftLength == 0,
-               "Should not be in the middle of receiving a PUT packet.");
-
   // For the first packet of first file
   bool success = false;
   if (aConfirm) {
     StartFileTransfer();
     if (CreateFile()) {
       success = WriteToFile(mBodySegment.get(), mBodySegmentLength);
     }
   }
@@ -493,20 +471,21 @@ BluetoothOppManager::AfterOppConnected()
 
 void
 BluetoothOppManager::AfterOppDisconnected()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   mConnected = false;
   mLastCommand = 0;
-  mBlob = nullptr;
   mDsFile = nullptr;
   mPacketLeftLength = 0;
 
+  ClearQueue();
+
   // We can't reset mSuccessFlag here since this function may be called
   // before we send system message of transfer complete
   // mSuccessFlag = false;
 
   if (mInputStream) {
     mInputStream->Close();
     mInputStream = nullptr;
   }
@@ -570,27 +549,21 @@ BluetoothOppManager::CreateFile()
   NS_ENSURE_TRUE(mOutputStream, false);
 
   return true;
 }
 
 bool
 BluetoothOppManager::WriteToFile(const uint8_t* aData, int aDataLength)
 {
-  if (!mOutputStream) {
-    NS_WARNING("No available output stream");
-    return false;
-  }
+  NS_ENSURE_TRUE(mOutputStream, false);
 
   uint32_t wrote = 0;
   mOutputStream->Write((const char*)aData, aDataLength, &wrote);
-  if (aDataLength != wrote) {
-    NS_WARNING("Writing to the file failed");
-    return false;
-  }
+  NS_ENSURE_TRUE(aDataLength == wrote, false);
 
   return true;
 }
 
 // Virtual function of class SocketConsumer
 void
 BluetoothOppManager::ExtractPacketHeaders(const ObexHeaderSet& aHeader)
 {
@@ -614,16 +587,18 @@ BluetoothOppManager::ExtractPacketHeader
 
     aHeader.GetBodyLength(&mBodySegmentLength);
   }
 }
 
 bool
 BluetoothOppManager::ExtractBlobHeaders()
 {
+  RetrieveSentFileName();
+
   nsresult rv = mBlob->GetType(sContentType);
   if (NS_FAILED(rv)) {
     NS_WARNING("Can't get content type");
     SendDisconnectRequest();
     return false;
   }
 
   uint64_t fileLength;
@@ -651,16 +626,62 @@ BluetoothOppManager::ExtractBlobHeaders(
     NS_WARNING("Can't create thread");
     SendDisconnectRequest();
     return false;
   }
 
   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
 BluetoothOppManager::IsReservedChar(PRUnichar c)
 {
   return (c < 0x0020 ||
           c == PRUnichar('?') || c == PRUnichar('|') || c == PRUnichar('<') ||
           c == PRUnichar('>') || c == PRUnichar('"') || c == PRUnichar(':') ||
           c == PRUnichar('/') || c == PRUnichar('*') || c == PRUnichar('\\'));
 }
@@ -817,16 +838,27 @@ BluetoothOppManager::ServerDataHandler(U
       NotifyAboutFileChange();
     }
   } else {
     NS_WARNING("Unhandled ObexRequestCode");
   }
 }
 
 void
+BluetoothOppManager::ClearQueue()
+{
+  mCurrentBlobIndex = -1;
+  mBlob = nullptr;
+
+  while (mBlobs.Count() > 0) {
+    mBlobs.RemoveObject(mBlobs[0]);
+  }
+}
+
+void
 BluetoothOppManager::ClientDataHandler(UnixSocketRawData* aMessage)
 {
   uint8_t opCode;
   int packetLength;
 
   if (mPacketLeftLength > 0) {
     opCode = mPutFinalFlag ? ObexRequestCode::PutFinal : ObexRequestCode::Put;
     packetLength = mPacketLeftLength;
@@ -855,17 +887,27 @@ BluetoothOppManager::ClientDataHandler(U
     NS_WARNING(str.get());
     FileTransferComplete();
     return;
   }
 
   if (mLastCommand == ObexRequestCode::PutFinal) {
     mSuccessFlag = true;
     FileTransferComplete();
-    SendDisconnectRequest();
+
+    if (mInputStream) {
+      mInputStream->Close();
+      mInputStream = nullptr;
+    }
+
+    if (mCurrentBlobIndex + 1 == mBlobs.Count()) {
+      SendDisconnectRequest();
+    } else {
+      StartSendingNextFile();
+    }
   } else if (mLastCommand == ObexRequestCode::Abort) {
     SendDisconnectRequest();
     FileTransferComplete();
   } else if (mLastCommand == ObexRequestCode::Disconnect) {
     AfterOppDisconnected();
     // Most devices will directly terminate connection after receiving
     // Disconnect request, so we make a delay here. If the socket hasn't been
     // disconnected, we will close it.
@@ -880,26 +922,18 @@ BluetoothOppManager::ClientDataHandler(U
     AfterOppConnected();
 
     // Keep remote information
     mRemoteObexVersion = aMessage->mData[3];
     mRemoteConnectionFlags = aMessage->mData[4];
     mRemoteMaxPacketLength =
       (((int)(aMessage->mData[5]) << 8) | aMessage->mData[6]);
 
-    /*
-     * 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);
-    }
+    sInstance->SendPutHeaderRequest(sFileName, sFileLength);
   } else if (mLastCommand == ObexRequestCode::Put) {
-
-    // Send PutFinal packet when we get response
     if (sWaitingToSendPutFinal) {
       SendPutFinalRequest();
       return;
     }
 
     if (mAbortFlag) {
       SendAbortRequest();
       return;
@@ -968,16 +1002,18 @@ BluetoothOppManager::SendConnectRequest(
   memcpy(s->mData, req, s->mSize);
   mSocket->SendSocketData(s);
 }
 
 void
 BluetoothOppManager::SendPutHeaderRequest(const nsAString& aFileName,
                                           int aFileSize)
 {
+  if (!mConnected) return;
+
   uint8_t* req = new uint8_t[mRemoteMaxPacketLength];
 
   int len = aFileName.Length();
   uint8_t* fileName = new uint8_t[(len + 1) * 2];
   const PRUnichar* fileNamePtr = aFileName.BeginReading();
 
   for (int i = 0; i < len; i++) {
     fileName[i * 2] = (uint8_t)(fileNamePtr[i] >> 8);
@@ -1057,32 +1093,36 @@ BluetoothOppManager::SendPutFinalRequest
   sWaitingToSendPutFinal = false;
 
   delete [] req;
 }
 
 void
 BluetoothOppManager::SendDisconnectRequest()
 {
+  if (!mConnected) return;
+
   // Section 3.3.2 "Disconnect", IrOBEX 1.2
   // [opcode:1][length:2][Headers:var]
   uint8_t req[255];
   int index = 3;
 
   SetObexPacketInfo(req, ObexRequestCode::Disconnect, index);
   mLastCommand = ObexRequestCode::Disconnect;
 
   UnixSocketRawData* s = new UnixSocketRawData(index);
   memcpy(s->mData, req, s->mSize);
   mSocket->SendSocketData(s);
 }
 
 void
 BluetoothOppManager::SendAbortRequest()
 {
+  if (!mConnected) return;
+
   // Section 3.3.5 "Abort", IrOBEX 1.2
   // [opcode:1][length:2][Headers:var]
   uint8_t req[255];
   int index = 3;
 
   SetObexPacketInfo(req, ObexRequestCode::Abort, index);
   mLastCommand = ObexRequestCode::Abort;
 
@@ -1102,17 +1142,16 @@ BluetoothOppManager::GetAddress(nsAStrin
 {
   return mSocket->GetAddress(aDeviceAddress);
 }
 
 void
 BluetoothOppManager::ReplyToConnect()
 {
   if (mConnected) return;
-  mConnected = true;
 
   // Section 3.3.1 "Connect", IrOBEX 1.2
   // [opcode:1][length:2][version:1][flags:1][MaxPktSizeWeCanReceive:2]
   // [Headers:var]
   uint8_t req[255];
   int index = 7;
 
   req[3] = 0x10; // version=1.0
@@ -1126,17 +1165,16 @@ BluetoothOppManager::ReplyToConnect()
   memcpy(s->mData, req, s->mSize);
   mSocket->SendSocketData(s);
 }
 
 void
 BluetoothOppManager::ReplyToDisconnect()
 {
   if (!mConnected) return;
-  mConnected = false;
 
   // Section 3.3.2 "Disconnect", IrOBEX 1.2
   // [opcode:1][length:2][Headers:var]
   uint8_t req[255];
   int index = 3;
 
   SetObexPacketInfo(req, ObexResponseCode::Success, index);
 
@@ -1365,18 +1403,17 @@ BluetoothOppManager::OnConnectSuccess(Bl
   // device disconnect with us.
   mSocket->GetAddress(mConnectedDeviceAddress);
 }
 
 void
 BluetoothOppManager::OnConnectError(BluetoothSocket* aSocket)
 {
   if (mRunnable) {
-    BluetoothValue v;
-    DispatchBluetoothReply(mRunnable, v,
+    DispatchBluetoothReply(mRunnable, BluetoothValue(),
                            NS_LITERAL_STRING("OnConnectError:no runnable"));
     mRunnable = nullptr;
   }
 
   mSocket = nullptr;
   mRfcommSocket = nullptr;
   mL2capSocket = nullptr;
 
--- a/dom/bluetooth/BluetoothOppManager.h
+++ b/dom/bluetooth/BluetoothOppManager.h
@@ -5,19 +5,20 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_bluetooth_bluetoothoppmanager_h__
 #define mozilla_dom_bluetooth_bluetoothoppmanager_h__
 
 #include "BluetoothCommon.h"
 #include "BluetoothProfileManagerBase.h"
 #include "BluetoothSocketObserver.h"
+#include "DeviceStorage.h"
 #include "mozilla/dom/ipc/Blob.h"
 #include "mozilla/ipc/UnixSocket.h"
-#include "DeviceStorage.h"
+#include "nsCOMArray.h"
 
 class nsIOutputStream;
 class nsIInputStream;
 class nsIVolumeMountLock;
 
 BEGIN_BLUETOOTH_NAMESPACE
 
 class BluetoothReplyRunnable;
@@ -52,17 +53,17 @@ public:
    * either call Disconnect() to close RFCOMM connection or start another
    * file-sending thread via calling SendFile() again.
    */
   void Connect(const nsAString& aDeviceObjectPath,
                BluetoothReplyRunnable* aRunnable);
   void Disconnect();
   bool Listen();
 
-  bool SendFile(BlobParent* aBlob);
+  bool SendFile(const nsAString& aDeviceAddress, BlobParent* aBlob);
   bool StopSendingFile();
   bool ConfirmReceivingFile(bool aConfirm);
 
   void SendConnectRequest();
   void SendPutHeaderRequest(const nsAString& aFileName, int aFileSize);
   void SendPutRequest(uint8_t* aFileBody, int aFileBodyLength);
   void SendPutFinalRequest();
   void SendDisconnectRequest();
@@ -88,33 +89,37 @@ public:
   virtual void OnDisconnect(BluetoothSocket* aSocket) MOZ_OVERRIDE;
   virtual void OnGetServiceChannel(const nsAString& aDeviceAddress,
                                    const nsAString& aServiceUuid,
                                    int aChannel) MOZ_OVERRIDE;
 
 private:
   BluetoothOppManager();
   void StartFileTransfer();
+  void StartSendingNextFile();
   void FileTransferComplete();
   void UpdateProgress();
   void ReceivingFileConfirmation();
   bool CreateFile();
   bool WriteToFile(const uint8_t* aData, int aDataLength);
   void DeleteReceivedFile();
   void ReplyToConnect();
   void ReplyToDisconnect();
   void ReplyToPut(bool aFinal, bool aContinue);
   void AfterOppConnected();
   void AfterFirstPut();
   void AfterOppDisconnected();
   void ValidateFileName();
   bool IsReservedChar(PRUnichar c);
+  void ClearQueue();
+  void RetrieveSentFileName();
   DeviceStorageFile* CreateDeviceStorageFile(nsIFile* aFile);
   void NotifyAboutFileChange();
   bool AcquireSdcardMountLock();
+
   /**
    * OBEX session status.
    * Set when OBEX session is established.
    */
   bool mConnected;
   nsString mConnectedDeviceAddress;
 
   /**
@@ -172,17 +177,19 @@ private:
    * Set when receiving the first PUT packet and wait for
    * ConfirmReceivingFile() to be called.
    */
   bool mWaitingForConfirmationFlag;
 
   nsAutoArrayPtr<uint8_t> mBodySegment;
   nsAutoArrayPtr<uint8_t> mReceivedDataBuffer;
 
+  int mCurrentBlobIndex;
   nsCOMPtr<nsIDOMBlob> mBlob;
+  nsCOMArray<nsIDOMBlob> mBlobs;
 
   /**
    * A seperate member thread is required because our read calls can block
    * execution, which is not allowed to happen on the IOThread.
    * 
    */
   nsCOMPtr<nsIThread> mReadFileThread;
   nsCOMPtr<nsIOutputStream> mOutputStream;
--- a/dom/bluetooth/linux/BluetoothDBusService.cpp
+++ b/dom/bluetooth/linux/BluetoothDBusService.cpp
@@ -2778,17 +2778,17 @@ BluetoothDBusService::SendFile(const nsA
   // Currently we only support one device sending one file at a time,
   // so we don't need aDeviceAddress here because the target device
   // has been determined when calling 'Connect()'. Nevertheless, keep
   // it for future use.
   BluetoothOppManager* opp = BluetoothOppManager::Get();
   BluetoothValue v = true;
   nsAutoString errorStr;
 
-  if (!opp->SendFile(aBlobParent)) {
+  if (!opp->SendFile(aDeviceAddress, aBlobParent)) {
     errorStr.AssignLiteral("Calling SendFile() failed");
   }
 
   DispatchBluetoothReply(aRunnable, v, errorStr);
 }
 
 void
 BluetoothDBusService::StopSendingFile(const nsAString& aDeviceAddress,