How to: Create an Unmanaged Feed Producer

This topic shows how to use an unmanaged language, such as C++, to create an application that uses Sync Services for FeedSync to produce an RSS feed from a list of files in a folder. The RSS feed produced by this application contains an item for each file in the specified folder. Each item in the feed contains the contents of its associated file as well as FeedSync metadata about the item.

This topic assumes a basic familiarity with C++ and COM concepts.

The examples in this topic focus on the following Sync Services for FeedSync components:

Understanding Feed Producers

A feed producer is a software component that produces a FeedSync feed that contains items that are provided by a synchronization provider. The application implements the IFeedIdConverter interface to convert IDs from the format of the provider to the FeedSync format, and implements IFeedItemConverter to convert item data from the format of the provider to the FeedSync format.

For more information about producing a FeedSync feed, see Producing RSS and Atom Feeds.

For more information about synchronization providers, see Synchronization Providers.

Build Requirements

  • Synchronization.h, FeedSync.h, FileSyncProvider.h: declarations for Sync Framework core components, Sync Services for FeedSync components, and Sync Services for File Systems components.

    #include <Synchronization.h>
    #include <FeedSync.h>
    #include <FileSyncProvider.h>
    
  • Synchronization.lib, FeedSync.lib, FileSyncProvider.lib: import libraries.

Example

The example code in this topic shows how to use an IFeedProducer object to produce an RSS feed that contains items provided by an IFileSyncProvider object. The example also shows how to implement the interfaces that convert IDs and item data from the format of the file sync provider to the FeedSync format.

IFileSyncProvider is a component of Sync Services for File Systems. For more information about Sync Services for File Systems, see this Microsoft Web site.

Implementing IFeedIdConverter

The IDs that a provider uses can be in any format. Therefore, Sync Services for FeedSync requires that an application implement the IFeedIdConverter interface to convert the IDs from the provider format to the FeedSync format and vice versa.

Declaring IFeedIdConverter

Add IFeedIdConverter to the class inheritance list.

class CFileSyncProviderIdConverter : public IFeedIdConverter

Add the IFeedIdConverter methods to the class declaration.

STDMETHOD(GetIdParameters)(
    ID_PARAMETERS * pIdParameters);

STDMETHOD(ConvertReplicaIdToString)(
    const BYTE * pbReplicaId, 
    IFeedIdConverterCallback * pCallback);

STDMETHOD(ConvertItemIdToString)(
    const BYTE * pbItemId, 
    IFeedIdConverterCallback * pCallback);

STDMETHOD(ConvertStringToReplicaId)(
    LPCWSTR wszStringId, 
    IFeedIdConverterCallback * pCallback);

STDMETHOD(ConvertStringToItemId)(
    LPCWSTR wszStringId, 
    IFeedIdConverterCallback * pCallback);

STDMETHOD(GenerateAnonymousReplicaId)(
    LPCWSTR wszWhen,
    ULONG ulSequence,
    IFeedIdConverterCallback * pCallback);

GetIdParameters Method

Sync Services calls IFeedIdConverter::GetIdParameters to obtain the ID format schema used by the provider. The implementation in this example returns the ID format schema retrieved from the IFileSyncProvider object. For the code that retrieves and stores the schema, see the section "Producing an RSS Feed" later in this topic.

STDMETHODIMP CFileSyncProviderIdConverter::GetIdParameters(
    ID_PARAMETERS * pIdParameters)
{
    HRESULT hr = E_FAIL;

    if (NULL == pIdParameters)
    {
        return E_POINTER;
    }
    else
    {
        *pIdParameters = m_idParams;
        return S_OK;
    }

    return hr;
}

ConvertReplicaIdToString Method

Sync Services for FeedSync calls IFeedIdConverter::ConvertReplicaIdToString to convert a replica ID from the provider format to a string. The string representation of the ID can be of any form and is written verbatim to the feed. The implementation in this example uses the OLE32 function StringFromGUID2 to convert the replica ID from a GUID to a WCHAR string. It returns the resulting string by using the IFeedIdConverterCallback::ConvertReplicaIdToStringComplete method.

STDMETHODIMP CFileSyncProviderIdConverter::ConvertReplicaIdToString(
    const BYTE * pbReplicaId, 
    IFeedIdConverterCallback * pCallback)
{
    HRESULT hr = E_FAIL;

    if (NULL == pbReplicaId || NULL == pCallback)
    {
        hr = E_POINTER;    
    }
    else
    {
        OLECHAR olestrReplicaId[64];
        DWORD cchId = 64;
        GUID* pguidReplicaId = (GUID*)pbReplicaId;

        int cchCopied = StringFromGUID2(*pguidReplicaId, olestrReplicaId, cchId);
        if (0 < cchCopied)
        {
            hr = pCallback->ConvertReplicaIdToStringComplete(olestrReplicaId);
        }
    }

    return hr;
}

ConvertItemIdToString Method

Sync Services for FeedSync calls IFeedIdConverter::ConvertItemIdToString to convert an item ID from the provider format to a string. The string representation of the ID can be of any form and is written verbatim to the feed. The implementation in this example converts an item ID formatted as a SYNC_GID structure to a WCHAR string. It uses the CRT function _ui64tow_s to convert the prefix part from a ULONGLONG to a WCHAR string. It also uses the OLE32 function StringFromGUID2 to convert the GUID part of the ID to a WCHAR string. The example concatenates these two strings into one and returns the resulting string by using IFeedIdConverterCallback::ConvertItemIdToStringComplete.

STDMETHODIMP CFileSyncProviderIdConverter::ConvertItemIdToString(
    const BYTE * pbItemId, 
    IFeedIdConverterCallback * pCallback)
{
    HRESULT hr = E_FAIL;

    if (NULL == pbItemId || NULL == pCallback)
    {
        hr = E_POINTER;    
    }
    else
    {
        SYNC_GID* pgid = (SYNC_GID*)pbItemId;
        
        // Convert the prefix to a string.
        errno_t err;
        WCHAR wszId[64];
        DWORD cchId = 64;
        err = _ui64tow_s(pgid->ullGidPrefix, wszId, cchId, 16);
        if (0 == err)
        {
            // Convert the GUID part to a string, appended to the prefix string.
            size_t cchPrefix = wcslen(wszId);
            int cchCopied = StringFromGUID2(pgid->guidUniqueId, &(wszId[cchPrefix]), cchId - cchPrefix);
            if (0 < cchCopied)
            {
                // Send the converted ID.
                hr = pCallback->ConvertItemIdToStringComplete(wszId);                
            }
        }
        else
        {
            hr = HRESULT_FROM_WIN32(err);                
        }
    }

    return hr;
}

ConvertStringToReplicaId Method

Sync Services for FeedSync calls IFeedIdConverter::ConvertStringToReplicaId to convert a replica ID from a string to the provider format. The string representation is exactly what was returned to Sync Services for FeedSync in the ConvertReplicaIdToString method. The implementation in this example uses the OLE32 function CLSIDFromString to convert the replica ID from a WCHAR string to a GUID. It returns the resulting ID by using the IFeedIdConverterCallback::ConvertStringToReplicaIdComplete method.

STDMETHODIMP CFileSyncProviderIdConverter::ConvertStringToReplicaId(
    LPCWSTR wszStringId, 
    IFeedIdConverterCallback * pCallback)
{
    HRESULT hr = E_FAIL;

    if (NULL == wszStringId || NULL == pCallback)
    {
        hr = E_POINTER;    
    }
    else
    {
        GUID guidReplicaId;

        hr = CLSIDFromString((LPOLESTR)wszStringId, &guidReplicaId);
        if (SUCCEEDED(hr))
        {
            hr = pCallback->ConvertStringToReplicaIdComplete((BYTE*)&guidReplicaId);
        }
    }

    return hr;
}

ConvertStringToItemId Method

Sync Services for FeedSync calls IFeedIdConverter::ConvertStringToItemId to convert an item ID from a string to the provider format. The string representation is exactly what was returned to Sync Services for FeedSync in the ConvertItemIdToString method. The implementation in this example uses the CRT function wcstoui64 to convert the prefix part of the ID from a WCHAR string to a ULONGLONG. It also uses the OLE32 function CLSIDFromString to convert the GUID part of the ID from a WCHAR string to a GUID. The example returns the resulting ID formatted as a SYNC_GID by using the IFeedIdConverterCallback::ConvertStringToItemIdComplete method.

STDMETHODIMP CFileSyncProviderIdConverter::ConvertStringToItemId(
    LPCWSTR wszStringId, 
    IFeedIdConverterCallback * pCallback)
{
    HRESULT hr = E_FAIL;

    if (NULL == wszStringId || NULL == pCallback)
    {
        hr = E_POINTER;    
    }
    else
    {
        SYNC_GID gid;
        
        // Convert the prefix from the string.
        WCHAR* pwszGuid = NULL;
        gid.ullGidPrefix = _wcstoui64(wszStringId, &pwszGuid, 16);

        // Convert the GUID part from the string.
        hr = CLSIDFromString(pwszGuid, &(gid.guidUniqueId));
        if (SUCCEEDED(hr))
        {
            // Send the converted ID.
            hr = pCallback->ConvertStringToItemIdComplete((BYTE*)&gid);
        }
    }

    return hr;
}

Methods That Are Not Implemented

The following method is not required for basic feed producer scenarios. This method can return E_NOTIMPL:

Implementing IFeedItemConverter

Item data from a provider can be in any format. Therefore, Sync Services for FeedSync requires that an application implement the IFeedItemConverter interface to convert item data from the provider format to the FeedSync format and vice versa.

Declaring IFeedItemConverter

Add IFeedItemConverter to the class inheritance list.

class CFileSyncProviderItemConverter : public IFeedItemConverter

Add the IFeedItemConverter methods to the class declaration.

STDMETHOD(ConvertItemDataToXml)(
    IUnknown *pItemData,
    IFeedItemConverterCallback *pCallback);

STDMETHOD(ConvertItemDataToXmlText)(
    IUnknown *pItemData,
    IFeedItemConverterCallback *pCallback);
   
STDMETHOD(ConvertXmlToItemData)(
    IUnknown * pItemXml,
    IFeedItemConverterCallback *pCallback);

STDMETHOD(ConvertXmlTextToItemData)(
    LPCWSTR wszItemXmlText,
    IFeedItemConverterCallback *pCallback);

ConvertItemDataToXmlText

Sync Services for FeedSync calls IFeedItemConverter::ConvertItemDataToXmlText to convert item data from the provider format to XML text. ConvertItemDataToXmlText is called when IFeedItemConverter::ConvertItemDataToXml returns E_NOTIMPL. The implementation in this example expects the files that are being produced to contain valid XML for an RSS item, in a Unicode format. An example of the file contents follows.

<item>
  <title>Sample</title>
  <description>A sample item.</description>
</item>

IFileSyncProvider provides the contents of a file as an IFileDataRetriever object. The implementation in this example gets an IStream object from the IFileDataRetriever object and uses it to read the contents of the file into a WCHAR string. It returns the resulting string by using the IFeedItemConverterCallback::ConvertItemDataToXmlTextComplete method.

STDMETHODIMP CFileSyncProviderItemConverter::ConvertItemDataToXmlText(
    IUnknown *pItemData,
    IFeedItemConverterCallback *pCallback)
{
    HRESULT hr = E_UNEXPECTED;

    if (NULL == pItemData || NULL == pCallback)
    {
        hr = E_POINTER;
    }
    else
    {
        // Get the data retriever implemented by Sync Services for File Systems.
        IFileDataRetriever* pItemRetriever = NULL;
        hr = pItemData->QueryInterface(__uuidof(pItemRetriever), (void**)&pItemRetriever);
        if (SUCCEEDED(hr))
        {
            // Get the IStream out of the data retriever.
            IStream* pItemStream = NULL;
            hr = pItemRetriever->GetFileStream(&pItemStream);
            if (SUCCEEDED(hr))
            {
                STATSTG ssFileData = {0};
                hr = pItemStream->Stat(&ssFileData, STATFLAG_DEFAULT);
                if (SUCCEEDED(hr))
                {
                    // Only handle a maximum file size that will fit in ULONG, for convenience.
                    ULONG cbFileData = (ULONG)ssFileData.cbSize.QuadPart;
                    WCHAR* pwszFileData = (WCHAR*)new BYTE[cbFileData + sizeof(WCHAR)]; // include space for NULL terminator
                    if (NULL == pwszFileData)
                    {
                        hr = E_OUTOFMEMORY;                 
                    }
                    else
                    {
                        ULONG cbRead;
                        hr = pItemStream->Read(pwszFileData, cbFileData, &cbRead);
                        if (cbRead != cbFileData)
                        {
                            hr = E_UNEXPECTED;
                        }
                        else
                        {
                            // Sync Services for FeedSync expects a NULL terminator on the XML string.
                            pwszFileData[cbFileData / sizeof(WCHAR)] = L'\0';
                            if (SUCCEEDED(hr))
                            {
                                hr = pCallback->ConvertItemDataToXmlTextComplete(pwszFileData);
                                
                                delete [] pwszFileData;
                            }
                        }
                    }
                }

                pItemStream->Release();
            }

            pItemRetriever->Release();
        }
    }

    return hr;
}

Methods That Are Not Implemented

The following methods are not required for basic feed producer scenarios. These methods can return E_NOTIMPL:

Producing an RSS Feed

Sync Services for FeedSync supplies the IFeedProducer Interface interface to help a provider produce items from its associated replica to a FeedSync feed. The implementation in this example uses an IFileSyncProvider object as the provider, and a folder in the file system as the replica. The example code takes the following steps to produce the feed:

  1. Creates an IFileSyncProvider object and configures it by specifying the folder to synchronize, and a filter that only includes files that have a .txt extension.

  2. Creates an IStream object and initializes it with an empty RSS feed. The following code declares the empty RSS feed.

    const CHAR c_szEmptyRSS[] = 
        "<?xml version=\"1.0\"?>\r\n"
        "<rss version=\"2.0\" xmlns:sx=\"https://www.microsoft.com/schemas/sse\">\r\n"
        "\t<channel>\r\n"
        "\t</channel>\r\n"
        "</rss>\r\n";
    
  3. Creates an IFeedProducer object and calls its IFeedProducer::ProduceFeed method.

  4. Writes the feed, which has been written to the IStream object by Sync Services for FeedSync, to a file in the replica folder.

The following code produces the feed.

HRESULT CFeedSynchronizerDlg::ProduceFeed(CString* pstrSyncFolder, const GUID* pguidReplica)
{
    HRESULT hr;

    // Create an IFileSyncProvider to represent the folder to produce.
    IFileSyncProvider* pFSP = NULL;
    hr = CoCreateInstance(CLSID_FileSyncProvider, NULL, CLSCTX_INPROC_SERVER,
        IID_IFileSyncProvider, (void**)&pFSP);
    if (SUCCEEDED(hr))
    {
        IFileSyncScopeFilter* pFilter = NULL;
        hr = pFSP->CreateNewScopeFilter(&pFilter);
        if (SUCCEEDED(hr))
        {
            // Filter folder contents to only include files with a .txt extension.
            hr = pFilter->SetFilenameIncludes(L"*.txt");

            // Keep a metadata store file in the same folder we are synchronizing.
            CString strMetaPath(*pstrSyncFolder);
            strMetaPath.Append(L"\\metadata.dat");

            hr = pFSP->Initialize(*pguidReplica, pstrSyncFolder->GetString(), 
                strMetaPath.GetString(), pstrSyncFolder->GetString(), 0, pFilter, NULL, NULL);
            if (SUCCEEDED(hr))
            {
                // Save the File Sync Provider's ID format schema so we can return it when asked.
                hr = pFSP->GetIdParameters(&(m_IdConverter.m_idParams));
                if (SUCCEEDED(hr))
                {
                    // Use the IStorage and IStream implementation provided by OLE32.
                    IStorage* pStg = NULL;
                    // Create a structured storage object backed by a temporary file.
                    hr = StgCreateDocfile(NULL, STGM_READWRITE | STGM_SHARE_EXCLUSIVE | STGM_CREATE 
                        | STGM_DIRECT | STGM_DELETEONRELEASE, 0, &pStg);
                    if (SUCCEEDED(hr))
                    {
                        IStream* pStream = NULL;
                        // Create an IStream object that can be used to read and write to the IStorage object.
                        hr = pStg->CreateStream(L"MyRSSStream", STGM_READWRITE | STGM_SHARE_EXCLUSIVE | STGM_DIRECT 
                            | STGM_CREATE, 0, 0, &pStream);
                        if (SUCCEEDED(hr))
                        {
                            // Initialize the stream with an empty RSS feed. This must be a single-byte CHAR 
                            // (not WCHAR) string and must not contain a NULL terminator or ProduceFeed will 
                            // fail with E_FAIL.
                            hr = pStream->Write(c_szEmptyRSS, sizeof(c_szEmptyRSS) - 1, NULL);
                            if (SUCCEEDED(hr))
                            {
                                // The stream is currently pointed at the end of the stream, so seek back to the beginning.
                                LARGE_INTEGER liSeek = {0};
                                hr = pStream->Seek(liSeek, STREAM_SEEK_SET, NULL);
                                if (SUCCEEDED(hr))
                                {
                                    // Create the FeedSync producer object.
                                    IFeedProducerConsumerServices* pFeedSvc = NULL;
                                    hr = CoCreateInstance(CLSID_FeedSyncServices, NULL, CLSCTX_INPROC_SERVER,
                                        IID_IFeedProducerConsumerServices, (void**)&pFeedSvc);
                                    if (SUCCEEDED(hr))
                                    {
                                        IFeedProducer* pFeedProducer = NULL;
                                        hr = pFeedSvc->CreateFeedProducer(&pFeedProducer);
                                        if (SUCCEEDED(hr))
                                        {

                                            // Produce the *.txt items in the specified folder to the stream.
                                            hr = pFeedProducer->ProduceFeed(pFSP, &m_IdConverter, &m_ItemConverter, NULL, pStream);
                                            if (SUCCEEDED(hr))
                                            {
                                                // The stream now contains an RSS feed filled with the contents of the .txt files
                                                // from the specified folder and with FeedSync metadata about each item.
                                                
                                                // Save the contents of the stream to a file.
                                                STATSTG stat = {0};
                                                hr = pStream->Stat(&stat, STATFLAG_DEFAULT);
                                                if (SUCCEEDED(hr))
                                                {
                                                    ULONG cbFeed = (ULONG)stat.cbSize.QuadPart;
                                                    CHAR* pszFeed = new CHAR[cbFeed];
                                                    if (NULL == pszFeed)
                                                    {
                                                        hr = E_OUTOFMEMORY;                                                
                                                    }
                                                    else
                                                    {
                                                        // Seek to the beginning of the stream.
                                                        hr = pStream->Seek(liSeek, STREAM_SEEK_SET, NULL);
                                                        if (SUCCEEDED(hr))
                                                        {
                                                            // Read the contents of the stream.
                                                            hr = pStream->Read(pszFeed, cbFeed, NULL);
                                                            if (SUCCEEDED(hr))
                                                            {
                                                                // Write the contents of the stream to a file.
                                                                CString strProducedFeed(*pstrSyncFolder);
                                                                strProducedFeed.Append(L"\\ProducedFeed.xml");
                                                                CFile fileStream(strProducedFeed.GetString(), CFile::modeCreate | CFile::modeWrite 
                                                                    | CFile::shareDenyNone);
                                                                fileStream.Write(pszFeed, cbFeed);
                                                            }
                                                        }

                                                        delete [] pszFeed;
                                                    }
                                                }
                                            }

                                            pFeedProducer->Release();
                                        }

                                        pFeedSvc->Release();                
                                    }
                                }
                            }

                            pStream->Release();                            
                        }

                        pStg->Release();
                    }
                }
            }

            pFilter->Release();        
        }

        pFSP->Release();    
    }

    return hr;
}

Next Steps

Now that you have created a FeedSync producer, you might want to create a feed consumer. A feed consumer is a software component that extracts items from a FeedSync feed and applies them to a destination replica by using a synchronization provider. For more information, see Consuming RSS and Atom Feeds.

See Also

Concepts

Producing RSS and Atom Feeds
Consuming RSS and Atom Feeds
Converting IDs and Items for RSS and Atom Feeds
Sync Services for FeedSync Components

Other Resources

Synchronizing RSS and Atom Feeds