WPD: Transfer Content To A Device

April 17, 2012

IntroductionKindle

Mayhap came here sooner than I thought. The previous article on the WPD API dealt with deleting resources. Too destructive perhaps, let’s create some resources this time.

My Kindle’s is hooked up to my PC. Let’s see if I can transfer a book to it.

Table Of Contents

TransferContentToDevice Method

Get the source code of the fourth article, #69: WPD: Deleting Resources, from the download page. Unzip it and open it up in Visual Studio.

Solution Explorer

In the Solution Explorer select the Program.cs file and open it. Clear the Main(…) method and add the following code to it.

var devices = new PortableDeviceCollection();
devices.Refresh();
var kindle = devices.First();
kindle.Connect();

kindle.TransferContentToDevice(
    @"d:\temp\Kindle_Users_Guide.azw",
    @"g:\documents");

kindle.Disconnect();

First we establish a connection to the Kindle, copy the book to it and finally disconnect from it

The magic happens in the TransferContentToDevice(…) method, which takes two parameters:

  • fileName: Full path of the file which you want to copy to the device
  • parentObjectId: The ID of the parent folder into which you want to copy the file

Modify the values of these parameters to suit your situation.

Top of page

Required Properties

Before we can start to implement the TransferContentToDevice(…) method we first need to add another private method to the PortableDevice class type.

private IPortableDeviceValues GetRequiredPropertiesForContentType(
    string fileName,
    string parentObjectId)
{
    //...
}

Before you can upload a resource to a device you need to collect some information about it. This information is stored in a IPortableDeviceValues collection.

Depending on the resource type you may need to collect more data (or properties) that describe the resource. Keep that it mind, the number of required properties might differ for your situation.

For adding a book to a Kindle you need to know the following information:

  • WPD_OBJECT_PARENT_ID: The ID of the folder into which you want to upload the resource
  • WPD_OBJECT_SIZE: The size of the object (in bytes)
  • WPD_OBJECT_ORIGINAL_FILE_NAME: The original filename of the object
  • WPD_OBJECT_NAME: The name of the object as you wish to represent it on the device

Time to implement the method. First you need to create a IPortableDeviceValues collection.

IPortableDeviceValues values = 
    new PortableDeviceTypesLib.PortableDeviceValues() as IPortableDeviceValues;

Then you can add the WPD_OBJECT_PARENT_ID to the collection:

var WPD_OBJECT_PARENT_ID = new _tagpropertykey();
WPD_OBJECT_PARENT_ID.fmtid = 
    new Guid(0xEF6B490D, 0x5CD8, 0x437A, 0xAF, 0xFC, 
                0xDA, 0x8B, 0x60, 0xEE, 0x4A, 0x3C);
WPD_OBJECT_PARENT_ID.pid = 3 ;
values.SetStringValue(ref WPD_OBJECT_PARENT_ID, parentObjectId);

Adding the rest of the properties is similar.

FileInfo fileInfo = new FileInfo(fileName);
var WPD_OBJECT_SIZE = new _tagpropertykey();
WPD_OBJECT_SIZE.fmtid = 
    new Guid(0xEF6B490D, 0x5CD8, 0x437A, 0xAF, 0xFC, 
                0xDA, 0x8B, 0x60, 0xEE, 0x4A, 0x3C);
WPD_OBJECT_SIZE.pid = 11;            
values.SetUnsignedLargeIntegerValue(WPD_OBJECT_SIZE, (ulong) fileInfo.Length);

var WPD_OBJECT_ORIGINAL_FILE_NAME = new _tagpropertykey();
WPD_OBJECT_ORIGINAL_FILE_NAME.fmtid = 
    new Guid(0xEF6B490D, 0x5CD8, 0x437A, 0xAF, 0xFC, 
                0xDA, 0x8B, 0x60, 0xEE, 0x4A, 0x3C);
WPD_OBJECT_ORIGINAL_FILE_NAME.pid = 12;
values.SetStringValue(WPD_OBJECT_ORIGINAL_FILE_NAME, Path.GetFileName(fileName));

var WPD_OBJECT_NAME = new _tagpropertykey();
WPD_OBJECT_NAME.fmtid = 
    new Guid(0xEF6B490D, 0x5CD8, 0x437A, 0xAF, 0xFC, 
                0xDA, 0x8B, 0x60, 0xEE, 0x4A, 0x3C);
WPD_OBJECT_NAME.pid = 4;
values.SetStringValue(WPD_OBJECT_NAME, Path.GetFileName(fileName));

Remark: To keep it easy I extracted the filename and path of the book and used that to set both the WPD_OBJECT_ORIGINAL_FILE_NAME and WPD_OBJECT_NAME properties.

Last but not least, don’t forget to return the collection.

return values;

Top of page

Transferring Content

Now that you can gather all the required properties you can start transferring content to the device. Let’s implement the TransferContentToDevice(…) method.

Get an IPortableDeviceContent interface to access resource specific methods.

IPortableDeviceContent content;
this._device.Content(out content);

Now using the GetRequiredPropertiesForContentType(…) method retrieve the required properties for the resource you want to upload.

IPortableDeviceValues values =
    GetRequiredPropertiesForContentType(fileName, parentObjectId);

Then get an IStream and the optimal buffer size to transfer the resource. This can be done by using the CreateObjectWithPropertiesAndData(…) method of the IPortableDeviceContent instance.

PortableDeviceApiLib.IStream tempStream;
uint optimalTransferSizeBytes = 0;
content.CreateObjectWithPropertiesAndData(
    values,
    out tempStream,
    ref optimalTransferSizeBytes,
    null);      

