Understanding Mobile Data Synchronization: Creating Custom File Filters

Custom File Filters in a Nutshell

In the previous article, “Understanding Mobile Data Synchronization: Utilizing MS ActiveSync Capabilities at a High Level,” you learned how to utilize simple but useful MS ActiveSync things in your desktop applications that want to interact with your PDA. Now, you will investigate one more area still related to the PC side—Custom File Filters.

Let me briefly explain what I will talk about. When you want to copy files from the desktop to a handheld device and vice versa, you may be required to perform some data conversion; for example, from desktop Word’s format to Pocket Word’s format. The same is true for all other types of Office documents such as Access databases (.mdb/.cdb files) or Excel files (.xls/.pxl), fonts (.fon), and so forth. You may want to handle your data in similar way. Besides, you even might have a desire/requirement to make such a conversion on the desktop and then send your data to you PDA. File Filters will serve to your needs here!

So what is a File Filter? It’s just a standard desktop COM Server. The detailed answer is located in the replfilt.h header file. If you take a look, you find quite a few interfaces there to make the job easier for you:

  • ICeFileFilterSite
  • ICeFileFilter
  • ICeFileFilterOptions

You have to implement at least the ICeFileFilter interface as a normal COM object with all its related stuff. Once your filter is properly registered, MS ActiveSync then call will it upon all copy/move operations on your data files.

Note: File Filter won’t be called if you transfer the data any way other than via the Mobile Device folder.

As you might guess already, ActiveSync has no other way to know about your file filter than the Registry. Your COM server, which implements the ICeFileFilter interface, must be registered as a regular COM object with some additional stuff. This stuff includes a couple of keys under HKEY_CLASSES_ROOCLSIDYourFilterCLSID, file types registration, and appropriate Windows CE Services Registry entries. As a bottom line, to use your newly created file filter, you will have to perform a few magical passes over the desktop Registry using either CeUtil functions or the standard registry API. CeUtil functions will help you there because you may register file filter for already known devices as well as newly connected ones.

Implementing a Custom File Filter

Now, on to the real coding. Because File Filter is a regular COM Server, it has to have all the usual COM decorations. You can implement it your favorite way, with ATL or manually. You may even use C# if you want to. Just take care about the appropriate C# interface wrappers and method calling convention (stdcall), that’s all. For simplicity, I will use C++ for all samples.

So, skipping the COM stuff, the ICeFileFilter implementation is a piece of cake. It has only one important method: NextConvertFile. This method is called by ActiveSync when it needs to perform file conversion. It is declared as follows:

HRESULT __stdcall NextConvertFile (int nConversion,
                  PFF_CONVERTINFO *pci,
                  PFF_SOURCEFILE *psf,
                  PFF_DESTINATIONFILE *pdf,
                  volatile BOOL *pbCancel, PF_ERROR *perr);

As you see, parameters contain an information about source and destination files, PFF_CONVERTINFO struct that has members describing how to perform the conversion; for example, pointer to ICeFileFilterSite, and so forth. ICeFileFilterSite is used to manage read/write operations over source and destination files and so on. For more details, please refer to the SDK documentation. Your sample implementation will simply translate ASCII data to UNICODE. The actual declaration and code are presented below:

class CDat2LogFileFilter : public ICeFileFilter,
                           public ICeFileFilterOptions
{
private:
   long m_lRef;
   UINT m_nCodePage;
   BOOL m_bShowOptions;

public:
   CDat2LogFileFilter();
   ~CDat2LogFileFilter();

   // IUnknown methods
   HRESULT __stdcall QueryInterface(REFIID riid, LPVOID *ppvObj);
   ULONG __stdcall AddRef();
   ULONG __stdcall Release();

   // ICeFileFilter methods
   HRESULT __stdcall NextConvertFile (int nConversion,
                     PFF_CONVERTINFO *pci, PFF_SOURCEFILE *psf,
                     PFF_DESTINATIONFILE *pdf,
                     volatile BOOL *pbCancel, PF_ERROR *perr);
   HRESULT __stdcall FilterOptions (THIS_ HWND hwndParent);
   HRESULT __stdcall FormatMessage (THIS_ DWORD dwFlags,
                                    DWORD dwMessageId,
                     DWORD dwLanguageId, LPTSTR lpBuffer,
                     DWORD dwSize, va_list *args, DWORD *pcb);

   // ICeFileFilterOptions methods
   HRESULT __stdcall SetFilterOptions(CFF_CONVERTOPTIONS* pco);
};
...
HRESULT __stdcall CDat2LogFileFilter::NextConvertFile (
                  int nConversion,
                  CFF_CONVERTINFO *pci,
                  CFF_SOURCEFILE *psf,
                  CFF_DESTINATIONFILE *pdf,
                  volatile BOOL *pbCancel, CF_ERROR *perr)
{
   IStream *pstreamSrc = NULL;
   IStream  *pstreamDest = NULL;
   ICeFileFilterSite *pffs = NULL;
   DWORD cBytesRemaining, cBytesRead;
   HRESULT hr = 0;
   int nToRead = 0;
   ULONG ulTotalMoved = 0;
   BYTE pBuff[BUFFSIZE];
   WCHAR pWBuff[BUFFSIZE*2+2];
   WORD wUnicodeSig = 0xFEFF;
   BOOL bSrcIsUnicode = FALSE;

   // return if we're called not the very first time
   if (nConversion > 0)
      return HRESULT_FROM_WIN32(ERROR_NO_MORE_ITEMS);

   if ( m_bShowOptions )
      FilterOptions(pci->hwndParent);

   ZeroMemory(pBuff,sizeof(pBuff));
   ZeroMemory(pWBuff,sizeof(pWBuff));

   // Get pointer to FileFilterSite interface.
   pffs = pci->pffs;

   // Open source file.
   hr = pffs->OpenSourceFile(PF_OPENFLAT, (PVOID *)&pstreamSrc);
   if (!SUCCEEDED (hr))
   {
      *perr = HRESULT_TO_PFERROR (hr, ERROR_ACCESS_DENIED);
      return E_FAIL;
   }

   // Open destination file.
   hr = pffs->OpenDestinationFile(PF_OPENFLAT, pdf->szFullpath,
                                  (PVOID *)&pstreamDest);
   if (!SUCCEEDED (hr))
   {
      pffs->CloseSourceFile (pstreamSrc);
      *perr = HRESULT_TO_PFERROR (hr, ERROR_ACCESS_DENIED);
      return E_FAIL;
   }

   cBytesRemaining = psf->cbSize;
   if ( pci->bImport )
   {
      hr = pstreamDest->Write ((PBYTE)&wUnicodeSig, 2, NULL);
      if (!SUCCEEDED (hr))
      {
         pffs->CloseSourceFile (pstreamSrc);
         pffs->CloseDestinationFile (TRUE, pstreamDest);
         *perr = HRESULT_TO_PFERROR (hr, ERROR_ACCESS_DENIED);
         return E_FAIL;
      }
   }


   // Convert & Copy data.
   for (; cBytesRemaining > 0; )
   {
      nToRead = min (BUFFSIZE, cBytesRemaining);


      hr = pstreamSrc->Read (pBuff, nToRead, &cBytesRead);
      if (cBytesRead == 0)
         break;

      if (*pbCancel)
      {
         hr = ERROR_CANCELLED;
         break;
      }

      int nLen = MultiByteToWideChar(m_nCodePage,0,(char*)pBuff,
                                     cBytesRead,0,0);
      MultiByteToWideChar(m_nCodePage,0,(char*)pBuff,cBytesRead,
                          pWBuff,nLen);

      hr = pstreamDest->Write ((PBYTE)pWBuff, nLen*2, NULL);
      if (!SUCCEEDED (hr))
         break;

      ulTotalMoved += cBytesRead;
      cBytesRemaining -= cBytesRead;
      pffs->ReportProgress (ulTotalMoved/psf->cbSize * 100);
   }

   // Perform some cleanup
   pffs->CloseSourceFile (pstreamSrc);
   pffs->CloseDestinationFile (TRUE, pstreamDest);

   if (hr == ERROR_CANCELLED)
      return HRESULT_FROM_WIN32 (ERROR_CANCELLED);

   if (!SUCCEEDED (hr))
   {
      *perr = hr;
      return E_FAIL;
   }

   return HRESULT_FROM_WIN32(ERROR_NO_MORE_ITEMS);
}

More by Author

Get the Free Newsletter!

Subscribe to Developer Insider for top news, trends & analysis

Must Read