Convert the IStream to an System.Runtime.InteropService.ComTypes.IStream.
Top of page

System.Runtime.InteropServices.ComTypes.IStream targetStream = 
    (System.Runtime.InteropServices.ComTypes.IStream) tempStream;

All that remains is to open a FileStream, read the source file and write it to the target stream. Make sure you close the IStream afterwards (try…finally).

try
{
    using (var sourceStream = 
        new FileStream(fileName, FileMode.Open, FileAccess.Read))
    {
        var buffer = new byte[optimalTransferSizeBytes];
        int bytesRead;
        do
        {
            bytesRead = sourceStream.Read(
                buffer, 0, (int)optimalTransferSizeBytes);
            IntPtr pcbWritten = IntPtr.Zero;
            targetStream.Write(
                buffer, (int)optimalTransferSizeBytes, pcbWritten);
        } while (bytesRead > 0);
    }
    targetStream.Commit(0);
}
finally
{
    Marshal.ReleaseComObject(tempStream);
}

And that is all she wrote. As always it’s best to backup your resources before you start experimenting with this code! You can download the source code accompanying this article from the download page. If you have any questions or suggestions please drop me an e-mail or submit a comment.

Top of page

About these ads

14 Responses to “WPD: Transfer Content To A Device”

  1. Magnus S Says:

    Many thanks for your WPD posts! Has been very useful for my understanding of how to use it from VB.NET.

  2. Pulkit Jain Says:

    Good articles n but the project attached is throwing error at line

    targetStream.Write(buffer, (int)optimalTransferSizeBytes, pcbWritten);
    Error : “Value does not fall within the expected range.”

    Help me out for removing the error.

    Pulkit

    • Christophe Says:

      What are you trying to do? What kind of device do you use and what kind of files are you trying to transfer to it?

      • Pulkit Jain Says:

        I am using nokia-c2 and I want to transfer a xml file from PC to my phone.

        I am able to perform transfer using WPDApiSample of windows sdk 7 but it is in C++.

        I have change the WPD_OBJECT_CONTENT_TYPE, WPD_OBJECT_FORMAT guid’s correctly but now i am getting COMexception unhandled error “The data area passed to a system call is too small. (Exception from HRESULT: 0x8007007A)”

        So do you have any idea what is happening?

        PS:Pervious error is due to as WPD_OBJECT_CONTENT_TYPE, WPD_OBJECT_FORMAT is not defined in your function.

  3. Semyon Says:

    I am tried to copy a text file to Acer A100 device and get the same error at the same line:
    targetStream.Write(buffer, (int)optimalTransferSizeBytes, pcbWritten);
    Error : “Value does not fall within the expected range.”
    I did try to use another public folder on the device – the same error.

    Do you have any idea what can be wrong?
    Thank you,
    Semyon

  4. Ian Edwards Says:

    Do you have a sample on how to Create a Folder on a Windows Portable device? I’m using the library to delete content and to transfer content to and from the device – but creating a folder would be a winner.

  5. Rotem Says:

    hello
    excellent work on the wpd projects, you have helped me alot.

    i am working on galaxy s2 device ,i am using the WPDTransferringContent project and i combine the method for uploading file to a device, while looping in inside the do while loop
    it seems like its working fine but in the end when the ‘bytesRead’ is not equals to the ‘optimalTransferSizeBytes’ i get an exption:

    The data area passed to a system call is too small. (Exception from HRESULT: 0x8007007A)

    can u help me please?!

    ps
    i found a beautiful class that enumeration all GUID send me an e-mail back and i will give it to u.

  6. the_Drood Says:

    To solve the error: “The data area passed to a system call is too small.” just change the target.Write-call to the following:

    if (bytesRead < (int)optimalTransferSizeBytes)
    targetStream.Write(buffer, bytesRead, pcbWritten);
    else
    targetStream.Write(buffer, (int)optimalTransferSizeBytes, pcbWritten);

    I had the same problem and this solution worked for me!
    Btw great work on WPD Christophe, helped me a lot. :-)


  7. [...] WPD Apis for transferring image files to a connected WPD supported device. I have been following THIS link. My problem is that everytime i try and transfer a file i keep getting the error: Value does [...]

  8. Jaran Nilsen Says:

    Hi Christophe,

    where do you get the fmtid GUID and pid values for the properties from? I have been searching endlessly and have a hard time finding an overview of these values.

    Any pointers?

    Thanks :)

  9. rich_persistance Says:

    Christophe, I can’t tell you how many hours you’ve saved me. Thank you!

    And, to give back for others, I’ve modified this code to work for a Galaxy Tab 2 10.1. There were two “aha” moments involved:

    1. The parentObjectId parameter in the TransferContentToDevice() method is very device-specific. For the Galaxy Tab 2, the root folder is named “Tablet” and it’s ID is “s10001″, so the call to TransferContentToDevice() looks like:

    kindle.TransferContentToDevice(
    @”c:\temp\test.txt”,
    @”s10001″);

    NOTES:
    - I left the object name as “kindle” even though I’m attaching a different device. Clearly, “kindle” is just the variable name and doesn’t matter in terms of function.
    - I am copying the file c:\temp\test.txt to the device. You will want to substitute a file name appropriate to your environment–any file that exists on your PC, basically.

    2. I also saw the error described by Rotem, above, and I adopted the_Drood’s solution:

    // credit to the_Drood for the following conditional write
    if (bytesRead < (int)optimalTransferSizeBytes) {
    targetStream.Write(buffer, bytesRead, pcbWritten);
    } else {
    targetStream.Write(buffer, (int)optimalTransferSizeBytes, pcbWritten);
    }


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 317 other followers

%d bloggers like this: