diff options
Diffstat (limited to 'camlibs/ptp2/library.c')
-rw-r--r-- | camlibs/ptp2/library.c | 3779 |
1 files changed, 3779 insertions, 0 deletions
diff --git a/camlibs/ptp2/library.c b/camlibs/ptp2/library.c new file mode 100644 index 000000000..d23275873 --- /dev/null +++ b/camlibs/ptp2/library.c @@ -0,0 +1,3779 @@ +/* library.c + * + * Copyright (C) 2001-2005 Mariusz Woloszyn <emsi@ipartners.pl> + * Copyright (C) 2003-2006 Marcus Meissner <marcus@jet.franken.de> + * Copyright (C) 2005 Hubert Figuiere <hfiguiere@teaser.fr> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the + * Free Software Foundation, Inc., 59 Temple Place - Suite 330, + * Boston, MA 02111-1307, USA. + */ +#include "config.h" + +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#include <time.h> +#include <langinfo.h> + +#include <gphoto2/gphoto2-library.h> +#include <gphoto2/gphoto2-port-log.h> +#include <gphoto2/gphoto2-setting.h> + +#ifdef ENABLE_NLS +# include <libintl.h> +# undef _ +# define _(String) dgettext (GETTEXT_PACKAGE, String) +# ifdef gettext_noop +# define N_(String) gettext_noop (String) +# else +# define N_(String) (String) +# endif +#else +# define textdomain(String) (String) +# define gettext(String) (String) +# define dgettext(Domain,Message) (Message) +# define dcgettext(Domain,Message,Type) (Message) +# define bindtextdomain(Domain,Directory) (Domain) +# define _(String) (String) +# define N_(String) (String) +#endif + +/* + * On MacOS (Darwin) and *BSD we're not using glibc, but libiconv. + * glibc knows that UCS-2 is to be in the local machine endianness, + * whereas libiconv does not. So we construct this macro to get + * things right. Reportedly, glibc 2.1.3 has a bug so that UCS-2 + * is always bigendian though, we would need to work around that + * too... + */ +#ifndef __GLIBC__ +#define UCS_2_INTERNAL "UCS-2-INTERNAL" +#else +#if (__GLIBC__ == 2 && __GLIBC_MINOR__ <= 1 ) +#error "Too old glibc. This versions iconv() implementation cannot be trusted." +#endif +#define UCS_2_INTERNAL "UCS-2" +#endif + +#include "ptp.h" +#include "ptp-bugs.h" +#include "ptp-private.h" + +#define GP_MODULE "PTP2" + +#define USB_START_TIMEOUT 8000 +#define USB_NORMAL_TIMEOUT 20000 +#define USB_TIMEOUT_CAPTURE 20000 + +#define CPR(context,result) {short r=(result); if (r!=PTP_RC_OK) {report_result ((context), r, params->deviceinfo.VendorExtensionID); return (translate_ptp_result (r));}} + +#define CPR_free(context,result, freeptr) {\ + short r=(result);\ + if (r!=PTP_RC_OK) {\ + report_result ((context), r, params->deviceinfo.VendorExtensionID);\ + free(freeptr);\ + return (translate_ptp_result (r));\ + }\ +} + +#define CR(result) {int r=(result);if(r<0) return (r);} +/* +#define CR_free(result, freeptr) {\ + int r=(result);\ + if(r<0){\ + free(freeptr);\ + return(r);\ + }\ +} +*/ +/* below macro makes a copy of fn without leading character ('/'), + * removes the '/' at the end if present, and calls folder_to_handle() + * funtion proviging as the first argument the string after the second '/'. + * for example if fn is '/store_00010001/DCIM/somefolder/', the macro will + * call folder_to_handle() with 'DCIM/somefolder' as the very first argument. + * it's used to omit storage pseudofolder and remove trailing '/' + */ + +#define find_folder_handle(fn,s,p,d) { \ + { \ + int len=strlen(fn); \ + char *backfolder=malloc(len); \ + char *tmpfolder; \ + memcpy(backfolder,fn+1, len); \ + if (backfolder[len-2]=='/') backfolder[len-2]='\0';\ + if ((tmpfolder=strchr(backfolder+1,'/'))==NULL) tmpfolder="/";\ + p=folder_to_handle(tmpfolder+1,s,0,(Camera *)d);\ + free(backfolder); \ + } \ +} + +#define folder_to_storage(fn,s) { \ + if (!strncmp(fn,"/"STORAGE_FOLDER_PREFIX,strlen(STORAGE_FOLDER_PREFIX)+1)) \ + { \ + if (strlen(fn)<strlen(STORAGE_FOLDER_PREFIX)+8+1) \ + return (GP_ERROR); \ + s = strtoul(fn + strlen(STORAGE_FOLDER_PREFIX)+1, NULL, 16); \ + } else { \ + gp_context_error (context, _("You need to specify a folder starting with /store_xxxxxxxxx/")); \ + return (GP_ERROR); \ + } \ +} + +typedef int (*getfunc_t)(CameraFilesystem*, const char*, const char *, CameraFileType, CameraFile *, void *, GPContext *); +typedef int (*putfunc_t)(CameraFilesystem*, const char*, CameraFile*, void*, GPContext*); + +struct special_file { + char *name; + getfunc_t getfunc; + putfunc_t putfunc; +}; + +static int nrofspecial_files = 0; +static struct special_file *special_files = NULL; + +static int +add_special_file (char *name, getfunc_t getfunc, putfunc_t putfunc) { + if (nrofspecial_files) + special_files = realloc (special_files, sizeof(special_files[0])*(nrofspecial_files+1)); + else + special_files = malloc (sizeof(special_files[0])); + + special_files[nrofspecial_files].name = strdup(name); + special_files[nrofspecial_files].putfunc = putfunc; + special_files[nrofspecial_files].getfunc = getfunc; + nrofspecial_files++; + return (GP_OK); +} + +#define STORAGE_FOLDER_PREFIX "store_" + +/* PTP error descriptions */ +static struct { + short n; + short v; + const char *txt; +} ptp_errors[] = { + {PTP_RC_Undefined, 0, N_("PTP Undefined Error")}, + {PTP_RC_OK, 0, N_("PTP OK!")}, + {PTP_RC_GeneralError, 0, N_("PTP General Error")}, + {PTP_RC_SessionNotOpen, 0, N_("PTP Session Not Open")}, + {PTP_RC_InvalidTransactionID, 0, N_("PTP Invalid Transaction ID")}, + {PTP_RC_OperationNotSupported, 0, N_("PTP Operation Not Supported")}, + {PTP_RC_ParameterNotSupported, 0, N_("PTP Parameter Not Supported")}, + {PTP_RC_IncompleteTransfer, 0, N_("PTP Incomplete Transfer")}, + {PTP_RC_InvalidStorageId, 0, N_("PTP Invalid Storage ID")}, + {PTP_RC_InvalidObjectHandle, 0, N_("PTP Invalid Object Handle")}, + {PTP_RC_DevicePropNotSupported, 0, N_("PTP Device Prop Not Supported")}, + {PTP_RC_InvalidObjectFormatCode, 0, N_("PTP Invalid Object Format Code")}, + {PTP_RC_StoreFull, 0, N_("PTP Store Full")}, + {PTP_RC_ObjectWriteProtected, 0, N_("PTP Object Write Protected")}, + {PTP_RC_StoreReadOnly, 0, N_("PTP Store Read Only")}, + {PTP_RC_AccessDenied, 0, N_("PTP Access Denied")}, + {PTP_RC_NoThumbnailPresent, 0, N_("PTP No Thumbnail Present")}, + {PTP_RC_SelfTestFailed, 0, N_("PTP Self Test Failed")}, + {PTP_RC_PartialDeletion, 0, N_("PTP Partial Deletion")}, + {PTP_RC_StoreNotAvailable, 0, N_("PTP Store Not Available")}, + {PTP_RC_SpecificationByFormatUnsupported, + 0, N_("PTP Specification By Format Unsupported")}, + {PTP_RC_NoValidObjectInfo, 0, N_("PTP No Valid Object Info")}, + {PTP_RC_InvalidCodeFormat, 0, N_("PTP Invalid Code Format")}, + {PTP_RC_UnknownVendorCode, 0, N_("PTP Unknown Vendor Code")}, + {PTP_RC_CaptureAlreadyTerminated, + 0, N_("PTP Capture Already Terminated")}, + {PTP_RC_DeviceBusy, 0, N_("PTP Device Busy")}, + {PTP_RC_InvalidParentObject, 0, N_("PTP Invalid Parent Object")}, + {PTP_RC_InvalidDevicePropFormat, 0, N_("PTP Invalid Device Prop Format")}, + {PTP_RC_InvalidDevicePropValue, 0, N_("PTP Invalid Device Prop Value")}, + {PTP_RC_InvalidParameter, 0, N_("PTP Invalid Parameter")}, + {PTP_RC_SessionAlreadyOpened, 0, N_("PTP Session Already Opened")}, + {PTP_RC_TransactionCanceled, 0, N_("PTP Transaction Canceled")}, + {PTP_RC_SpecificationOfDestinationUnsupported, + 0, N_("PTP Specification Of Destination Unsupported")}, + {PTP_RC_EK_FilenameRequired, PTP_VENDOR_EASTMAN_KODAK, N_("PTP EK Filename Required")}, + {PTP_RC_EK_FilenameConflicts, PTP_VENDOR_EASTMAN_KODAK, N_("PTP EK Filename Conflicts")}, + {PTP_RC_EK_FilenameInvalid, PTP_VENDOR_EASTMAN_KODAK, N_("PTP EK Filename Invalid")}, + + {PTP_ERROR_IO, 0, N_("PTP I/O error")}, + {PTP_ERROR_BADPARAM, 0, N_("PTP Error: bad parameter")}, + {PTP_ERROR_DATA_EXPECTED, 0, N_("PTP Protocol error, data expected")}, + {PTP_ERROR_RESP_EXPECTED, 0, N_("PTP Protocol error, response expected")}, + {0, 0, NULL} +}; + +void +report_result (GPContext *context, short result, short vendor) +{ + unsigned int i; + + for (i = 0; ptp_errors[i].txt; i++) + if ((ptp_errors[i].n == result) && ( + (ptp_errors[i].v == 0) || (ptp_errors[i].v == vendor) + )) + gp_context_error (context, "%s", dgettext(GETTEXT_PACKAGE, ptp_errors[i].txt)); +} + +int +translate_ptp_result (short result) +{ + switch (result) { + case PTP_RC_ParameterNotSupported: + return (GP_ERROR_BAD_PARAMETERS); + case PTP_ERROR_BADPARAM: + return (GP_ERROR_BAD_PARAMETERS); + case PTP_RC_OK: + return (GP_OK); + default: + return (GP_ERROR); + } +} + +static short +translate_gp_result (int result) +{ + switch (result) { + case GP_OK: + return (PTP_RC_OK); + case GP_ERROR: + default: + GP_DEBUG ("PTP: gp_port_* function returned 0x%04x \t %i",result,result); + return (PTP_RC_GeneralError); + } +} + +static struct { + const char *model; + unsigned short usb_vendor; + unsigned short usb_product; + unsigned long known_bugs; +} models[] = { + /* + * The very first PTP camera (with special firmware only), also + * called "PTP Prototype", may report non PTP interface class + */ + {"Kodak:DC240 (PTP mode)", 0x040a, 0x0121, 0}, + /* + * Old DC-4800 firmware reported custom interface class, so we have + * to detect it by product/vendor IDs + */ + {"Kodak:DC4800", 0x040a, 0x0160, 0}, + /* Below other camers known to be detected by interface class */ + + {"Kodak:DX3900", 0x040a, 0x0170, 0}, + {"Kodak:MC3", 0x040a, 0x0400, 0}, + /* reported by Ken Moffat */ + {"Kodak:Z7590", 0x040a, 0x0403, 0}, + {"Kodak:DX3500", 0x040a, 0x0500, 0}, + {"Kodak:DX3600", 0x040a, 0x0510, 0}, + {"Kodak:DX3215", 0x040a, 0x0525, 0}, + {"Kodak:DX3700", 0x040a, 0x0530, 0}, + {"Kodak:CX4230", 0x040a, 0x0535, 0}, + {"Kodak:LS420", 0x040a, 0x0540, 0}, + {"Kodak:DX4900", 0x040a, 0x0550, 0}, + {"Kodak:DX4330", 0x040a, 0x0555, 0}, + {"Kodak:CX4200", 0x040a, 0x0560, 0}, + {"Kodak:CX4210", 0x040a, 0x0560, 0}, + {"Kodak:LS743", 0x040a, 0x0565, 0}, + /* both above with different product IDs + normal/retail versions of the same model */ + {"Kodak:CX4300", 0x040a, 0x0566, 0}, + {"Kodak:CX4310", 0x040a, 0x0566, 0}, + {"Kodak:LS753", 0x040a, 0x0567, 0}, + {"Kodak:LS443", 0x040a, 0x0568, 0}, + {"Kodak:LS663", 0x040a, 0x0569, 0}, + {"Kodak:DX6340", 0x040a, 0x0570, 0}, + {"Kodak:CX6330", 0x040a, 0x0571, 0}, + {"Kodak:DX6440", 0x040a, 0x0572, 0}, + {"Kodak:CX6230", 0x040a, 0x0573, 0}, + {"Kodak:CX6200", 0x040a, 0x0574, 0}, + {"Kodak:DX6490", 0x040a, 0x0575, 0}, + {"Kodak:DX4530", 0x040a, 0x0576, 0}, + {"Kodak:DX7630", 0x040a, 0x0577, 0}, + {"Kodak:CX7300", 0x040a, 0x0578, 0}, + {"Kodak:CX7310", 0x040a, 0x0578, 0}, + {"Kodak:CX7220", 0x040a, 0x0579, 0}, + {"Kodak:CX7330", 0x040a, 0x057a, 0}, + {"Kodak:CX7430", 0x040a, 0x057b, 0}, + {"Kodak:CX7530", 0x040a, 0x057c, 0}, + {"Kodak:DX7440", 0x040a, 0x057d, 0}, + /* c300 Pau Rodriguez-Estivill <prodrigestivill@yahoo.es> */ + {"Kodak:C300", 0x040a, 0x057e, 0}, + {"Kodak:DX7590", 0x040a, 0x057f, 0}, + {"Kodak:Z730", 0x040a, 0x0580, 0}, + {"Kodak:CX6445", 0x040a, 0x0584, 0}, + {"Kodak:CX7525", 0x040a, 0x0586, 0}, + /* a sf bugreporter */ + {"Kodak:Z700", 0x040a, 0x0587, 0}, + /* EasyShare Z740, Benjamin Mesing <bensmail@gmx.net> */ + {"Kodak:Z740", 0x040a, 0x0588, 0}, + /* EasyShare C360, Guilherme de S. Pastore via Debian */ + {"Kodak:C360", 0x040a, 0x0589, 0}, + /* Giulio Salani <ilfunambolo@gmail.com> */ + {"Kodak:C310", 0x040a, 0x058a, 0}, + /* Brandon Sharitt */ + {"Kodak:C330", 0x040a, 0x058c, PTPBUG_DCIM_WRONG_PARENT}, + /* c340 Maurizio Daniele <hayabusa@portalis.it> */ + {"Kodak:C340", 0x040a, 0x058d, 0}, + {"Kodak:V530", 0x040a, 0x058e, 0}, + /* v550 Jon Burgess <jburgess@uklinux.net> */ + {"Kodak:V550", 0x040a, 0x058f, 0}, + {"Kodak:P850", 0x040a, 0x0592, 0}, + /* https://launchpad.net/distros/ubuntu/+source/libgphoto2/+bug/67532 */ + {"Kodak:C530", 0x040a, 0x059a, 0}, + /* Ivan Baldo, http://bugs.debian.org/cgi-bin/bugreport.cgi?bug=387998 */ + {"Kodak:CD33", 0x040a, 0x059c, 0}, + /* https://sourceforge.net/tracker/?func=detail&atid=208874&aid=1565144&group_id=8874 */ + {"Kodak:Z612", 0x040a, 0x059d, 0}, + /* David D. Huff Jr. <David.Huff@computer-critters.com> */ + {"Kodak:Z650", 0x040a, 0x059e, 0}, + /* http://sourceforge.net/tracker/index.php?func=detail&aid=1547142&group_id=8874&atid=358874 */ + {"Kodak:C533", 0x040a, 0x05a2, 0}, + /* Marc Santhoff <M.Santhoff@t-online.de> */ + {"Kodak:C643", 0x040a, 0x05a7, 0}, + + /* HP PTP cameras */ + {"HP:PhotoSmart 812 (PTP mode)", 0x03f0, 0x4202, 0}, + {"HP:PhotoSmart 850 (PTP mode)", 0x03f0, 0x4302, PTPBUG_DUPE_FILE}, + /* HP PhotoSmart 935: T. Kaproncai, 25 Jul 2003*/ + {"HP:PhotoSmart 935 (PTP mode)", 0x03f0, 0x4402, 0}, + /* HP:PhotoSmart 945: T. Jelbert, 2004/03/29 */ + {"HP:PhotoSmart 945 (PTP mode)", 0x03f0, 0x4502, 0}, + {"HP:PhotoSmart 318 (PTP mode)", 0x03f0, 0x6302, 0}, + {"HP:PhotoSmart 612 (PTP mode)", 0x03f0, 0x6302, 0}, + {"HP:PhotoSmart 715 (PTP mode)", 0x03f0, 0x6402, 0}, + {"HP:PhotoSmart 120 (PTP mode)", 0x03f0, 0x6502, 0}, + {"HP:PhotoSmart 320 (PTP mode)", 0x03f0, 0x6602, 0}, + {"HP:PhotoSmart 720 (PTP mode)", 0x03f0, 0x6702, 0}, + {"HP:PhotoSmart 620 (PTP mode)", 0x03f0, 0x6802, 0}, + {"HP:PhotoSmart 735 (PTP mode)", 0x03f0, 0x6a02, 0}, + {"HP:PhotoSmart 707 (PTP mode)", 0x03f0, 0x6b02, 0}, + {"HP:PhotoSmart 733 (PTP mode)", 0x03f0, 0x6c02, 0}, + {"HP:PhotoSmart 607 (PTP mode)", 0x03f0, 0x6d02, 0}, + {"HP:PhotoSmart 507 (PTP mode)", 0x03f0, 0x6e02, 0}, + {"HP:PhotoSmart 635 (PTP mode)", 0x03f0, 0x7102, 0}, + /* report from Federico Prat Villar <fprat@lsi.uji.es> */ + {"HP:PhotoSmart 43x (PTP mode)", 0x03f0, 0x7202, 0}, + {"HP:PhotoSmart M307 (PTP mode)", 0x03f0, 0x7302, 0}, + {"HP:PhotoSmart 407 (PTP mode)", 0x03f0, 0x7402, 0}, + {"HP:PhotoSmart M22 (PTP mode)", 0x03f0, 0x7502, 0}, + {"HP:PhotoSmart 717 (PTP mode)", 0x03f0, 0x7602, 0}, + {"HP:PhotoSmart 817 (PTP mode)", 0x03f0, 0x7702, 0}, + {"HP:PhotoSmart 417 (PTP mode)", 0x03f0, 0x7802, 0}, + {"HP:PhotoSmart 517 (PTP mode)", 0x03f0, 0x7902, 0}, + /* http://sourceforge.net/tracker/index.php?func=detail&aid=1365941&group_id=8874&atid=108874 */ + {"HP:PhotoSmart M415 (PTP mode)", 0x03f0, 0x7a02, 0}, + /* irc contact, YGingras */ + {"HP:PhotoSmart M23 (PTP mode)", 0x03f0, 0x7b02, 0}, + {"HP:PhotoSmart 217 (PTP mode)", 0x03f0, 0x7c02, 0}, + /* irc contact */ + {"HP:PhotoSmart 317 (PTP mode)", 0x03f0, 0x7d02, 0}, + {"HP:PhotoSmart 818 (PTP mode)", 0x03f0, 0x7e02, 0}, + /* Robin <diilbert.atlantis@gmail.com> */ + {"HP:PhotoSmart M425 (PTP mode)", 0x03f0, 0x8002, 0}, + {"HP:PhotoSmart M525 (PTP mode)", 0x03f0, 0x8102, 0}, + {"HP:PhotoSmart M527 (PTP mode)", 0x03f0, 0x8202, 0}, + {"HP:PhotoSmart M725 (PTP mode)", 0x03f0, 0x8402, 0}, + {"HP:PhotoSmart M727 (PTP mode)", 0x03f0, 0x8502, 0}, + /* http://sourceforge.net/tracker/index.php?func=detail&aid=1584447&group_id=8874&atid=358874 */ + {"HP:PhotoSmart R927 (PTP mode)", 0x03f0, 0x8702, 0}, + {"HP:PhotoSmart E327 (PTP mode)", 0x03f0, 0x8b02, 0}, + + /* Most Sony PTP cameras use the same product/vendor IDs. */ + {"Sony:PTP", 0x054c, 0x004e, 0}, + {"Sony:DSC-H1 (PTP mode)", 0x054c, 0x004e, 0}, + {"Sony:DSC-H2 (PTP mode)", 0x054c, 0x004e, 0}, + {"Sony:DSC-H5 (PTP mode)", 0x054c, 0x004e, 0}, + {"Sony:DSC-P5 (PTP mode)", 0x054c, 0x004e, 0}, + {"Sony:DSC-P10 (PTP mode)", 0x054c, 0x004e, 0}, + {"Sony:DSC-F707V (PTP mode)", 0x054c, 0x004e, 0}, + {"Sony:DSC-F717 (PTP mode)", 0x054c, 0x004e, 0}, + {"Sony:DSC-F828 (PTP mode)", 0x054c, 0x004e, 0}, + {"Sony:DSC-P30 (PTP mode)", 0x054c, 0x004e, 0}, + /* P32 reported on May 1st by Justin Alexander <justin (at) harshangel.com> */ + {"Sony:DSC-P31 (PTP mode)", 0x054c, 0x004e, 0}, + {"Sony:DSC-P32 (PTP mode)", 0x054c, 0x004e, 0}, + {"Sony:DSC-P41 (PTP mode)", 0x054c, 0x004e, 0}, + {"Sony:DSC-P43 (PTP mode)", 0x054c, 0x004e, 0}, + {"Sony:DSC-P50 (PTP mode)", 0x054c, 0x004e, 0}, + {"Sony:DSC-P51 (PTP mode)", 0x054c, 0x004e, 0}, + {"Sony:DSC-P52 (PTP mode)", 0x054c, 0x004e, 0}, + {"Sony:DSC-P71 (PTP mode)", 0x054c, 0x004e, 0}, + {"Sony:DSC-P72 (PTP mode)", 0x054c, 0x004e, 0}, + {"Sony:DSC-P73 (PTP mode)", 0x054c, 0x004e, 0}, + {"Sony:DSC-P92 (PTP mode)", 0x054c, 0x004e, 0}, + {"Sony:DSC-P93 (PTP mode)", 0x054c, 0x004e, 0}, + {"Sony:DSC-P100 (PTP mode)", 0x054c, 0x004e, 0}, + {"Sony:DSC-P120 (PTP mode)", 0x054c, 0x004e, 0}, + {"Sony:DSC-P200 (PTP mode)", 0x054c, 0x004e, 0}, + {"Sony:DSC-R1 (PTP mode)", 0x054c, 0x004e, 0}, + {"Sony:DSC-S40 (PTP mode)", 0x054c, 0x004e, 0}, + {"Sony:DSC-S60 (PTP mode)", 0x054c, 0x004e, 0}, + {"Sony:DSC-S75 (PTP mode)", 0x054c, 0x004e, 0}, + {"Sony:DSC-S85 (PTP mode)", 0x054c, 0x004e, 0}, + {"Sony:DSC-T1 (PTP mode)", 0x054c, 0x004e, 0}, + {"Sony:DSC-T3 (PTP mode)", 0x054c, 0x004e, 0}, + {"Sony:DSC-T10 (PTP mode)", 0x054c, 0x004e, 0}, + {"Sony:DSC-U20 (PTP mode)", 0x054c, 0x004e, 0}, + {"Sony:DSC-V1 (PTP mode)", 0x054c, 0x004e, 0}, + {"Sony:DSC-W1 (PTP mode)", 0x054c, 0x004e, 0}, + {"Sony:DSC-W12 (PTP mode)", 0x054c, 0x004e, 0}, + {"Sony:MVC-CD300 (PTP mode)", 0x054c, 0x004e, 0}, + {"Sony:MVC-CD500 (PTP mode)", 0x054c, 0x004e, 0}, + {"Sony:DSC-U10 (PTP mode)", 0x054c, 0x004e, 0}, + + /* Nikon Coolpix 2500: M. Meissner, 05 Oct 2003 */ + {"Nikon:Coolpix 2500 (PTP mode)", 0x04b0, 0x0109, 0}, + /* Nikon Coolpix 5700: A. Tanenbaum, 29 Oct 2002 */ + {"Nikon:Coolpix 5700 (PTP mode)", 0x04b0, 0x010d, PTP_CAP}, + /* Nikon Coolpix 4500: T. Kaproncai, 22 Aug 2003 */ + {"Nikon Coolpix 4500 (PTP mode)", 0x04b0, 0x010b, 0}, + /* Nikon Coolpix 4300: Marco Rodriguez, 10 dic 2002 */ + {"Nikon:Coolpix 4300 (PTP mode)", 0x04b0, 0x010f, 0}, + /* Nikon Coolpix 3500: M. Meissner, 07 May 2003 */ + {"Nikon:Coolpix 3500 (PTP mode)", 0x04b0, 0x0111, 0}, + /* Nikon Coolpix 885: S. Anderson, 19 nov 2002 */ + {"Nikon:Coolpix 885 (PTP mode)", 0x04b0, 0x0112, 0}, + /* Nikon Coolpix 5000, Firmware v1.7 or later */ + {"Nikon:Coolpix 5000 (PTP mode)", 0x04b0, 0x0113, 0}, + /* Nikon Coolpix 3100 */ + {"Nikon:Coolpix 3100 (PTP mode)", 0x04b0, 0x0115, 0}, + /* Nikon Coolpix 2100 */ + {"Nikon:Coolpix 2100 (PTP mode)", 0x04b0, 0x0117, 0}, + /* Nikon Coolpix 5400: T. Kaproncai, 25 Jul 2003 */ + {"Nikon:Coolpix 5400 (PTP mode)", 0x04b0, 0x0119, 0}, + /* Nikon Coolpix 3700: T. Ehlers, 18 Jan 2004 */ + {"Nikon:Coolpix 3700 (PTP mode)", 0x04b0, 0x011d, 0}, + /* Nikon Coolpix 3200 */ + {"Nikon:Coolpix 3200 (PTP mode)", 0x04b0, 0x0121, 0}, + /* Nikon Coolpix 2200 */ + {"Nikon:Coolpix 2200 (PTP mode)", 0x04b0, 0x0122, 0}, + /* Nikon Coolpix 4800 */ + {"Nikon:Coolpix 4800 (PTP mode)", 0x04b0, 0x0129, 0}, + /* Nikon Coolpix SQ: M. Holzbauer, 07 Jul 2003 */ + {"Nikon:Coolpix 4100 (PTP mode)", 0x04b0, 0x012d, 0}, + /* Nikon Coolpix 5600: Andy Shevchenko, 11 Aug 2005 */ + {"Nikon:Coolpix 5600 (PTP mode)", 0x04b0, 0x012e, PTP_CAP}, + /* 4600: Martin Klaffenboeck <martin.klaffenboeck@gmx.at> */ + {"Nikon:Coolpix 4600 (PTP mode)", 0x04b0, 0x0130, 0}, + /* 4600: Roberto Costa <roberto.costa@ensta.org>, 22 Oct 2006 */ + {"Nikon:Coolpix 4600a (PTP mode)", 0x04b0, 0x0131, 0}, + {"Nikon:Coolpix 5900 (PTP mode)", 0x04b0, 0x0135, PTP_CAP}, + {"Nikon:Coolpix P1 (PTP mode)", 0x04b0, 0x0140, PTP_CAP}, + /* Marcus Meissner */ + {"Nikon:Coolpix P2 (PTP mode)", 0x04b0, 0x0142, PTP_CAP}, + /* Lowe, John Michael <jomlowe@iupui.edu> */ + {"Nikon:Coolpix S2 (PTP mode)", 0x04b0, 0x014e, 0}, + {"Nikon:Coolpix SQ (PTP mode)", 0x04b0, 0x0202, 0}, + /* lars marowski bree, 16.8.2004 */ + {"Nikon:Coolpix 4200 (PTP mode)", 0x04b0, 0x0204, 0}, + /* Nikon Coolpix 5200: Andy Shevchenko, 18 Jul 2005 */ + {"Nikon:Coolpix 5200 (PTP mode)", 0x04b0, 0x0206, 0}, + /* https://launchpad.net/bugs/63473 */ + {"Nikon:Coolpix L1 (PTP mode)", 0x04b0, 0x0208, 0}, + /* Nikon Coolpix 2000 */ + {"Nikon:Coolpix 2000 (PTP mode)", 0x04b0, 0x0302, 0}, + + /* From IRC reporter. */ + {"Nikon:Coolpix L4 (PTP mode)", 0x04b0, 0x0305, 0}, + + /* Nikon D100 has a PTP mode: westin 2002.10.16 */ + {"Nikon:DSC D100 (PTP mode)", 0x04b0, 0x0402, 0}, + /* D2H SLR in PTP mode from Steve Drew <stevedrew@gmail.com> */ + {"Nikon:D2H SLR (PTP mode)", 0x04b0, 0x0404, 0}, + {"Nikon:DSC D70 (PTP mode)", 0x04b0, 0x0406, PTP_CAP}, + + /* Justin Case <justin_case@gmx.net> */ + {"Nikon:D2X SLR (PTP mode)", 0x04b0, 0x0408, PTP_CAP}, + + /* Niclas Gustafsson (nulleman @ sf) */ + {"Nikon:D50 (PTP mode)", 0x04b0, 0x040a, PTP_CAP}, + + {"Nikon:DSC D70s (PTP mode)", 0x04b0, 0x040e, PTP_CAP}, + /* Jana Jaeger <jjaeger.suse.de> */ + {"Nikon:DSC D200 (PTP mode)", 0x04b0, 0x0410, PTP_CAP}, + /* Christian Deckelmann @ SUSE */ + {"Nikon:DSC D80 (PTP mode)", 0x04b0, 0x0412, PTP_CAP}, + + /* Sridharan Rengaswamy <sridhar@stsci.edu>, Coolpix L3 */ + {"Nikon:Coolpix L3 (PTP mode)", 0x04b0, 0x041a, 0}, + + /* Thomas Luzat <thomas.luzat@gmx.net> */ + {"Panasonic:DMC-FZ20 (alternate id)", 0x04da, 0x2372, 0}, + /* http://sourceforge.net/tracker/index.php?func=detail&aid=1350226&group_id=8874&atid=208874 */ + {"Panasonic:DMC-LZ2", 0x04da, 0x2372, 0}, + /* https://sourceforge.net/tracker/index.php?func=detail&aid=1405541&group_id=8874&atid=358874 */ + {"Panasonic:DMC-LC1", 0x04da, 0x2372, 0}, + + /* Søren Krarup Olesen <sko@acoustics.aau.dk> */ + {"Leica:D-LUX 2", 0x04da, 0x2375, 0}, + + /* http://sourceforge.net/tracker/index.php?func=detail&aid=1275100&group_id=8874&atid=358874 */ + {"Panasonic:Lumix FZ5", 0x04da, 0x2372, 0}, + + {"Panasonic:DMC-FZ20", 0x04da, 0x2374, 0}, + /* from Tomas Herrdin <tomas.herrdin@swipnet.se> */ + {"Panasonic:DMC-LS3", 0x04da, 0x2374, 0}, + + /* http://callendor.zongo.be/wiki/OlympusMju500 */ + {"Olympus:mju 500", 0x07b4, 0x0113, 0}, + /* From VICTOR <viaaurea@yahoo.es> */ + {"Olympus:C-350Z", 0x07b4, 0x0114, 0}, + {"Olympus:D-560Z", 0x07b4, 0x0114, 0}, + {"Olympus:X-250", 0x07b4, 0x0114, 0}, + /* http://sourceforge.net/tracker/index.php?func=detail&aid=1349900&group_id=8874&atid=108874 */ + {"Olympus:C-310Z", 0x07b4, 0x0114, 0}, + {"Olympus:D-540Z", 0x07b4, 0x0114, 0}, + {"Olympus:X-100", 0x07b4, 0x0114, 0}, + /* https://sourceforge.net/tracker/index.php?func=detail&aid=1442115&group_id=8874&atid=358874 */ + {"Olympus:C-55Z", 0x07b4, 0x0114, 0}, + {"Olympus:C-5500Z", 0x07b4, 0x0114, 0}, + + /* https://sourceforge.net/tracker/?func=detail&atid=358874&aid=1272944&group_id=8874 */ + {"Olympus:IR-300", 0x07b4, 0x0114, 0}, + + /* IRC report */ + {"Casio:EX-Z120", 0x07cf, 0x1042, 0}, + + /* (at least some) newer Canon cameras can be switched between + * PTP and "normal" (i.e. Canon) mode + * Canon PS G3: A. Marinichev, 20 nov 2002 + */ + {"Canon:PowerShot S45 (PTP mode)", 0x04a9, 0x306d, PTPBUG_DELETE_SENDS_EVENT}, + /* 0x306c is S45 in normal (canon) mode */ + {"Canon:PowerShot G3 (PTP mode)", 0x04a9, 0x306f, PTPBUG_DELETE_SENDS_EVENT}, + /* 0x306e is G3 in normal (canon) mode */ + {"Canon:PowerShot S230 (PTP mode)", 0x04a9, 0x3071, PTPBUG_DELETE_SENDS_EVENT}, + /* 0x3070 is S230 in normal (canon) mode */ + {"Canon:Digital IXUS v3 (PTP mode)", 0x04a9, 0x3071, PTPBUG_DELETE_SENDS_EVENT}, + /* it's the same as S230 */ + + {"Canon:Digital IXUS II (PTP mode)", 0x04a9, 0x3072, PTPBUG_DELETE_SENDS_EVENT|PTP_CAP|PTP_CAP_PREVIEW}, + {"Canon:PowerShot SD100 (PTP mode)", 0x04a9, 0x3072, PTPBUG_DELETE_SENDS_EVENT|PTP_CAP|PTP_CAP_PREVIEW}, + + {"Canon:PowerShot A70 (PTP)", 0x04a9, 0x3073, PTPBUG_DELETE_SENDS_EVENT|PTP_CAP|PTP_CAP_PREVIEW}, + {"Canon:PowerShot A60 (PTP)", 0x04a9, 0x3074, PTPBUG_DELETE_SENDS_EVENT|PTP_CAP|PTP_CAP_PREVIEW}, + /* IXUS 400 has the same PID in both modes, Till Kamppeter */ + {"Canon:Digital IXUS 400 (PTP mode)", 0x04a9, 0x3075, PTPBUG_DELETE_SENDS_EVENT|PTP_CAP|PTP_CAP_PREVIEW}, + {"Canon:PowerShot A300 (PTP mode)", 0x04a9, 0x3076, PTPBUG_DELETE_SENDS_EVENT|PTP_CAP|PTP_CAP_PREVIEW}, + {"Canon:PowerShot S50 (PTP mode)", 0x04a9, 0x3077, PTPBUG_DELETE_SENDS_EVENT}, + {"Canon:PowerShot G5 (PTP mode)", 0x04a9, 0x3085, PTPBUG_DELETE_SENDS_EVENT}, + {"Canon:Elura 50 (PTP mode)", 0x04a9, 0x3087, PTPBUG_DELETE_SENDS_EVENT}, + /* 0x3084 is the EOS 300D/Digital Rebel in normal (canon) mode */ + {"Canon:EOS 300D (PTP mode)", 0x04a9, 0x3099, PTPBUG_DCIM_WRONG_PARENT}, + {"Canon:EOS Digital Rebel (PTP mode)", 0x04a9, 0x3099, PTPBUG_DCIM_WRONG_PARENT}, + {"Canon:EOS Kiss Digital (PTP mode)", 0x04a9, 0x3099, PTPBUG_DCIM_WRONG_PARENT}, + {"Canon:PowerShot A80 (PTP)", 0x04a9, 0x309a, PTPBUG_DELETE_SENDS_EVENT|PTP_CAP|PTP_CAP_PREVIEW}, + {"Canon:Digital IXUS i (PTP mode)", 0x04a9, 0x309b, PTPBUG_DELETE_SENDS_EVENT}, + {"Canon:PowerShot S1 IS (PTP mode)", 0x04a9, 0x309c, PTPBUG_DELETE_SENDS_EVENT|PTP_CAP|PTP_CAP_PREVIEW}, + {"Canon:Powershot S70 (PTP mode)", 0x04a9, 0x30b1, PTPBUG_DELETE_SENDS_EVENT|PTP_CAP|PTP_CAP_PREVIEW}, + {"Canon:Powershot S60 (PTP mode)", 0x04a9, 0x30b2, PTPBUG_DELETE_SENDS_EVENT}, + {"Canon:Powershot G6 (PTP mode)", 0x04a9, 0x30b3, PTPBUG_DELETE_SENDS_EVENT|PTP_CAP|PTP_CAP_PREVIEW}, + {"Canon:Digital IXUS 500 (PTP mode)", 0x04a9, 0x30b4, PTPBUG_DELETE_SENDS_EVENT|PTP_CAP|PTP_CAP_PREVIEW}, + {"Canon:PowerShot S500 (PTP mode)", 0x04a9, 0x30b4, PTPBUG_DELETE_SENDS_EVENT|PTP_CAP|PTP_CAP_PREVIEW}, + {"Canon:PowerShot A75 (PTP mode)", 0x04a9, 0x30b5, PTPBUG_DELETE_SENDS_EVENT|PTP_CAP|PTP_CAP_PREVIEW}, + {"Canon:PowerShot SD110 (PTP mode)", 0x04a9, 0x30b6, PTPBUG_DELETE_SENDS_EVENT|PTP_CAP|PTP_CAP_PREVIEW}, + {"Canon:Digital IXUS IIs (PTP mode)", 0x04a9, 0x30b6, PTPBUG_DELETE_SENDS_EVENT|PTP_CAP|PTP_CAP_PREVIEW}, + {"Canon:PowerShot A400 (PTP mode)", 0x04a9, 0x30b7, PTPBUG_DELETE_SENDS_EVENT|PTP_CAP|PTP_CAP_PREVIEW}, + {"Canon:PowerShot A310 (PTP mode)", 0x04a9, 0x30b8, PTPBUG_DELETE_SENDS_EVENT}, + {"Canon:PowerShot A85 (PTP mode)", 0x04a9, 0x30b9, PTPBUG_DELETE_SENDS_EVENT|PTP_CAP|PTP_CAP_PREVIEW}, + {"Canon:Digital IXUS 430 (PTP mode)", 0x04a9, 0x30ba, PTPBUG_DELETE_SENDS_EVENT}, + {"Canon:PowerShot S410 (PTP mode)", 0x04a9, 0x30ba, PTPBUG_DELETE_SENDS_EVENT|PTP_CAP|PTP_CAP_PREVIEW}, + {"Canon:PowerShot A95 (PTP mode)", 0x04a9, 0x30bb, PTPBUG_DELETE_SENDS_EVENT|PTP_CAP|PTP_CAP_PREVIEW}, + {"Canon:Digital IXUS 40 (PTP mode)", 0x04a9, 0x30bf, PTPBUG_DELETE_SENDS_EVENT|PTP_CAP|PTP_CAP_PREVIEW}, + {"Canon:PowerShot SD200 (PTP mode)", 0x04a9, 0x30c0, PTPBUG_DELETE_SENDS_EVENT}, + {"Canon:Digital IXUS 30 (PTP mode)", 0x04a9, 0x30c0, PTPBUG_DELETE_SENDS_EVENT}, + {"Canon:PowerShot A520 (PTP mode)", 0x04a9, 0x30c1, PTPBUG_DELETE_SENDS_EVENT|PTP_CAP|PTP_CAP_PREVIEW}, + {"Canon:PowerShot A510 (PTP mode)", 0x04a9, 0x30c2, PTPBUG_DELETE_SENDS_EVENT}, + {"Canon:EOS 1D Mark II (PTP mode)", 0x04a9, 0x30ea, PTPBUG_DCIM_WRONG_PARENT}, + {"Canon:EOS 20D (PTP mode)", 0x04a9, 0x30ec, PTPBUG_DCIM_WRONG_PARENT}, + /* 30ef is the ID in explicit PTP mode. + * 30ee is the ID with the camera in Canon mode, but the camera reacts to + * PTP commands according to: + * https://sourceforge.net/tracker/?func=detail&atid=108874&aid=1394326&group_id=8874 + * They need to have different names. + */ + {"Canon:EOS 350D (PTP mode)", 0x04a9, 0x30ee, PTPBUG_DCIM_WRONG_PARENT}, + {"Canon:EOS 350D", 0x04a9, 0x30ef, PTPBUG_DCIM_WRONG_PARENT}, + {"Canon:PowerShot S2 IS (PTP mode)", 0x04a9, 0x30f0, PTPBUG_DELETE_SENDS_EVENT|PTP_CAP|PTP_CAP_PREVIEW}, + {"Canon:PowerShot SD430 (PTP mode)", 0x04a9, 0x30f1, PTPBUG_DELETE_SENDS_EVENT|PTP_CAP|PTP_CAP_PREVIEW}, + {"Canon:Digital IXUS Wireless (PTP mode)",0x04a9, 0x30f1, PTPBUG_DELETE_SENDS_EVENT|PTP_CAP|PTP_CAP_PREVIEW}, + {"Canon:Digital IXUS 700 (PTP mode)", 0x04a9, 0x30f2, PTPBUG_DELETE_SENDS_EVENT}, + {"Canon:PowerShot SD500 (PTP mode)", 0x04a9, 0x30f2, PTPBUG_DELETE_SENDS_EVENT}, + /* A340, Andreas Stempfhuber <andi@afulinux.de> */ + {"Canon:PowerShot A430 (PTP mode)", 0x04a9, 0x30f8, PTPBUG_DELETE_SENDS_EVENT}, + /* Conan Colx, A410, gphoto-Feature Requests-1342538 */ + {"Canon:PowerShot A410 (PTP mode)", 0x04a9, 0x30f9, PTPBUG_DELETE_SENDS_EVENT}, + /* http://sourceforge.net/tracker/index.php?func=detail&aid=1411976&group_id=8874&atid=358874 */ + {"Canon:PowerShot S80 (PTP mode)", 0x04a9, 0x30fa, PTPBUG_DELETE_SENDS_EVENT|PTP_CAP|PTP_CAP_PREVIEW}, + /* A620, Tom Roelz */ + {"Canon:PowerShot A620 (PTP mode)", 0x04a9, 0x30fc, PTPBUG_DELETE_SENDS_EVENT|PTP_CAP|PTP_CAP_PREVIEW}, + /* A610, Andriy Kulchytskyy <whoops@ukrtop.com> */ + {"Canon:PowerShot A610 (PTP mode)", 0x04a9, 0x30fd, PTPBUG_DELETE_SENDS_EVENT}, + /* Irc Reporter */ + {"Canon:PowerShot SD630 (PTP mode)", 0x04a9, 0x30fe, PTPBUG_DELETE_SENDS_EVENT}, + {"Canon:Digital IXUS 65 (PTP mode)", 0x04a9, 0x30fe, PTPBUG_DELETE_SENDS_EVENT}, + /* Rob Lensen <rob@bsdfreaks.nl> */ + {"Canon:Digital IXUS 55 (PTP mode)", 0x04a9, 0x30ff, PTPBUG_DELETE_SENDS_EVENT}, + {"Canon:PowerShot SD450 (PTP mode)", 0x04a9, 0x30ff, PTPBUG_DELETE_SENDS_EVENT}, + {"Canon:Optura 600 (PTP mode)", 0x04a9, 0x3105, 0}, + /* Jeff Mock <jeff@mock.com> */ + {"Canon:EOS 5D (PTP mode)", 0x04a9, 0x3101, PTPBUG_DCIM_WRONG_PARENT}, + /* Nick Richards <nick@nedrichards.com> */ + {"Canon:Digital IXUS 50 (PTP mode)", 0x04a9, 0x310e, PTPBUG_DELETE_SENDS_EVENT}, + /* Canon 400D does not have the infamous PTP bug */ + {"Canon:EOS 400D (PTP mode)", 0x04a9, 0x3110, 0}, + /* https://sourceforge.net/tracker/?func=detail&atid=358874&aid=1456391&group_id=8874 */ + {"Canon:EOS 30D (PTP mode)", 0x04a9, 0x3113, PTPBUG_DCIM_WRONG_PARENT}, + {"Canon:Digital IXUS 750 (PTP mode)", 0x04a9, 0x3116, PTPBUG_DELETE_SENDS_EVENT}, + {"Canon:PowerShot A700 (PTP mode)", 0x04a9, 0x3117, PTPBUG_DELETE_SENDS_EVENT}, + /* http://sourceforge.net/tracker/index.php?func=detail&aid=1498577&group_id=8874&atid=358874 */ + {"Canon:PowerShot SD700 (PTP mode)", 0x04a9, 0x3119, PTPBUG_DELETE_SENDS_EVENT}, + {"Canon:Digital IXUS 800 (PTP mode)", 0x04a9, 0x3119, PTPBUG_DELETE_SENDS_EVENT}, + /* Gert Vervoort <gert.vervoort@hccnet.nl> */ + {"Canon:PowerShot S3 IS (PTP mode)", 0x04a9, 0x311a, PTPBUG_DELETE_SENDS_EVENT}, + /* David Goodenough <david.goodenough at linkchoose.co.uk> */ + {"Canon:PowerShot A540 (PTP mode)", 0x04a9, 0x311b, PTPBUG_DELETE_SENDS_EVENT}, + /* Irc reporter */ + {"Canon:Digital IXUS 60 (PTP mode)", 0x04a9, 0x311c, PTPBUG_DELETE_SENDS_EVENT}, + /* Harald Dunkel <harald.dunkel@t-online.de> */ + {"Canon:PowerShot G7 (PTP mode)", 0x04a9, 0x3125, PTPBUG_DELETE_SENDS_EVENT}, + /* Ales Kozumplik <kozumplik@gmail.com> */ + {"Canon:PowerShot A530 (PTP mode)", 0x04a9, 0x3126, PTPBUG_DELETE_SENDS_EVENT}, + /* Jerome Vizcaino <vizcaino_jerome@yahoo.fr> */ + {"Canon:Digital IXUS 850 IS (PTP mode)",0x04a9, 0x3136, PTPBUG_DELETE_SENDS_EVENT}, + /* http://sourceforge.net/tracker/index.php?func=detail&aid=1565043&group_id=8874&atid=358874 */ + {"Canon:PowerShot A710 IS (PTP mode)", 0x04a9, 0x3138, PTPBUG_DELETE_SENDS_EVENT}, + {"Canon:PowerShot A640 (PTP mode)", 0x04a9, 0x3139, PTPBUG_DELETE_SENDS_EVENT|PTP_CAP|PTP_CAP_PREVIEW}, + {"Canon:PowerShot A630 (PTP mode)", 0x04a9, 0x313a, PTPBUG_DELETE_SENDS_EVENT}, + + + + /* Konica-Minolta PTP cameras */ + {"Konica-Minolta:DiMAGE A2 (PTP mode)", 0x132b, 0x0001, 0}, + {"Konica-Minolta:DiMAGE Z2 (PictBridge mode)", 0x132b, 0x0007, 0}, + {"Konica-Minolta:DiMAGE Z3 (PictBridge mode)", 0x132b, 0x0018, 0}, + {"Konica-Minolta:DiMAGE Z5 (PictBridge mode)", 0x132b, 0x0022, 0}, + + /* Fuji PTP cameras */ + {"Fuji:FinePix S7000 (PictBridge mode)",0x04cb, 0x0142, 0}, + {"Fuji:FinePix A330 (PictBridge mode)", 0x04cb, 0x014a, 0}, + {"Fuji:FinePix E900 (PictBridge mode)", 0x04cb, 0x0193, 0}, + {"Fuji:FinePix F30 (PictBridge mode)", 0x04cb, 0x019b, 0}, + + /* Ricoh Caplio GX */ + {"Ricoh:Caplio GX (PTP mode)", 0x05ca, 0x0325, 0}, + /* Ricoh Caplio R3 */ + {"Ricoh:Caplio R3 (PTP mode)", 0x05ca, 0x032f, 0}, + + /* Rollei dr5 */ + {"Rollei:dr5 (PTP mode)", 0x05ca, 0x220f, 0}, + + /* Ricoh Caplio GX 8 */ + {"Ricoh:Caplio GX 8 (PTP mode)", 0x05ca, 0x032d, 0}, + + /* Pentax cameras */ + {"Pentax:Optio 43WR", 0x0a17, 0x000d, 0}, + + {"Sanyo:VPC-C5 (PTP mode)", 0x0474, 0x0230, 0}, + + /* This camera speaks _only_ PictBridge, so it is too limited + * for us. -Marcus + {"Motorola:K1 (PTP mode)", 0x22b8, 0x4811, 0}, + */ + + /************ Add MTP devices below this line ***********/ + /* Jay MacDonald <jay@cheakamus.com> */ + {"iRiver:T10 (alternate)", 0x4102, 0x1113, PTP_MTP}, + /* Andreas Thienemann <andreas@bawue.de> */ + {"iRiver:T20 FM", 0x4102, 0x1114, PTP_MTP}, + /* Roger Pixley <skreech2@gmail.com> */ + {"iRiver:U10", 0x4102, 0x1116, PTP_MTP}, + /* Freaky <freaky@bananateam.nl> */ + {"iRiver:T10", 0x4102, 0x1117, PTP_MTP}, + /* Martin Senst <martin.senst@gmx.de> */ + {"iRiver:T20", 0x4102, 0x1118, PTP_MTP}, + /* Bruno Parente Lima <brunoparente77@yahoo.com.br> */ + {"iRiver:T30", 0x4102, 0x1119, PTP_MTP}, + /* reported by by David Wolpoff */ + {"iRiver:T10 2GB", 0x4102, 0x1120, PTP_MTP}, + /* Reported by Adam Torgerson */ + {"iRiver:Clix", 0x4102, 0x112a, PTP_MTP}, + /* Scott Call <scott.call@gmail.com> */ + {"iRiver:H10 20GB", 0x4102, 0x2101, PTP_MTP}, + /* Petr Spatka spatka@luzanky.cz */ + {"iRiver:H10", 0x4102, 0x2102, PTP_MTP}, + {"iRiver:Portable Media Center", 0x1006, 0x4002, PTP_MTP}, + {"iRiver:Portable Media Center", 0x1006, 0x4003, PTP_MTP}, + /* From: thanosz@softhome.net */ + {"Philipps:HDD6320", 0x0471, 0x01eb, PTP_MTP}, + {"Philipps:HDD6320 2", 0x0471, 0x014b, PTP_MTP}, + {"Philipps:HDD6130/17", 0x0471, 0x014c, PTP_MTP}, + /* borrowed from libmtp source */ + {"Creative:Zen Vision", 0x041e, 0x411f, PTP_MTP}, + {"Creative:Portable Media Center", 0x041e, 0x4123, PTP_MTP}, + {"Creative:Zen Xtra", 0x041e, 0x4128, PTP_MTP}, + {"Second generation Dell DJ", 0x041e, 0x412f, PTP_MTP}, + {"Creative:Zen Micro", 0x041e, 0x4130, PTP_MTP}, + {"Creative:Zen Touch", 0x041e, 0x4131, PTP_MTP}, + {"Dell:Pocket DJ", 0x041e, 0x4132, PTP_MTP}, + {"Creative:Zen Sleek", 0x041e, 0x4137, PTP_MTP}, + /* Jennifer Scalf <oneferna@gmail.com> */ + {"Creative:Zen MicroPhoto", 0x041e, 0x413c, PTP_MTP}, + {"Creative:Zen Sleek Photo", 0x041e, 0x413d, PTP_MTP}, + {"Creative:Zen Vision:M", 0x041e, 0x413e, PTP_MTP}, + /* Reported by marazm@o2.pl */ + {"Creative:Zen V", 0x041e, 0x4150, PTP_MTP}, + /* Reported by danielw@iinet.net.au */ + {"Creative:Zen Vision M", 0x041e, 0x4151, PTP_MTP}, + /* Reported by Darel on the XNJB forums */ + {"Creative:Zen V plus", 0x041e, 0x4152, PTP_MTP}, + /* From Richard Low of the libmtp devteam */ + {"Creative:Zen Vision W", 0x041e, 0x4153, PTP_MTP}, + /* The 2 below will not work out of the box, since the autoswitch + * to USB Mass Storage once probed. + * https://sourceforge.net/tracker/?func=detail&atid=108874&aid=1577793&group_id=8874 + */ + {"Dunlop:MP3 player 1GB", 0x10d6, 0x2200, PTP_MTP}, + {"EGOMAN:MD223AFD", 0x10d6, 0x2200, PTP_MTP}, + + /* IRC reporter */ + {"Dell:DJ Itty", 0x413c, 0x4500, PTP_MTP}, + + {"Intel:Bandon Portable Media Center", 0x045e, 0x00c9, PTP_MTP}, + + /* From Gerhard Mekenkamp */ + {"Philips:GoGear Audio", 0x0471, 0x0165, PTP_MTP}, + + /* Marcoen Hirschberg <marcoen@users.sourceforge.net> */ + {"Toshiba:Gigabeat MEGF-40", 0x0930, 0x0009, PTP_MTP}, + {"Toshiba:Gigabeat", 0x0930, 0x000c, PTP_MTP}, + {"Toshiba:Gigabeat S", 0x0930, 0x0010, PTP_MTP}, + /* Reported by Rob Brown */ + {"Toshiba:Gigabeat P10", 0x0930, 0x0011, PTP_MTP}, + + /* Reported by gudul1@users.sourceforge.net */ + {"Archos 104", 0x0e79, 0x120a, PTP_MTP}, + + /* From Mark Veinot */ + {"JVC:Alneo XA-HD500", 0x04f1, 0x6105, PTP_MTP}, + + /* http://sourceforge.net/tracker/index.php?func=detail&aid=1474056&group_id=8874&atid=108874 */ + {"Samsung:YP-T7J", 0x04e8, 0x5047, PTP_MTP}, + {"Samsung:YH-820", 0x04e8, 0x502e, PTP_MTP}, + {"Samsung:YH-925", 0x04e8, 0x502f, PTP_MTP}, + /* Reported by cstrickler@gmail.com */ + {"Samsung:YP-U2J (YP-U2JXB/XAA)", 0x04e8, 0x5054, PTP_MTP}, + /* Reported by Andrew Benson */ + {"Samsung:YP-F2J", 0x04e8, 0x5057, PTP_MTP}, + /* Reported by Patrick <skibler@gmail.com> */ + {"Samsung:YP-K5", 0x04e8, 0x505a, PTP_MTP}, + + {"Samsung:YH-999 Portable Media Center",0x04e8, 0x5a0f, PTP_MTP}, + + /* Reported by Brian Robison */ + {"SanDisk:Sansa m240", 0x0781, 0x7400, PTP_MTP}, + /* Reported by tangent_@users.sourceforge.net */ + {"SanDisk:Sansa c150", 0x0781, 0x7410, PTP_MTP}, + /* http://sourceforge.net/tracker/index.php?func=detail&aid=1515815&group_id=8874&atid=358874 */ + {"Sandisk:Sansa e200", 0x0781, 0x7420, PTP_MTP}, + {"Sandisk:Sansa e260", 0x0781, 0x7420, PTP_MTP}, + +}; + +static struct { + unsigned short format_code; + const char *txt; +} object_formats[] = { + {PTP_OFC_Undefined, "application/x-unknown"}, + {PTP_OFC_Association, "application/x-association"}, + {PTP_OFC_Script, "application/x-script"}, + {PTP_OFC_Executable, "application/octet-stream"}, + {PTP_OFC_Text, "text/plain"}, + {PTP_OFC_HTML, "text/html"}, + {PTP_OFC_DPOF, "text/plain"}, + {PTP_OFC_AIFF, "audio/x-aiff"}, + {PTP_OFC_WAV, GP_MIME_WAV}, + {PTP_OFC_MP3, "audio/mpeg"}, + {PTP_OFC_AVI, GP_MIME_AVI}, + {PTP_OFC_MPEG, "video/mpeg"}, + {PTP_OFC_ASF, "video/x-asf"}, + {PTP_OFC_QT, "video/quicktime"}, + {PTP_OFC_EXIF_JPEG, GP_MIME_JPEG}, + {PTP_OFC_TIFF_EP, "image/x-tiffep"}, + {PTP_OFC_FlashPix, "image/x-flashpix"}, + {PTP_OFC_BMP, GP_MIME_BMP}, + {PTP_OFC_CIFF, "image/x-ciff"}, + {PTP_OFC_Undefined_0x3806, "application/x-unknown"}, + {PTP_OFC_GIF, "image/gif"}, + {PTP_OFC_JFIF, GP_MIME_JPEG}, + {PTP_OFC_PCD, "image/x-pcd"}, + {PTP_OFC_PICT, "image/x-pict"}, + {PTP_OFC_PNG, GP_MIME_PNG}, + {PTP_OFC_Undefined_0x380C, "application/x-unknown"}, + {PTP_OFC_TIFF, GP_MIME_TIFF}, + {PTP_OFC_TIFF_IT, "image/x-tiffit"}, + {PTP_OFC_JP2, "image/x-jpeg2000bff"}, + {PTP_OFC_JPX, "image/x-jpeg2000eff"}, + {0, NULL} +}; + +static int +set_mimetype (Camera *camera, CameraFile *file, uint16_t ofc) +{ + int i; + + for (i = 0; object_formats[i].format_code; i++) + if (object_formats[i].format_code == ofc) + { + CR (gp_file_set_mime_type (file, object_formats[i].txt)); + return (GP_OK); + } + + CR (gp_file_set_mime_type (file, "application/x-unknown")); + return (GP_OK); +} + + + +static void +strcpy_mime(char * dest, uint16_t ofc) { + int i; + + for (i = 0; object_formats[i].format_code; i++) + if (object_formats[i].format_code == ofc) { + strcpy(dest, object_formats[i].txt); + return; + } + strcpy(dest, "application/x-unknown"); +} + +static uint32_t +get_mimetype (Camera *camera, CameraFile *file) +{ + int i; + const char *mimetype; + + gp_file_get_mime_type (file, &mimetype); + for (i = 0; object_formats[i].format_code; i++) + if (!strcmp(mimetype,object_formats[i].txt)) + return (object_formats[i].format_code); + return (PTP_OFC_Undefined); +} + +struct _PTPData { + Camera *camera; + GPContext *context; +}; +typedef struct _PTPData PTPData; + +#define CONTEXT_BLOCK_SIZE 100000 +static short +ptp_read_func (unsigned char *bytes, unsigned int size, void *data, unsigned int *readbytes) +{ + Camera *camera = ((PTPData *)data)->camera; + int toread, result = GP_ERROR, curread = 0; + int usecontext = (size > CONTEXT_BLOCK_SIZE); + int progressid = 0, tries = 0; + GPContext *context = ((PTPData *)data)->context; + + /* Split into small blocks. Too large blocks (>1x MB) would + * timeout. + */ +retry: + if (usecontext) + progressid = gp_context_progress_start (context, (size/CONTEXT_BLOCK_SIZE), _("Downloading...")); + while (curread < size) { + int oldsize = curread; + + toread = size - curread; + if (toread > 4096) + toread = 4096; + result = gp_port_read (camera->port, (char*)(bytes + curread), toread); + if (result == 0) { + result = gp_port_read (camera->port, (char*)(bytes + curread), toread); + } + if (result < 0) + break; + curread += result; + if (usecontext && (oldsize/CONTEXT_BLOCK_SIZE < curread/CONTEXT_BLOCK_SIZE)) + gp_context_progress_update (context, progressid, curread/CONTEXT_BLOCK_SIZE); + if (result < toread) /* short reads are common */ + break; + } + if (usecontext) + gp_context_progress_stop (context, progressid); + if (result > 0) { + *readbytes = curread; + return (PTP_RC_OK); + } else { + if (result == GP_ERROR_IO_READ) { + gp_log (GP_LOG_DEBUG, "ptp2/usbread", "Clearing halt on IN EP and retrying once.\n"); + gp_port_usb_clear_halt (camera->port, GP_PORT_USB_ENDPOINT_IN); + /* retrying only makes sense if we did not read anything yet */ + if ((tries++ < 1) && (curread == 0)) + goto retry; + } + return (translate_gp_result (result)); + } +} + +static short +ptp_write_func (unsigned char *bytes, unsigned int size, void *data) +{ + Camera *camera = ((PTPData *)data)->camera; + int towrite, result = GP_ERROR, curwrite = 0; + int progressid = 0; + int usecontext = (size > CONTEXT_BLOCK_SIZE); + GPContext *context = ((PTPData *)data)->context; + + /* + * gp_port_write returns (in case of success) the number of bytes + * write. Too large blocks (>5x MB) could timeout. + */ + if (usecontext) + progressid = gp_context_progress_start (context, (size/CONTEXT_BLOCK_SIZE), _("Uploading...")); + while (curwrite < size) { + int oldsize = curwrite; + + towrite = size-curwrite; + if (towrite > 4096) + towrite = 4096; + result = gp_port_write (camera->port, (char*)(bytes + curwrite), towrite); + if (result < 0) + break; + curwrite += result; + if (usecontext && (oldsize/CONTEXT_BLOCK_SIZE < curwrite/CONTEXT_BLOCK_SIZE)) + gp_context_progress_update (context, progressid, curwrite/CONTEXT_BLOCK_SIZE); + if (result < towrite) /* short writes happen */ + break; + } + if (usecontext) + gp_context_progress_stop (context, progressid); + /* Should load wMaxPacketsize from endpoint first. But works fine for all EPs. */ + if ((size % 512) == 0) + gp_port_write (camera->port, "x", 0); + if (result < 0) + return (translate_gp_result (result)); + return PTP_RC_OK; +} + +static short +ptp_check_int (unsigned char *bytes, unsigned int size, void *data, unsigned int *rlen) +{ + Camera *camera = ((PTPData *)data)->camera; + int result; + + /* + * gp_port_check_int returns (in case of success) the number of bytes + * read. + */ + + result = gp_port_check_int (camera->port, (char*)bytes, size); + if (result==0) result = gp_port_check_int (camera->port, (char*)bytes, size); + if (result >= 0) { + *rlen = result; + return (PTP_RC_OK); + } else { + return (translate_gp_result (result)); + } +} + +static short +ptp_check_int_fast (unsigned char *bytes, unsigned int size, void *data, unsigned int *rlen) +{ + Camera *camera = ((PTPData *)data)->camera; + int result; + + /* + * gp_port_check_int returns (in case of success) the number of bytes + * read. libptp doesn't need that. + */ + + result = gp_port_check_int_fast (camera->port, (char*)bytes, size); + if (result==0) result = gp_port_check_int_fast (camera->port, (char*)bytes, size); + if (result >= 0) { + *rlen = result; + return (PTP_RC_OK); + } else { + return (translate_gp_result (result)); + } +} + + +static void +#ifdef __GNUC__ +__attribute__((__format__(printf,2,0))) +#endif +ptp_debug_func (void *data, const char *format, va_list args) +{ + gp_logv (GP_LOG_DEBUG, "ptp", format, args); +} + +static void +#ifdef __GNUC__ +__attribute__((__format__(printf,2,0))) +#endif +ptp_error_func (void *data, const char *format, va_list args) +{ + PTPData *ptp_data = data; + char buf[2048]; + + vsnprintf (buf, sizeof (buf), format, args); + gp_context_error (ptp_data->context, "%s", buf); +} + +int +camera_abilities (CameraAbilitiesList *list) +{ + int i; + CameraAbilities a; + + for (i = 0; i < sizeof(models)/sizeof(models[0]); i++) { + memset(&a, 0, sizeof(a)); + strcpy (a.model, models[i].model); + a.status = GP_DRIVER_STATUS_PRODUCTION; + a.port = GP_PORT_USB; + a.speed[0] = 0; + a.usb_vendor = models[i].usb_vendor; + a.usb_product= models[i].usb_product; + if (models[i].known_bugs & PTP_MTP) { + a.operations = GP_OPERATION_NONE; + a.device_type = GP_DEVICE_AUDIO_PLAYER; + a.file_operations = GP_FILE_OPERATION_DELETE; + } else { + a.device_type = GP_DEVICE_STILL_CAMERA; + a.operations = GP_OPERATION_NONE; + if (models[i].known_bugs & PTP_CAP) + a.operations |= GP_OPERATION_CAPTURE_IMAGE | GP_OPERATION_CONFIG; + if (models[i].known_bugs & PTP_CAP_PREVIEW) + a.operations |= GP_OPERATION_CAPTURE_PREVIEW; + a.file_operations = GP_FILE_OPERATION_PREVIEW | + GP_FILE_OPERATION_DELETE; + } + a.folder_operations = GP_FOLDER_OPERATION_PUT_FILE | + GP_FOLDER_OPERATION_MAKE_DIR | + GP_FOLDER_OPERATION_REMOVE_DIR; + CR (gp_abilities_list_append (list, a)); + } + + memset(&a, 0, sizeof(a)); + strcpy(a.model, "USB PTP Class Camera"); + a.status = GP_DRIVER_STATUS_TESTING; + a.port = GP_PORT_USB; + a.speed[0] = 0; + a.usb_class = 6; + a.usb_subclass = 1; + a.usb_protocol = 1; + a.operations = GP_CAPTURE_IMAGE | GP_OPERATION_CONFIG; + a.file_operations = GP_FILE_OPERATION_PREVIEW| + GP_FILE_OPERATION_DELETE; + a.folder_operations = GP_FOLDER_OPERATION_PUT_FILE + | GP_FOLDER_OPERATION_MAKE_DIR | + GP_FOLDER_OPERATION_REMOVE_DIR; + a.device_type = GP_DEVICE_STILL_CAMERA; + CR (gp_abilities_list_append (list, a)); + memset(&a, 0, sizeof(a)); + strcpy(a.model, "MTP Device"); + a.status = GP_DRIVER_STATUS_TESTING; + a.port = GP_PORT_USB; + a.speed[0] = 0; + a.usb_class = 666; + a.usb_subclass = -1; + a.usb_protocol = -1; + a.operations = GP_OPERATION_NONE; + a.file_operations = GP_FILE_OPERATION_DELETE; + a.folder_operations = GP_FOLDER_OPERATION_PUT_FILE + | GP_FOLDER_OPERATION_MAKE_DIR | + GP_FOLDER_OPERATION_REMOVE_DIR; + a.device_type = GP_DEVICE_AUDIO_PLAYER; + CR (gp_abilities_list_append (list, a)); + + memset(&a, 0, sizeof(a)); + strcpy(a.model, "PTP/IP Camera"); + a.status = GP_DRIVER_STATUS_TESTING; + a.port = GP_PORT_PTPIP; + a.operations = GP_CAPTURE_IMAGE | + GP_OPERATION_CONFIG; + a.file_operations = GP_FILE_OPERATION_PREVIEW | + GP_FILE_OPERATION_DELETE; + a.folder_operations = GP_FOLDER_OPERATION_PUT_FILE | + GP_FOLDER_OPERATION_MAKE_DIR | + GP_FOLDER_OPERATION_REMOVE_DIR; + a.device_type = GP_DEVICE_STILL_CAMERA; + CR (gp_abilities_list_append (list, a)); + + return (GP_OK); +} + +int +camera_id (CameraText *id) +{ + strcpy (id->text, "PTP"); + + return (GP_OK); +} + +static int +camera_exit (Camera *camera, GPContext *context) +{ + if (camera->pl!=NULL) { + /* close iconv converters */ + iconv_close(camera->pl->params.cd_ucs2_to_locale); + iconv_close(camera->pl->params.cd_locale_to_ucs2); + /* close ptp session */ + ptp_closesession (&camera->pl->params); + free (camera->pl); + camera->pl = NULL; + } + if (camera->port!=NULL) { + /* clear halt */ + gp_port_usb_clear_halt + (camera->port, GP_PORT_USB_ENDPOINT_IN); + gp_port_usb_clear_halt + (camera->port, GP_PORT_USB_ENDPOINT_OUT); + gp_port_usb_clear_halt + (camera->port, GP_PORT_USB_ENDPOINT_INT); + } + + /* FIXME: free all camera->pl->params.objectinfo[] and + other malloced data, like wifi profiles */ + + return (GP_OK); +} + +static int +camera_about (Camera *camera, CameraText *text, GPContext *context) +{ + /* Note that we are not a so called 'Licensed Implementation' of MTP + * ... (for a LI you need express approval from Microsoft etc.) + */ + strncpy (text->text, + _("PTP2 driver\n" + "(c)2001-2005 by Mariusz Woloszyn <emsi@ipartners.pl>.\n" + "(c)2003-2006 by Marcus Meissner <marcus@jet.franken.de>.\n" + "This driver supports cameras that support PTP or PictBridge(tm), and\n" + "Media Players that support the Media Transfer Protocol (MTP).\n" + "\n" + "This driver is not a 'Licensed Implementation' of the Media Transfer Protocol.\n" + "\n" + "Enjoy!"), sizeof (text->text)); + return (GP_OK); +} + +static inline int +handle_to_n (uint32_t handle, Camera *camera) +{ + int i; + for (i = 0; i < camera->pl->params.handles.n; i++) + if (camera->pl->params.handles.Handler[i]==handle) return i; + /* else not found */ + return (PTP_HANDLER_SPECIAL); +} + +static inline int +storage_handle_to_n (uint32_t storage, uint32_t handle, Camera *camera) +{ + int i; + for (i = 0; i < camera->pl->params.handles.n; i++) + if ( (camera->pl->params.handles.Handler[i] == handle) && + (camera->pl->params.objectinfo[i].StorageID == storage) + ) + return i; + /* else not found */ + return (PTP_HANDLER_SPECIAL); +} + +/* add new object to internal driver structures. issued when creating +folder, uploading objects, or captured images. */ +static int +add_object (Camera *camera, uint32_t handle, GPContext *context) +{ + int n; + PTPParams *params = &camera->pl->params; + + /* increase number of objects */ + n = ++params->handles.n; + /* realloc more space for camera->pl->params.objectinfo */ + params->objectinfo = (PTPObjectInfo*) + realloc(params->objectinfo, + sizeof(PTPObjectInfo)*n); + /* realloc more space for params->handles.Handler */ + params->handles.Handler= (uint32_t *) + realloc(params->handles.Handler, + sizeof(uint32_t)*n); + /* clear objectinfo entry for new object and assign new handler */ + memset(¶ms->objectinfo[n-1],0,sizeof(PTPObjectInfo)); + params->handles.Handler[n-1]=handle; + /* get new obectinfo */ + CPR (context, ptp_getobjectinfo(params, handle, ¶ms->objectinfo[n-1])); + return (GP_OK); +} + +static int +camera_capture_preview (Camera *camera, CameraFile *file, GPContext *context) +{ + unsigned char *data = NULL; + uint32_t size = 0; + int ret; + PTPParams *params = &camera->pl->params; + + /* Currently disabled, since we must make sure for Canons + * that prepare capture was called. + * Enable: remote 0 &&, run + * gphoto2 --set-config capture=on --capture-preview + */ + if (camera->pl->params.deviceinfo.VendorExtensionID == PTP_VENDOR_CANON) { + if (!ptp_operation_issupported(&camera->pl->params, PTP_OC_CANON_ViewfinderOn)) { + gp_context_error (context, + _("Sorry, your Canon camera does not support Canon Viewfinder mode")); + return GP_ERROR_NOT_SUPPORTED; + } + ret = ptp_canon_viewfinderon (params); + if (ret != PTP_RC_OK) { + gp_context_error (context, _("Canon enable viewfinder failed: %d"), ret); + return GP_ERROR; + } + ret = ptp_canon_getviewfinderimage (params, &data, &size); + if (ret != PTP_RC_OK) { + gp_context_error (context, _("Canon get viewfinder image failed: %d"), ret); + return GP_ERROR; + } + gp_file_set_data_and_size ( file, (char*)data, size ); + gp_file_set_mime_type (file, GP_MIME_JPEG); /* always */ + /* Add an arbitrary file name so caller won't crash */ + gp_file_set_name (file, "canon_preview.jpg"); + + ret = ptp_canon_viewfinderoff (params); + if (ret != PTP_RC_OK) { + gp_context_error (context, _("Canon disable viewfinder failed: %d"), ret); + return GP_ERROR; + } + return GP_OK; + } + return GP_ERROR_NOT_SUPPORTED; +} + +static int +get_folder_from_handle (Camera *camera, uint32_t storage, uint32_t handle, char *folder) { + int i, ret; + + if (handle == PTP_HANDLER_ROOT) + return GP_OK; + + i = storage_handle_to_n (storage, handle, camera); + if (i == PTP_HANDLER_SPECIAL) + return (GP_ERROR_BAD_PARAMETERS); + + ret = get_folder_from_handle (camera, storage, camera->pl->params.objectinfo[i].ParentObject, folder); + if (ret != GP_OK) + return ret; + + strcat (folder, camera->pl->params.objectinfo[i].Filename); + strcat (folder, "/"); + return (GP_OK); +} + +static int +add_objectid_to_gphotofs(Camera *camera, CameraFilePath *path, GPContext *context, + uint32_t newobject, PTPObjectInfo *oi) { + int ret; + PTPParams *params = &camera->pl->params; + CameraFile *file = NULL; + unsigned char *ximage = NULL; + CameraFileInfo info; + + ret = gp_file_new(&file); + if (ret!=GP_OK) return ret; + gp_file_set_type (file, GP_FILE_TYPE_NORMAL); + gp_file_set_name(file, path->name); + set_mimetype (camera, file, oi->ObjectFormat); + CPR (context, ptp_getobject(params, newobject, &ximage)); + ret = gp_file_set_data_and_size(file, (char*)ximage, oi->ObjectCompressedSize); + if (ret != GP_OK) return ret; + ret = gp_filesystem_append(camera->fs, path->folder, path->name, context); + if (ret != GP_OK) return ret; + ret = gp_filesystem_set_file_noop(camera->fs, path->folder, file, context); + if (ret != GP_OK) return ret; + + /* we also get the fs info for free, so just set it */ + info.file.fields = GP_FILE_INFO_TYPE | GP_FILE_INFO_NAME | + GP_FILE_INFO_WIDTH | GP_FILE_INFO_HEIGHT | + GP_FILE_INFO_SIZE; + strcpy_mime (info.file.type, oi->ObjectFormat); + strcpy(info.file.name,path->name); + info.file.width = oi->ImagePixWidth; + info.file.height = oi->ImagePixHeight; + info.file.size = oi->ObjectCompressedSize; + info.preview.fields = GP_FILE_INFO_TYPE | + GP_FILE_INFO_WIDTH | GP_FILE_INFO_HEIGHT | + GP_FILE_INFO_SIZE; + strcpy_mime (info.preview.type, oi->ThumbFormat); + info.preview.width = oi->ThumbPixWidth; + info.preview.height = oi->ThumbPixHeight; + info.preview.size = oi->ThumbCompressedSize; + return gp_filesystem_set_info_noop(camera->fs, path->folder, info, context); +} + +/** + * camera_nikon_capture: + * params: Camera* - camera object + * CameraCaptureType type - type of object to capture + * CameraFilePath *path - filled out with filename and folder on return + * GPContext* context - gphoto context for this operation + * + * This function captures an image using special Nikon capture to SDRAM. + * The object(s) do(es) not appear in the "objecthandles" array returned by GetObjectHandles, + * so we need to download them here immediately. + * + * Return values: A gphoto return code. + * Upon success CameraFilePath *path contains the folder and filename of the captured + * image. + */ +static int +camera_nikon_capture (Camera *camera, CameraCaptureType type, CameraFilePath *path, + GPContext *context) +{ + static int capcnt = 0; + PTPObjectInfo oi; + PTPParams *params = &camera->pl->params; + PTPDevicePropDesc propdesc; + int i, ret, hasc101 = 0, burstnumber = 1; + + if (type != GP_CAPTURE_IMAGE) + return GP_ERROR_NOT_SUPPORTED; + + if (params->deviceinfo.VendorExtensionID!=PTP_VENDOR_NIKON) + return GP_ERROR_NOT_SUPPORTED; + + if (!ptp_operation_issupported(params,PTP_OC_NIKON_Capture)) { + gp_context_error(context, + _("Sorry, your camera does not support Nikon capture")); + return GP_ERROR_NOT_SUPPORTED; + } + if ( ptp_property_issupported(params, PTP_DPC_StillCaptureMode) && + (PTP_RC_OK == ptp_getdevicepropdesc (params, PTP_DPC_StillCaptureMode, &propdesc)) && + (propdesc.DataType == PTP_DTC_UINT16) && + (propdesc.CurrentValue.u16 == 2) /* Burst Mode */ && + ptp_property_issupported(params, PTP_DPC_BurstNumber) && + (PTP_RC_OK == ptp_getdevicepropdesc (params, PTP_DPC_BurstNumber, &propdesc)) && + (propdesc.DataType == PTP_DTC_UINT16) + ) { + burstnumber = propdesc.CurrentValue.u16; + gp_log (GP_LOG_DEBUG, "ptp2", "burstnumber %d", burstnumber); + } + + do { + ret = ptp_nikon_capture(params, 0xffffffff); + } while (ret == PTP_RC_DeviceBusy); + CPR (context, ret); + + CR (gp_port_set_timeout (camera->port, USB_TIMEOUT_CAPTURE)); + + while (!((ptp_nikon_device_ready(params) == PTP_RC_OK) && hasc101)) { + int i, evtcnt; + PTPUSBEventContainer *nevent = NULL; + + /* Just busy loop until the camera is ready again. */ + /* and wait for the 0xc101 event */ + ret = ptp_nikon_check_event(params, &nevent, &evtcnt); + if (ret != PTP_RC_OK) + break; + for (i=0;i<evtcnt;i++) { + /*fprintf(stderr,"1:nevent.Code is %x / param %lx\n", nevent[i].code, (unsigned long)nevent[i].param1);*/ + if (nevent[i].code == 0xc101) hasc101=1; + } + free (nevent); + } + + for (i=0;i<burstnumber;i++) { + /* In Burst mode, the image is always 0xffff0001. + * The firmware just gives us one after the other for the same ID + */ + ret = ptp_getobjectinfo (params, 0xffff0001, &oi); + if (ret != PTP_RC_OK) { + fprintf (stderr,"getobjectinfo(%x) failed: %d\n", 0xffff0001, ret); + return GP_ERROR_IO; + } + if (oi.ParentObject != 0) + fprintf(stderr,"Parentobject is 0x%lx now?\n", (unsigned long)oi.ParentObject); + /* Happens on Nikon D70, we get Storage ID 0. So fake one. */ + if (oi.StorageID == 0) + oi.StorageID = 0x00010001; + sprintf (path->folder,"/"STORAGE_FOLDER_PREFIX"%08lx",(unsigned long)oi.StorageID); + sprintf (path->name, "capt%04d.jpg", capcnt++); + ret = add_objectid_to_gphotofs(camera, path, context, 0xffff0001, &oi); + if (ret != GP_OK) { + fprintf (stderr, "failed to add object\n"); + return ret; + } + } + return GP_OK; +} + +/* To use: + * gphoto2 --set-config capture=on --config --capture-image + * gphoto2 -f /store_80000001 -p 1 + * will download a file called "VirtualObject" + */ +static int +camera_canon_capture (Camera *camera, CameraCaptureType type, CameraFilePath *path, + GPContext *context) +{ + static int capcnt = 0; + PTPObjectInfo oi; + int i, ret, isevent; + PTPParams *params = &camera->pl->params; + uint32_t newobject = 0x0; + PTPPropertyValue propval; + uint16_t val16; + PTPContainer event; + PTPUSBEventContainer usbevent; + uint32_t handle; + + if (!ptp_operation_issupported(params, PTP_OC_CANON_InitiateCaptureInMemory)) { + gp_context_error (context, + _("Sorry, your Canon camera does not support Canon Capture initiation")); + return GP_ERROR_NOT_SUPPORTED; + } + + if (!ptp_property_issupported(params, PTP_DPC_CANON_FlashMode)) { + /* did not call --set-config capture=on, do it for user */ + ret = camera_prepare_capture (camera, context); + if (ret != GP_OK) + return ret; + if (!ptp_property_issupported(params, PTP_DPC_CANON_FlashMode)) { + gp_context_error (context, + _("Sorry, initializing your camera did not work. Please report this.")); + return GP_ERROR_NOT_SUPPORTED; + } + } + + propval.u16=3; /* 3 */ + ret = ptp_setdevicepropvalue(params, PTP_DPC_CANON_D029, &propval, PTP_DTC_UINT16); + if (ret != PTP_RC_OK) + gp_log (GP_LOG_DEBUG, "ptp", "setdevicepropvalue 0xd029 failed, %d\n", ret); + +#if 0 + /* FIXME: For now, to avoid flash during debug */ + propval.u8 = 0; + ret = ptp_setdevicepropvalue(params, PTP_DPC_CANON_FlashMode, &propval, PTP_DTC_UINT8); +#endif + ret = ptp_canon_initiatecaptureinmemory (params); + if (ret != PTP_RC_OK) { + gp_context_error (context, _("Canon Capture failed: %d"), ret); + return GP_ERROR; + } + /* Catch event */ + if (PTP_RC_OK == (val16 = params->event_wait (params, &event))) { + if (event.Code == PTP_EC_CaptureComplete) + gp_log (GP_LOG_DEBUG, "ptp", "Event: capture complete. \n"); + else + gp_log (GP_LOG_DEBUG, "ptp", "Unknown event: 0x%X\n", event.Code); + } /* else no event yet ... try later. */ + + /* checking events in stack. */ + for (i=0;i<100;i++) { + ret = ptp_canon_checkevent (params,&usbevent,&isevent); + if (ret!=PTP_RC_OK) + continue; + if (isevent) + gp_log (GP_LOG_DEBUG, "ptp","evdata: L=0x%X, T=0x%X, C=0x%X, trans_id=0x%X, p1=0x%X, p2=0x%X, p3=0x%X\n", usbevent.length,usbevent.type,usbevent.code,usbevent.trans_id, usbevent.param1, usbevent.param2, usbevent.param3); + if ( isevent && + (usbevent.type==PTP_USB_CONTAINER_EVENT) && + (usbevent.code==PTP_EC_CANON_RequestObjectTransfer) + ) { + int j; + + handle=usbevent.param1; + gp_log (GP_LOG_DEBUG, "ptp", "PTP_EC_CANON_RequestObjectTransfer, object handle=0x%X. \n",usbevent.param1); + gp_log (GP_LOG_DEBUG, "ptp","evdata: L=0x%X, T=0x%X, C=0x%X, trans_id=0x%X, p1=0x%X, p2=0x%X, p3=0x%X\n", usbevent.length,usbevent.type,usbevent.code,usbevent.trans_id, usbevent.param1, usbevent.param2, usbevent.param3); + newobject = usbevent.param1; + + for (j=0;j<2;j++) { + ret=ptp_canon_checkevent(params,&usbevent,&isevent); + if ((ret==PTP_RC_OK) && isevent) + gp_log (GP_LOG_DEBUG, "ptp", "evdata: L=0x%X, T=0x%X, C=0x%X, trans_id=0x%X, p1=0x%X, p2=0x%X, p3=0x%X\n", usbevent.length,usbevent.type,usbevent.code,usbevent.trans_id, usbevent.param1, usbevent.param2, usbevent.param3); + } + + + ret = ptp_canon_aeafawb(params,7); + break; + } + } + /* Catch event, attempt 2 */ + if (val16!=PTP_RC_OK) { + if (PTP_RC_OK==params->event_wait (params, &event)) { + if (event.Code==PTP_EC_CaptureComplete) + printf("Event: capture complete. \n"); + else + printf("Event: 0x%X\n", event.Code); + } else + printf("No expected capture complete event\n"); + } + if (i==100) { + gp_log (GP_LOG_DEBUG, "ptp","ERROR: Capture timed out!\n"); + return GP_ERROR_TIMEOUT; + } + + /* FIXME: handle multiple images (as in BurstMode) */ + ret = ptp_getobjectinfo (params, newobject, &oi); + if (ret != PTP_RC_OK) return GP_ERROR_IO; + if (oi.ParentObject != 0) { + fprintf(stderr,"Parentobject is 0x%lx now?\n", (unsigned long)oi.ParentObject); + } + sprintf (path->folder,"/"STORAGE_FOLDER_PREFIX"%08lx",(unsigned long)oi.StorageID); + sprintf (path->name, "capt%04d.jpg", capcnt++); + return add_objectid_to_gphotofs(camera, path, context, newobject, &oi); +} + +static int +camera_capture (Camera *camera, CameraCaptureType type, CameraFilePath *path, + GPContext *context) +{ + PTPContainer event; + PTPParams *params = &camera->pl->params; + uint32_t newobject = 0x0; + + if ( (params->deviceinfo.VendorExtensionID == PTP_VENDOR_NIKON) && + ptp_operation_issupported(params, PTP_OC_NIKON_Capture) + ){ + char buf[1024]; + if ((GP_OK != gp_setting_get("ptp2","capturetarget",buf)) || !strcmp(buf,"sdram")) + return camera_nikon_capture (camera, type, path, context); + } + if ( (params->deviceinfo.VendorExtensionID == PTP_VENDOR_CANON) && + ptp_operation_issupported(params, PTP_OC_CANON_InitiateCaptureInMemory) + ) { + char buf[1024]; + if ((GP_OK != gp_setting_get("ptp2","capturetarget",buf)) || !strcmp(buf,"sdram")) + return camera_canon_capture (camera, type, path, context); + } + + if (type != GP_CAPTURE_IMAGE) + return GP_ERROR_NOT_SUPPORTED; + + if (!ptp_operation_issupported(params,PTP_OC_InitiateCapture)) { + gp_context_error(context, + _("Sorry, your camera does not support generic capture")); + return GP_ERROR_NOT_SUPPORTED; + } + + /* A capture may take longer than the standard 8 seconds. + * The G5 for instance does, or in dark rooms ... + * Even 16 seconds might not be enough. (Marcus) + */ + /* ptp_initiatecapture() returns immediately, only the event + * indicating that the capure has been completed may occur after + * few seconds. moving down the code. (kil3r) + */ + CPR(context,ptp_initiatecapture(params, 0x00000000, 0x00000000)); + CR (gp_port_set_timeout (camera->port, USB_TIMEOUT_CAPTURE)); + /* A word of comments is worth here. + * After InitiateCapture camera should report with ObjectAdded event + * all newly created objects. However there might be more than one + * newly created object. There a two scenarios here, which may occur + * both at the time. + * 1) InitiateCapture trigers capture of more than one object if the + * camera is in burst mode for example. + * 2) InitiateCapture creates a number of objects, but not all + * objects represents images. This happens when the camera creates a + * folder for newly captured image(s). This may happen with the + * fresh, formatted flashcard or in burs mode if the camera is + * configured to create a dedicated folder for a burst of pictures. + * The newly created folder (an association object) is reported + * before the images that are stored after its creation. + * Thus we set CameraFilePath to the path to last object reported by + * the camera. + */ + + /* I hate workarounds! Nikon is not 100% PTP compatible here! */ + if (params->deviceinfo.VendorExtensionID==PTP_VENDOR_NIKON) + goto out; + { + short ret = params->event_wait(params,&event); + CR (gp_port_set_timeout (camera->port, USB_NORMAL_TIMEOUT)); + if (ret!=PTP_RC_OK) goto err; + } + while (event.Code==PTP_EC_ObjectAdded) { + /* add newly created object to internal structures */ + add_object (camera, event.Param1, context); + newobject = event.Param1; + + if (params->event_wait (params, &event)!=PTP_RC_OK) + { + gp_context_error (context, + _("Capture command completed, but no confirmation received")); + goto err; + } + } + if (event.Code==PTP_EC_CaptureComplete) + goto out; + + gp_context_error (context,_("Received event 0x%04x"),event.Code); + +err: + /* we're not setting *path on error! */ + return GP_ERROR; + +out: + /* clear path, so we get defined results even without object info */ + path->name[0]='\0'; + path->folder[0]='\0'; + + if (newobject != 0) { + int i; + + for (i = params->handles.n ; i--; ) { + PTPObjectInfo *obinfo; + + if (params->handles.Handler[i] != newobject) + continue; + obinfo = &camera->pl->params.objectinfo[i]; + strcpy (path->name, obinfo->Filename); + sprintf (path->folder,"/"STORAGE_FOLDER_PREFIX"%08lx/",(unsigned long)obinfo->StorageID); + get_folder_from_handle (camera, obinfo->StorageID, obinfo->ParentObject, path->folder); + /* delete last / or we get confused later. */ + path->folder[ strlen(path->folder)-1 ] = '\0'; + CR (gp_filesystem_append (camera->fs, path->folder, + path->name, context)); + break; + } + } + return GP_OK; +} + +static int +camera_wait_for_event (Camera *camera, int timeout, + CameraEventType *eventtype, void **eventdata, + GPContext *context) { + PTPContainer event; + PTPParams *params = &camera->pl->params; + uint32_t newobject = 0x0; + CameraFilePath *path; + int i, oldtimeout; + uint16_t ret; + + memset (&event, 0, sizeof(event)); + gp_port_get_timeout (camera->port, &oldtimeout); + gp_port_set_timeout (camera->port, timeout); + ret = params->event_wait(params,&event); + gp_port_set_timeout (camera->port, oldtimeout); + + if (ret!=PTP_RC_OK) { + /* FIXME: Might be another error, but usually is a timeout */ + gp_log (GP_LOG_DEBUG, "ptp2", "wait_for_event: received error 0x%04x", ret); + *eventtype = GP_EVENT_TIMEOUT; + return GP_OK; + } + gp_log (GP_LOG_DEBUG, "ptp2", "wait_for_event: code=0x%04x, param1 0x%08x", + event.Code, event.Param1 + ); + + switch (event.Code) { + case PTP_EC_ObjectAdded: + path = (CameraFilePath *)malloc(sizeof(CameraFilePath)); + if (!path) + return GP_ERROR_NO_MEMORY; + newobject = event.Param1; + add_object (camera, event.Param1, context); + path->name[0]='\0'; + path->folder[0]='\0'; + + for (i = params->handles.n ; i--; ) { + PTPObjectInfo *obinfo; + + if (params->handles.Handler[i] != newobject) + continue; + obinfo = &camera->pl->params.objectinfo[i]; + strcpy (path->name, obinfo->Filename); + sprintf (path->folder,"/"STORAGE_FOLDER_PREFIX"%08lx/",(unsigned long)obinfo->StorageID); + get_folder_from_handle (camera, obinfo->StorageID, obinfo->ParentObject, path->folder); + /* delete last / or we get confused later. */ + path->folder[ strlen(path->folder)-1 ] = '\0'; + CR (gp_filesystem_append (camera->fs, path->folder, + path->name, context)); + break; + } + *eventtype = GP_EVENT_FILE_ADDED; + *eventdata = path; + break; + default: { + char *x; + + *eventtype = GP_EVENT_UNKNOWN; + x = malloc(strlen("PTP Event 0123, Param1 01234567")+1); + if (x) { + sprintf (x, "PTP Event %04x, Param1 %08x", event.Code, event.Param1); + *eventdata = x; + } + break; + } + } + return GP_OK; +} + +static void +_value_to_str(PTPPropertyValue *data, uint16_t dt, char *txt) { + if (dt == PTP_DTC_STR) { + sprintf (txt, "'%s'", data->str); + return; + } + if (dt & PTP_DTC_ARRAY_MASK) { + int i; + + sprintf (txt, "a[%d] ", data->a.count); + txt += strlen(txt); + for ( i=0; i<data->a.count; i++) { + _value_to_str(&data->a.v[i], dt & ~PTP_DTC_ARRAY_MASK, txt); + txt += strlen(txt); + if (i!=data->a.count-1) { + sprintf (txt, ","); + txt++; + } + } + } else { + switch (dt) { + case PTP_DTC_UNDEF: + sprintf (txt, "Undefined"); + break; + case PTP_DTC_INT8: + sprintf (txt, "%d", data->i8); + break; + case PTP_DTC_UINT8: + sprintf (txt, "%u", data->u8); + break; + case PTP_DTC_INT16: + sprintf (txt, "%d", data->i16); + break; + case PTP_DTC_UINT16: + sprintf (txt, "%u", data->u16); + break; + case PTP_DTC_INT32: + sprintf (txt, "%d", data->i32); + break; + case PTP_DTC_UINT32: + sprintf (txt, "%u", data->u32); + break; + /* + PTP_DTC_INT64 + PTP_DTC_UINT64 + PTP_DTC_INT128 + PTP_DTC_UINT128 + */ + default: + sprintf (txt, "Unknown %x", dt); + break; + } + } + return; +} + +static const char * +_get_getset(uint8_t gs) { + switch (gs) { + case PTP_DPGS_Get: return N_("read only"); + case PTP_DPGS_GetSet: return N_("readwrite"); + default: return N_("Unknown"); + } + return N_("Unknown"); +} + +#if 0 /* leave out ... is confusing -P downloads */ +#pragma pack(1) +struct canon_theme_entry { + uint16_t unknown1; + uint32_t offset; + uint32_t length; + uint8_t name[8]; + char unknown2[8]; +}; + +static int +canon_theme_get (CameraFilesystem *fs, const char *folder, const char *filename, + CameraFileType type, CameraFile *file, void *data, + GPContext *context) +{ + uint16_t res; + Camera *camera = (Camera*)data; + PTPParams *params = &camera->pl->params; + unsigned char *xdata; + unsigned int size; + int i; + struct canon_theme_entry *ent; + + ((PTPData *) camera->pl->params.data)->context = context; + + res = ptp_canon_get_customize_data (params, 1, &xdata, &size); + if (res != PTP_RC_OK) { + report_result(context, res, params->deviceinfo.VendorExtensionID); + return (translate_ptp_result(res)); + } + if (size < 42+sizeof(struct canon_theme_entry)*5) + return GP_ERROR_BAD_PARAMETERS; + ent = (struct canon_theme_entry*)(xdata+42); + for (i=0;i<5;i++) { + fprintf(stderr,"entry %d: unknown1 = %x\n", i, ent[i].unknown1); + fprintf(stderr,"entry %d: off = %d\n", i, ent[i].offset); + fprintf(stderr,"entry %d: len = %d\n", i, ent[i].length); + fprintf(stderr,"entry %d: name = %s\n", i, ent[i].name); + } + CR (gp_file_set_data_and_size (file, (char*)xdata, size)); + return (GP_OK); +} + +static int +canon_theme_put (CameraFilesystem *fs, const char *folder, CameraFile *file, + void *data, GPContext *context) +{ + /* not yet */ + return (GP_OK); +} +#endif + +static int +nikon_curve_get (CameraFilesystem *fs, const char *folder, const char *filename, + CameraFileType type, CameraFile *file, void *data, + GPContext *context) +{ + uint16_t res; + Camera *camera = (Camera*)data; + PTPParams *params = &camera->pl->params; + unsigned char *xdata; + unsigned int size; + int n; + PTPNIKONCurveData *tonecurve; + char *ntcfile; + char *charptr; + double *doubleptr; + ((PTPData *) camera->pl->params.data)->context = context; + + res = ptp_nikon_curve_download (params, &xdata, &size); + if (res != PTP_RC_OK) { + report_result(context, res, params->deviceinfo.VendorExtensionID); + return (translate_ptp_result(res)); + } + tonecurve = (PTPNIKONCurveData *) xdata; + ntcfile = malloc(2000); + memcpy(ntcfile,"\x9d\xdc\x7d\x00\x65\xd4\x11\xd1\x91\x94\x44\x45\x53\x54\x00\x00\xff\x05\xbb\x02\x00\x00\x01\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00\x9d\xdc\x7d\x03\x65\xd4\x11\xd1\x91\x94\x44\x45\x53\x54\x00\x00\x00\x00\x00\x00\xff\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00\xff\x00\x00\x00", 92); + doubleptr=(double *) &ntcfile[92]; + *doubleptr++ = (double) tonecurve->XAxisStartPoint/255; + *doubleptr++ = (double) tonecurve->XAxisEndPoint/255; + *doubleptr++ = (double) tonecurve->MidPointIntegerPart + + tonecurve->MidPointDecimalPart/100; + *doubleptr++ = (double) tonecurve->YAxisStartPoint/255; + *doubleptr++ = (double) tonecurve->YAxisEndPoint/255; + charptr=(char*) doubleptr; + *charptr++ = (char) tonecurve->NCoordinates; + memcpy(charptr, "\x00\x00\x00", 3); + charptr +=3; + doubleptr = (double *) charptr; + for(n=0;n<tonecurve->NCoordinates;n++) { + *doubleptr = (double) tonecurve->CurveCoordinates[n].X/255; + doubleptr = &doubleptr[1]; + *doubleptr = (double) tonecurve->CurveCoordinates[n].Y/255; + doubleptr = &doubleptr[1]; + } + *doubleptr++ = (double) 0; + charptr = (char*) doubleptr; + memcpy(charptr,"\x9d\xdc\x7d\x03\x65\xd4\x11\xd1\x91\x94\x44\x45\x53\x54\x00\x00\x01\x00\x00\x00\xff\x03\x00\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0\x3f\x00\x00\x00\x00\x00\x00\xf0\x3f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0\x3f\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0\x3f\x00\x00\x00\x00\x00\x00\xf0\x3f\x00\x00\x00\x00\x00\x00\x00\x00\x9d\xdc\x7d\x03\x65\xd4\x11\xd1\x91\x94\x44\x45\x53\x54\x00\x00\x02\x00\x00\x00\xff\x03\x00\x00\x00\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0\x3f\x00\x00\x00\x00\x00\x00\xf0\x3f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0\x3f\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0\x3f\x00\x00\x00\x00\x00\x00\xf0\x3f\x00\x00\x00\x00\x00\x00\x00\x00\x9d\xdc\x7d\x03\x65\xd4\x11\xd1\x91\x94\x44\x45\x53\x54\x00\x00\x03\x00\x00\x00\xff\x03\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0\x3f\x00\x00\x00\x00\x00\x00\xf0\x3f\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0\x3f\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xf0\x3f\x00\x00\x00\x00\x00\x00\xf0\x3f\x00\x00\x00\x00\x00\x00\x00\x00",429); + charptr += 429; + CR (gp_file_set_data_and_size (file, ntcfile, (long)charptr - (long)ntcfile)); + /* do not free ntcfile, it is managed by filesys now */ + free (xdata); + return (GP_OK); +} + +static int +nikon_curve_put (CameraFilesystem *fs, const char *folder, CameraFile *file, + void *data, GPContext *context) +{ + /* not yet */ + return (GP_OK); +} + +static int +camera_summary (Camera* camera, CameraText* summary, GPContext *context) +{ + int n, i, j; + int spaceleft; + char *txt; + PTPParams *params = &(camera->pl->params); + PTPDeviceInfo pdi; + PTPStorageIDs storageids; + + spaceleft = sizeof(summary->text); + n = snprintf (summary->text, sizeof (summary->text), + _("Model: %s\n" + " device version: %s\n" + " serial number: %s\n" + "Vendor extension ID: 0x%08x\n" + "Vendor extension description: %s\n" + ), + params->deviceinfo.Model, + params->deviceinfo.DeviceVersion, + params->deviceinfo.SerialNumber, + params->deviceinfo.VendorExtensionID, + params->deviceinfo.VendorExtensionDesc); + + if (n>=sizeof (summary->text)) + return GP_OK; + spaceleft -= n; + txt = summary->text + strlen (summary->text); + +/* Dump Formats */ + n = snprintf (txt, spaceleft,_("\nCapture Formats: ")); + if (n >= spaceleft) return GP_OK; spaceleft -= n; txt += n; + + for (i=0;i<params->deviceinfo.CaptureFormats_len;i++) { + n = ptp_render_ofc (params, params->deviceinfo.CaptureFormats[i], spaceleft, txt); + if (n >= spaceleft) return GP_OK; spaceleft -= n; txt += n; + if (i<params->deviceinfo.CaptureFormats_len-1) { + n = snprintf (txt, spaceleft," "); + if (n >= spaceleft) return GP_OK; spaceleft -= n; txt += n; + } + } + n = snprintf (txt, spaceleft,"\n"); + if (n >= spaceleft) return GP_OK; spaceleft -= n; txt += n; + + n = snprintf (txt, spaceleft,_("Display Formats: ")); + if (n >= spaceleft) return GP_OK; spaceleft -= n; txt += n; + for (i=0;i<params->deviceinfo.ImageFormats_len;i++) { + n = ptp_render_ofc (params, params->deviceinfo.ImageFormats[i], spaceleft, txt); + if (n >= spaceleft) return GP_OK; spaceleft -= n; txt += n; + if (i<params->deviceinfo.ImageFormats_len-1) { + n = snprintf (txt, spaceleft,", "); + if (n >= spaceleft) return GP_OK; spaceleft -= n; txt += n; + } + } + n = snprintf (txt, spaceleft,"\n"); + if (n >= spaceleft) return GP_OK; spaceleft -= n; txt += n; + + if ((params->deviceinfo.VendorExtensionID == PTP_VENDOR_MICROSOFT) && + ptp_operation_issupported(params,PTP_OC_MTP_GetObjectPropsSupported) + ) { + n = snprintf (txt, spaceleft,_("Supported MTP Object Properties:\n")); + if (n >= spaceleft) return GP_OK; spaceleft -= n; txt += n; + for (i=0;i<params->deviceinfo.ImageFormats_len;i++) { + uint16_t ret, *props = NULL; + uint32_t propcnt = 0; + int j; + + n = snprintf (txt, spaceleft,"\t"); + if (n >= spaceleft) return GP_OK; spaceleft -= n; txt += n; + n = ptp_render_ofc (params, params->deviceinfo.ImageFormats[i], spaceleft, txt); + if (n >= spaceleft) return GP_OK; spaceleft -= n; txt += n; + n = snprintf (txt, spaceleft,"/%04x:", params->deviceinfo.ImageFormats[i]); + if (n >= spaceleft) return GP_OK; spaceleft -= n; txt += n; + + ret = ptp_mtp_getobjectpropssupported (params, params->deviceinfo.ImageFormats[i], &propcnt, &props); + if (ret != PTP_RC_OK) { + n = snprintf (txt, spaceleft,_(" PTP error %04x on query"), ret); + if (n >= spaceleft) return GP_OK; spaceleft -= n; txt += n; + } else { + for (j=0;j<propcnt;j++) { + n = snprintf (txt, spaceleft," %04x/",props[j]); + if (n >= spaceleft) return GP_OK; spaceleft -= n; txt += n; + n = ptp_render_mtp_propname(props[j],spaceleft,txt); + if (n >= spaceleft) return GP_OK; spaceleft -= n; txt += n; + } + free(props); + } + n = snprintf (txt, spaceleft,"\n"); + if (n >= spaceleft) return GP_OK; spaceleft -= n; txt += n; + } + } + +/* Dump out dynamic capabilities */ + n = snprintf (txt, spaceleft,_("\nDevice Capabilities:\n")); + if (n >= spaceleft) return GP_OK; spaceleft -= n; txt += n; + + /* First line for file operations */ + n = snprintf (txt, spaceleft,_("\tFile Download, ")); + if (n >= spaceleft) return GP_OK; spaceleft -= n; txt += n; + if (ptp_operation_issupported(params,PTP_OC_DeleteObject)) + n = snprintf (txt, spaceleft,_("File Deletion, ")); + else + n = snprintf (txt, spaceleft,_("No File Deletion, ")); + if (n >= spaceleft) return GP_OK; spaceleft -= n; txt += n; + + if (ptp_operation_issupported(params,PTP_OC_SendObject)) + n = snprintf (txt, spaceleft,_("File Upload\n")); + else + n = snprintf (txt, spaceleft,_("No File Upload\n")); + if (n >= spaceleft) return GP_OK; spaceleft -= n; txt += n; + + /* Second line for capture */ + if (ptp_operation_issupported(params,PTP_OC_InitiateCapture)) + n = snprintf (txt, spaceleft,_("\tGeneric Image Capture, ")); + else + n = snprintf (txt, spaceleft,_("\tNo Image Capture, ")); + if (n >= spaceleft) return GP_OK; spaceleft -= n; txt += n; + if (ptp_operation_issupported(params,PTP_OC_InitiateOpenCapture)) + n = snprintf (txt, spaceleft,_("Open Capture, ")); + else + n = snprintf (txt, spaceleft,_("No Open Capture, ")); + if (n >= spaceleft) return GP_OK; spaceleft -= n; txt += n; + + n = 0; + if ((params->deviceinfo.VendorExtensionID == PTP_VENDOR_CANON) && + ptp_operation_issupported(&camera->pl->params, PTP_OC_CANON_ViewfinderOn)) { + n = snprintf (txt, spaceleft,_("Canon Capture\n")); + } else { + if ((params->deviceinfo.VendorExtensionID == PTP_VENDOR_NIKON) && + ptp_operation_issupported(&camera->pl->params, PTP_OC_NIKON_Capture)) { + n = snprintf (txt, spaceleft,_("Nikon Capture\n")); + } else { + n = snprintf (txt, spaceleft,_("No vendor specific capture\n")); + } + } + if (n >= spaceleft) return GP_OK; spaceleft -= n; txt += n; + + /* Third line for Wifi support, but just leave it out if not there. */ + if ((params->deviceinfo.VendorExtensionID == PTP_VENDOR_NIKON) && + ptp_operation_issupported(&camera->pl->params, PTP_OC_NIKON_GetProfileAllData)) { + n = snprintf (txt, spaceleft,_("\tNikon Wifi support\n")); + if (n >= spaceleft) return GP_OK; spaceleft -= n; txt += n; + } + + if ((params->deviceinfo.VendorExtensionID == PTP_VENDOR_CANON) && + ptp_operation_issupported(&camera->pl->params, PTP_OC_CANON_GetMACAddress)) { + n = snprintf (txt, spaceleft,_("\tCanon Wifi support\n")); + if (n >= spaceleft) return GP_OK; spaceleft -= n; txt += n; + } + +/* Dump storage information */ + + if (ptp_operation_issupported(params,PTP_OC_GetStorageIDs) && + ptp_operation_issupported(params,PTP_OC_GetStorageInfo) + ) { + CPR (context, ptp_getstorageids(params, + &storageids)); + n = snprintf (txt, spaceleft,_("\nStorage Devices Summary:\n")); + if (n >= spaceleft) return GP_OK; spaceleft -= n; txt += n; + + for (i=0; i<storageids.n; i++) { + char tmpname[20], *s; + + PTPStorageInfo storageinfo; + if ((storageids.Storage[i]&0x0000ffff)==0) + continue; + + n = snprintf (txt, spaceleft,"store_%08x:\n",(unsigned int)storageids.Storage[i]); + if (n>=spaceleft) return GP_OK;spaceleft-=n;txt+=n; + + CPR (context, ptp_getstorageinfo(params, + storageids.Storage[i], &storageinfo)); + n = snprintf (txt, spaceleft,_("\tStorageDescription: %s\n"), + storageinfo.StorageDescription?storageinfo.StorageDescription:_("None") + ); + if (n>=spaceleft) return GP_OK;spaceleft-=n;txt+=n; + n = snprintf (txt, spaceleft,_("\tVolumeLabel: %s\n"), + storageinfo.VolumeLabel?storageinfo.VolumeLabel:_("None") + ); + if (n>=spaceleft) return GP_OK;spaceleft-=n;txt+=n; + + switch (storageinfo.StorageType) { + case PTP_ST_Undefined: s = _("Undefined"); break; + case PTP_ST_FixedROM: s = _("Builtin ROM"); break; + case PTP_ST_RemovableROM: s = _("Removable ROM"); break; + case PTP_ST_FixedRAM: s = _("Builtin RAM"); break; + case PTP_ST_RemovableRAM: s = _("Removable RAM (memory card)"); break; + default: + snprintf(tmpname, sizeof(tmpname), _("Unknown: 0x%04x\n"), storageinfo.StorageType); + s = tmpname; + break; + } + n = snprintf (txt, spaceleft,_("\tStorage Type: %s\n"), s); + if (n>=spaceleft) return GP_OK;spaceleft-=n;txt+=n; + + switch (storageinfo.FilesystemType) { + case PTP_FST_Undefined: s = _("Undefined"); break; + case PTP_FST_GenericFlat: s = _("Generic Flat"); break; + case PTP_FST_GenericHierarchical: s = _("Generic Hierarchical"); break; + case PTP_FST_DCF: s = _("Digital Camera Layout (DCIM)"); break; + default: + snprintf(tmpname, sizeof(tmpname), _("Unknown: 0x%04x\n"), storageinfo.FilesystemType); + s = tmpname; + break; + } + n = snprintf (txt, spaceleft,_("\tFilesystemtype: %s\n"), s); + if (n>=spaceleft) return GP_OK;spaceleft-=n;txt+=n; + + switch (storageinfo.AccessCapability) { + case PTP_AC_ReadWrite: s = _("Read-Write"); break; + case PTP_AC_ReadOnly: s = _("Read-Only"); break; + case PTP_AC_ReadOnly_with_Object_Deletion: s = _("Read Only with Object deletion"); break; + default: + snprintf(tmpname, sizeof(tmpname), _("Unknown: 0x%04x\n"), storageinfo.AccessCapability); + s = tmpname; + break; + } + n = snprintf (txt, spaceleft,_("\tAccess Capability: %s\n"), s); + if (n>=spaceleft) return GP_OK;spaceleft-=n;txt+=n; + n = snprintf (txt, spaceleft,_("\tMaximum Capability: %llu (%lu MB)\n"), + (unsigned long long)storageinfo.MaxCapability, + (unsigned long)(storageinfo.MaxCapability/1024/1024) + ); + if (n>=spaceleft) return GP_OK;spaceleft-=n;txt+=n; + n = snprintf (txt, spaceleft,_("\tFree Space (Bytes): %llu (%lu MB)\n"), + (unsigned long long)storageinfo.FreeSpaceInBytes, + (unsigned long)(storageinfo.FreeSpaceInBytes/1024/1024) + ); + if (n>=spaceleft) return GP_OK;spaceleft-=n;txt+=n; + n = snprintf (txt, spaceleft,_("\tFree Space (Images): %d\n"), (unsigned int)storageinfo.FreeSpaceInImages); + if (n>=spaceleft) return GP_OK;spaceleft-=n;txt+=n; + } + } + + n = snprintf (txt, spaceleft,_("\nDevice Property Summary:\n")); + if (n>=spaceleft) return GP_OK;spaceleft-=n;txt+=n; + /* The information is cached. However, the canon firmware changes + * the available properties in capture mode. + */ + CPR(context, ptp_getdeviceinfo(&camera->pl->params, &pdi)); + + for (i=0;i<pdi.DevicePropertiesSupported_len;i++) { + PTPDevicePropDesc dpd; + unsigned int dpc = pdi.DevicePropertiesSupported[i]; + const char *propname = ptp_get_property_description (params, dpc); + + if (propname) { + /* string registered for i18n in ptp.c. */ + sprintf(txt, "%s(0x%04x):", _(propname), dpc); + } else { + sprintf(txt, "Property 0x%04x:", dpc); + } + txt = txt+strlen(txt); + + memset (&dpd, 0, sizeof (dpd)); + ptp_getdevicepropdesc (params, dpc, &dpd); + + sprintf (txt, "(%s) ",_get_getset(dpd.GetSet));txt += strlen (txt); + sprintf (txt, "(type=0x%x) ",dpd.DataType);txt += strlen (txt); + switch (dpd.FormFlag) { + case PTP_DPFF_None: break; + case PTP_DPFF_Range: { + sprintf (txt, "Range [");txt += strlen(txt); + _value_to_str (&dpd.FORM.Range.MinimumValue, dpd.DataType, txt); + txt += strlen(txt); + sprintf (txt, " - ");txt += strlen(txt); + _value_to_str (&dpd.FORM.Range.MaximumValue, dpd.DataType, txt); + txt += strlen(txt); + sprintf (txt, ", step ");txt += strlen(txt); + _value_to_str (&dpd.FORM.Range.StepSize, dpd.DataType, txt); + txt += strlen(txt); + sprintf (txt, "] value: ");txt += strlen(txt); + txt += strlen(txt); + break; + } + case PTP_DPFF_Enumeration: + sprintf (txt, "Enumeration [");txt += strlen(txt); + if ((dpd.DataType & PTP_DTC_ARRAY_MASK) == PTP_DTC_ARRAY_MASK) { + sprintf (txt, "\n\t");txt += strlen(txt); + } + for (j = 0; j<dpd.FORM.Enum.NumberOfValues; j++) { + _value_to_str(dpd.FORM.Enum.SupportedValue+j,dpd.DataType,txt); + txt += strlen(txt); + if (j != dpd.FORM.Enum.NumberOfValues-1) { + sprintf (txt, ",");txt += strlen(txt); + if ((dpd.DataType & PTP_DTC_ARRAY_MASK) == PTP_DTC_ARRAY_MASK) { + sprintf (txt, "\n\t");txt += strlen(txt); + } + } + } + if ((dpd.DataType & PTP_DTC_ARRAY_MASK) == PTP_DTC_ARRAY_MASK) { + sprintf (txt, "\n\t");txt += strlen(txt); + } + sprintf (txt, "] value: ");txt += strlen(txt); + break; + } + txt += strlen (txt); + ptp_render_property_value(params, dpc, &dpd, sizeof(summary->text) - strlen(summary->text) - 1, txt); + if (strlen(txt)) { + txt += strlen (txt); + sprintf(txt, " ("); + txt+=2; + _value_to_str (&dpd.CurrentValue, dpd.DataType, txt); + txt += strlen (txt); + sprintf(txt, ")"); + txt++; + } else { + _value_to_str (&dpd.CurrentValue, dpd.DataType, txt); + } + txt += strlen(txt); + sprintf(txt,"\n"); + txt += strlen(txt); + } + return (GP_OK); +} + +/* following functions are used for fs testing only */ +#if 0 +static void +add_dir (Camera *camera, uint32_t parent, uint32_t handle, const char *foldername) +{ + int n; + n=camera->pl->params.handles.n++; + camera->pl->params.objectinfo = (PTPObjectInfo*) + realloc(camera->pl->params.objectinfo, + sizeof(PTPObjectInfo)*(n+1)); + camera->pl->params.handles.handler[n]=handle; + + camera->pl->params.objectinfo[n].Filename=malloc(strlen(foldername)+1); + strcpy(camera->pl->params.objectinfo[n].Filename, foldername); + camera->pl->params.objectinfo[n].ObjectFormat=PTP_OFC_Association; + camera->pl->params.objectinfo[n].AssociationType=PTP_AT_GenericFolder; + + camera->pl->params.objectinfo[n].ParentObject=parent; +} +#endif + +#if 0 +static void +move_object_by_handle (Camera *camera, uint32_t parent, uint32_t handle) +{ + int n; + + for (n=0; n<camera->pl->params.handles.n; n++) + if (camera->pl->params.handles.handler[n]==handle) break; + if (n==camera->pl->params.handles.n) return; + camera->pl->params.objectinfo[n].ParentObject=parent; +} +#endif + +#if 0 +static void +move_object_by_number (Camera *camera, uint32_t parent, int n) +{ + if (n>=camera->pl->params.handles.n) return; + camera->pl->params.objectinfo[n].ParentObject=parent; +} +#endif + +static uint32_t +find_child (const char *file, uint32_t storage, uint32_t handle, Camera *camera) +{ + int i; + PTPObjectInfo *oi = camera->pl->params.objectinfo; + + for (i = 0; i < camera->pl->params.handles.n; i++) { + if ((oi[i].StorageID==storage) && (oi[i].ParentObject==handle)) + if (!strcmp(oi[i].Filename,file)) + return (camera->pl->params.handles.Handler[i]); + } + /* else not found */ + return (PTP_HANDLER_SPECIAL); +} + + +static uint32_t +folder_to_handle(const char *folder, uint32_t storage, uint32_t parent, Camera *camera) +{ + char *c; + if (!strlen(folder)) return PTP_HANDLER_ROOT; + if (!strcmp(folder,"/")) return PTP_HANDLER_ROOT; + + c=strchr(folder,'/'); + if (c!=NULL) { + *c=0; + parent=find_child (folder, storage, parent, camera); + return folder_to_handle(c+1, storage, parent, camera); + } else { + return find_child (folder, storage, parent, camera); + } +} + + +static int +file_list_func (CameraFilesystem *fs, const char *folder, CameraList *list, + void *data, GPContext *context) +{ + Camera *camera = (Camera *)data; + PTPParams *params = &camera->pl->params; + uint32_t parent, storage=0x0000000; + int i; + + /*((PTPData *)((Camera *)data)->pl->params.data)->context = context;*/ + gp_log (GP_LOG_DEBUG, "ptp2", "file_list_func(%s)", folder); + + /* There should be NO files in root folder */ + if (!strcmp(folder, "/")) + return (GP_OK); + + if (!strcmp(folder, "/special")) { + for (i=0; i<nrofspecial_files; i++) + CR (gp_list_append (list, special_files[i].name, NULL)); + return (GP_OK); + } + + /* compute storage ID value from folder patch */ + folder_to_storage(folder,storage); + + /* Get (parent) folder handle omiting storage pseudofolder */ + find_folder_handle(folder,storage,parent,data); + + for (i = 0; i < params->handles.n; i++) { + /* not our parent -> next */ + if (params->objectinfo[i].ParentObject!=parent) + continue; + + /* not on our storage devices -> next */ + if ((ptp_operation_issupported(params,PTP_OC_GetStorageIDs) + && (params->objectinfo[i].StorageID != storage))) + continue; + + /* Is a directory -> next */ + if (params->objectinfo[i].ObjectFormat == PTP_OFC_Association) + continue; + + if (!params->objectinfo[i].Filename) + continue; + + if (1 || CAN_HAVE_DUPE_FILE(camera->pl)) { + /* HP Photosmart 850, the camera tends to duplicate filename in the list. + * Original patch by clement.rezvoy@gmail.com */ + /* search backwards, likely gets hits faster. */ + if (GP_OK == gp_list_find_by_name(list, NULL, params->objectinfo[i].Filename)) { + gp_log (GP_LOG_ERROR, "ptp2/file_list_func", + "Duplicate filename '%s' in folder '%s'. Ignoring nth entry.\n", + params->objectinfo[i].Filename, folder); + continue; + } + } + CR(gp_list_append (list, params->objectinfo[i].Filename, NULL)); + } + return GP_OK; +} + +static int +folder_list_func (CameraFilesystem *fs, const char *folder, CameraList *list, + void *data, GPContext *context) +{ + PTPParams *params = &((Camera *)data)->pl->params; + int i; + uint32_t handler,storage; + + /*((PTPData *)((Camera *)data)->pl->params.data)->context = context;*/ + gp_log (GP_LOG_DEBUG, "ptp2", "folder_list_func(%s)", folder); + + /* add storage pseudofolders in root folder */ + if (!strcmp(folder, "/")) { + PTPStorageIDs storageids; + + if (ptp_operation_issupported(params,PTP_OC_GetStorageIDs)) { + CPR (context, ptp_getstorageids(params, + &storageids)); + } else { + storageids.n = 1; + storageids.Storage[0]=0xdeadbeef; + } + for (i=0; i<storageids.n; i++) { + char fname[PTP_MAXSTRLEN]; + + if ((storageids.Storage[i]&0x0000ffff)==0) continue; + snprintf(fname, strlen(STORAGE_FOLDER_PREFIX)+9, + STORAGE_FOLDER_PREFIX"%08x", + storageids.Storage[i]); + CR (gp_list_append (list, fname, NULL)); + } + + if (nrofspecial_files) + CR (gp_list_append (list, "special", NULL)); + + return (GP_OK); + } + + if (!strcmp(folder, "/special")) { + /* no folders in here */ + return (GP_OK); + } + + /* compute storage ID value from folder path */ + folder_to_storage(folder,storage); + + /* Get folder handle omiting storage pseudofolder */ + find_folder_handle(folder,storage,handler,data); + + /* Look for objects we can present as directories. + * Currently we specify *any* PTP association as directory. + */ + for (i = 0; i < params->handles.n; i++) { + if ( (params->objectinfo[i].ParentObject==handler) && + ((!ptp_operation_issupported(params,PTP_OC_GetStorageIDs)) || + (params->objectinfo[i].StorageID == storage) + ) && + (params->objectinfo[i].ObjectFormat==PTP_OFC_Association) + ) + CR (gp_list_append (list, params->objectinfo[i].Filename, NULL)); + } + return (GP_OK); +} + +/* To avoid roundtrips for querying prop desc + * that are uninteresting for us we list all + * that are exposed by PTP anyway (and are r/o). + */ +static unsigned short uninteresting_props [] = { + PTP_OPC_StorageID, + PTP_OPC_ObjectFormat, + PTP_OPC_ProtectionStatus, + PTP_OPC_ObjectSize, + PTP_OPC_AssociationType, + PTP_OPC_AssociationDesc, + PTP_OPC_ParentObject +}; + +static int +ptp_mtp_render_metadata ( + PTPParams *params, uint32_t object_id, uint16_t ofc, CameraFile *file +) { + uint16_t ret, *props = NULL; + uint32_t propcnt = 0; + int j; + + ret = ptp_mtp_getobjectpropssupported (params, ofc, &propcnt, &props); + if (ret != PTP_RC_OK) return (GP_ERROR); + + for (j=0;j<propcnt;j++) { + char propname[256]; + char text[256]; + PTPObjectPropDesc opd; + int i, n; + + for (i=sizeof(uninteresting_props)/sizeof(uninteresting_props[0]);i--;) + if (uninteresting_props[i] == props[j]) + break; + if (i != -1) /* Is uninteresting. */ + continue; + + n = ptp_render_mtp_propname(props[j], sizeof(propname), propname); + gp_file_append (file, "<", 1); + gp_file_append (file, propname, n); + gp_file_append (file, ">", 1); + + ret = ptp_mtp_getobjectpropdesc (params, props[j], ofc, &opd); + if (ret != PTP_RC_OK) { + fprintf (stderr," getobjectpropdesc returns 0x%x\n", ret); + } else { + PTPPropertyValue pv; + ret = ptp_mtp_getobjectpropvalue (params, object_id, props[j], &pv, opd.DataType); + if (ret != PTP_RC_OK) { + sprintf (text, "failure to retrieve %x of oid %x, ret %x", props[j], object_id, ret); + } else { + switch (opd.DataType) { + default:sprintf (text, "Unknown type %d", opd.DataType); + break; + case PTP_DTC_STR: + snprintf (text, sizeof(text), "%s", pv.str?pv.str:""); + break; + case PTP_DTC_INT32: + sprintf (text, "%d", pv.i32); + break; + case PTP_DTC_INT16: + sprintf (text, "%d", pv.i16); + break; + case PTP_DTC_INT8: + sprintf (text, "%d", pv.i8); + break; + case PTP_DTC_UINT32: + sprintf (text, "%u", pv.u32); + break; + case PTP_DTC_UINT16: + sprintf (text, "%u", pv.u16); + break; + case PTP_DTC_UINT8: + sprintf (text, "%u", pv.u8); + break; + } + } + gp_file_append (file, text, strlen(text)); + } + gp_file_append (file, "</", 2); + gp_file_append (file, propname, n); + gp_file_append (file, ">\n", 2); + + } + free(props); + return (GP_OK); +} + +/* To avoid roundtrips for querying prop desc if it is R/O + * we list all that are by standard means R/O. + */ +static unsigned short readonly_props [] = { + PTP_OPC_StorageID, + PTP_OPC_ObjectFormat, + PTP_OPC_ProtectionStatus, + PTP_OPC_ObjectSize, + PTP_OPC_AssociationType, + PTP_OPC_AssociationDesc, + PTP_OPC_ParentObject, + PTP_OPC_PersistantUniqueObjectIdentifier, + PTP_OPC_DateAdded, + PTP_OPC_CorruptOrUnplayable, + PTP_OPC_RepresentativeSampleFormat, + PTP_OPC_RepresentativeSampleSize, + PTP_OPC_RepresentativeSampleHeight, + PTP_OPC_RepresentativeSampleWidth, + PTP_OPC_RepresentativeSampleDuration +}; + +static int +ptp_mtp_parse_metadata ( + PTPParams *params, uint32_t object_id, uint16_t ofc, CameraFile *file +) { + uint16_t ret, *props = NULL; + uint32_t propcnt = 0; + char *filedata = NULL; + unsigned long filesize = 0; + int j; + + if (gp_file_get_data_and_size (file, (const char**)&filedata, &filesize) < GP_OK) + return (GP_ERROR); + + ret = ptp_mtp_getobjectpropssupported (params, ofc, &propcnt, &props); + if (ret != PTP_RC_OK) return (GP_ERROR); + + for (j=0;j<propcnt;j++) { + char propname[256],propname2[256]; + char *begin, *end, *content; + PTPObjectPropDesc opd; + int i, n; + PTPPropertyValue pv; + + for (i=sizeof(readonly_props)/sizeof(readonly_props[0]);i--;) + if (readonly_props[i] == props[j]) + break; + if (i != -1) /* Is read/only */ + continue; + n = ptp_render_mtp_propname(props[j], sizeof(propname), propname); + sprintf (propname2, "<%s>", propname); + begin= strstr (filedata, propname2); + if (!begin) continue; + begin += strlen(propname2); + sprintf (propname2, "</%s>", propname); + end = strstr (begin, propname2); + if (!end) continue; + *end = '\0'; + content = strdup(begin); + *end = '<'; + gp_log (GP_LOG_DEBUG, "ptp2", "found tag %s, content %s", propname, content); + ret = ptp_mtp_getobjectpropdesc (params, props[j], ofc, &opd); + if (ret != PTP_RC_OK) { + gp_log (GP_LOG_DEBUG, "ptp2", " getobjectpropdesc returns 0x%x", ret); + free (content); content = NULL; + continue; + } + if (opd.GetSet == 0) { + gp_log (GP_LOG_DEBUG, "ptp2", "Tag %s is read only, sorry.", propname); + continue; + } + switch (opd.DataType) { + default:gp_log (GP_LOG_ERROR, "ptp2", "mtp parser: Unknown datatype %d, content %s", opd.DataType, content); + free (content); content = NULL; + continue; + break; + case PTP_DTC_STR: + pv.str = content; + break; + case PTP_DTC_INT32: + sscanf (content, "%d", &pv.i32); + break; + case PTP_DTC_INT16: + sscanf (content, "%hd", &pv.i16); + break; + case PTP_DTC_INT8: + sscanf (content, "%hhd", &pv.i8); + break; + case PTP_DTC_UINT32: + sscanf (content, "%u", &pv.u32); + break; + case PTP_DTC_UINT16: + sscanf (content, "%hu", &pv.u16); + break; + case PTP_DTC_UINT8: + sscanf (content, "%hhu", &pv.u8); + break; + } + ret = ptp_mtp_setobjectpropvalue (params, object_id, props[j], &pv, opd.DataType); + free (content); content = NULL; + } + free(props); + return (GP_OK); +} + +static int +mtp_get_playlist_string( + Camera *camera, uint32_t object_id, char **xcontent, int *xcontentlen +) { + PTPParams *params = &camera->pl->params; + uint32_t numobjects = 0, *objects = NULL; + uint16_t ret; + int i, contentlen = 0; + char *content = NULL; + + ret = ptp_mtp_getobjectreferences (params, object_id, &objects, &numobjects); + if (ret != PTP_RC_OK) + return (translate_ptp_result(ret)); + + for (i=0;i<numobjects;i++) { + char buf[4096]; + int len; + + memset(buf, 0, sizeof(buf)); + len = 0; + object_id = objects[i]; + do { + int j = handle_to_n(object_id, camera); + if (j == PTP_HANDLER_SPECIAL) + break; + /* make space for new filename */ + memmove (buf+strlen(params->objectinfo[j].Filename)+1, buf, len); + memcpy (buf+1, params->objectinfo[j].Filename, strlen (params->objectinfo[j].Filename)); + buf[0] = '/'; + object_id = params->objectinfo[j].ParentObject; + len = strlen(buf); + } while (object_id != 0); + memmove (buf+strlen("/store_00010001"), buf, len); + sprintf (buf,"/store_%08x",(unsigned int)params->objectinfo[handle_to_n(objects[i],camera)].StorageID); + buf[strlen(buf)]='/'; + len = strlen(buf); + + if (content) { + content = realloc (content, contentlen+len+1+1); + strcpy (content+contentlen, buf); + strcpy (content+contentlen+len, "\n"); + contentlen += len+1; + } else { + content = malloc (len+1+1); + strcpy (content, buf); + strcpy (content+len, "\n"); + contentlen = len+1; + } + } + if (!content) content = malloc(1); + if (xcontent) + *xcontent = content; + else + free (content); + *xcontentlen = contentlen; + return (GP_OK); +} + +static int +mtp_put_playlist( + Camera *camera, char *content, int contentlen, PTPObjectInfo *oi, GPContext *context +) { + char *s = content; + unsigned char data[1]; + uint32_t storage, objectid, playlistid; + uint32_t *oids = NULL; + int nrofoids = 0; + uint16_t ret; + + while (*s) { + char *t = strchr(s,'\n'); + char *fn, *filename; + if (t) { + fn = malloc (t-s+1); + if (!fn) return GP_ERROR_NO_MEMORY; + memcpy (fn, s, t-s); + fn[t-s]='\0'; + } else { + fn = malloc (strlen(s)+1); + if (!fn) return GP_ERROR_NO_MEMORY; + strcpy (fn,s); + } + filename = strrchr (fn,'/'); + if (!filename) { + free (fn); + if (!t) break; + s = t+1; + continue; + } + *filename = '\0';filename++; + + /* compute storage ID value from folder patch */ + folder_to_storage(fn,storage); + /* Get file number omiting storage pseudofolder */ + find_folder_handle(fn, storage, objectid, camera); + objectid = find_child(filename, storage, objectid, camera); + if (objectid != PTP_HANDLER_SPECIAL) { + if (nrofoids) { + oids = realloc(oids, sizeof(oids[0])*(nrofoids+1)); + if (!oids) return GP_ERROR_NO_MEMORY; + } else { + oids = malloc(sizeof(oids[0])); + if (!oids) return GP_ERROR_NO_MEMORY; + } + oids[nrofoids] = objectid; + nrofoids++; + } else { + /*fprintf (stderr,"%s/%s NOT FOUND!\n", fn, filename);*/ + gp_log (GP_LOG_ERROR , "mtp playlist upload", "Object %s/%s not found on device.", fn, filename); + } + free (fn); + if (!t) break; + s = t+1; + } + oi->ObjectCompressedSize = 1; + oi->ObjectFormat = PTP_OFC_MTP_AbstractAudioVideoPlaylist; + ret = ptp_sendobjectinfo(&camera->pl->params, &storage, &oi->ParentObject, &playlistid, oi); + if (ret != PTP_RC_OK) { + gp_log (GP_LOG_ERROR, "put mtp playlist", "failed sendobjectinfo of playlist."); + return GP_ERROR; + } + ret = ptp_sendobject(&camera->pl->params, (unsigned char*)data, 1); + if (ret != PTP_RC_OK) { + gp_log (GP_LOG_ERROR, "put mtp playlist", "failed dummy sendobject of playlist."); + return GP_ERROR; + } + ret = ptp_mtp_setobjectreferences (&camera->pl->params, playlistid, oids, nrofoids); + if (ret != PTP_RC_OK) { + gp_log (GP_LOG_ERROR, "put mtp playlist", "failed setobjectreferences."); + return GP_ERROR; + } + /* update internal structures */ + add_object(camera, playlistid, context); + return GP_OK; +} + +static int +mtp_get_playlist( + Camera *camera, CameraFile *file, uint32_t object_id, GPContext *context +) { + char *content; + int ret, contentlen; + + ret = mtp_get_playlist_string( camera, object_id, &content, &contentlen); + if (ret != GP_OK) return ret; + /* takes ownership of content */ + gp_file_set_data_and_size (file, content, contentlen); + return (GP_OK); +} + +static int +get_file_func (CameraFilesystem *fs, const char *folder, const char *filename, + CameraFileType type, CameraFile *file, void *data, + GPContext *context) +{ + Camera *camera = data; + /* Note that "image" points to unsigned chars whereas all the other + * functions which set image return pointers to chars. + * However, we calculate a number of unsigned values in this function, + * so we cannot make it signed either. + * Therefore, sometimes a "ximage" char* helper, since wild casts of pointers + * confuse the compilers aliasing mechanisms. + * If you do not like that, feel free to clean up the datatypes. + * (TODO for Marcus and 2.2 ;) + */ + unsigned char * image=NULL; + uint32_t object_id; + uint32_t size; + uint32_t storage; + PTPObjectInfo * oi; + PTPParams *params = &camera->pl->params; + + ((PTPData *) params->data)->context = context; + + if (!strcmp (folder, "/special")) { + int i; + + for (i=0;i<nrofspecial_files;i++) + if (!strcmp (special_files[i].name, filename)) + return special_files[i].getfunc (fs, folder, filename, type, file, data, context); + return (GP_ERROR_BAD_PARAMETERS); /* file not found */ + } + + /* compute storage ID value from folder patch */ + folder_to_storage(folder,storage); + + /* Get file number omiting storage pseudofolder */ + find_folder_handle(folder, storage, object_id, data); + object_id = find_child(filename, storage, object_id, camera); + if ((object_id=handle_to_n(object_id, camera))==PTP_HANDLER_SPECIAL) + return (GP_ERROR_BAD_PARAMETERS); + + oi=¶ms->objectinfo[object_id]; + + GP_DEBUG ("Getting file."); + switch (type) { + + case GP_FILE_TYPE_EXIF: { + uint32_t offset; + uint32_t maxbytes; + unsigned char *ximage = NULL; + + /* Check if we have partial downloads. Otherwise we can just hope + * upstream downloads the whole image to get EXIF data. */ + if (!ptp_operation_issupported(params, PTP_OC_GetPartialObject)) + return (GP_ERROR_NOT_SUPPORTED); + /* Device may hang is a partial read is attempted beyond the file */ + if (oi->ObjectCompressedSize < 10) + return (GP_ERROR_NOT_SUPPORTED); + + /* We only support JPEG / EXIF format ... others might hang. */ + if (oi->ObjectFormat != PTP_OFC_EXIF_JPEG) + return (GP_ERROR_NOT_SUPPORTED); + + /* Note: Could also use Canon partial downloads */ + CPR (context, ptp_getpartialobject (params, + params->handles.Handler[object_id], + 0, 10, &ximage)); + + if (!((ximage[0] == 0xff) && (ximage[1] == 0xd8))) { /* SOI */ + free (image); + return (GP_ERROR_NOT_SUPPORTED); + } + if (!((ximage[2] == 0xff) && (ximage[3] == 0xe1))) { /* App0 */ + free (image); + return (GP_ERROR_NOT_SUPPORTED); + } + if (0 != memcmp(ximage+6, "Exif", 4)) { + free (ximage); + return (GP_ERROR_NOT_SUPPORTED); + } + offset = 2; + maxbytes = (ximage[4] << 8 ) + ximage[5]; + free (ximage); + ximage = NULL; + CPR (context, ptp_getpartialobject (params, + params->handles.Handler[object_id], + offset, maxbytes, &ximage)); + CR (gp_file_set_data_and_size (file, (char*)ximage, maxbytes)); + break; + } + case GP_FILE_TYPE_PREVIEW: { + unsigned char *ximage = NULL; + + /* If thumb size is 0 then there is no thumbnail at all... */ + if((size=oi->ThumbCompressedSize)==0) return (GP_ERROR_NOT_SUPPORTED); + CPR (context, ptp_getthumb(params, + params->handles.Handler[object_id], + &ximage)); + CR (gp_file_set_data_and_size (file, (char*)ximage, size)); + /* XXX does gp_file_set_data_and_size free() image ptr upon + failure?? */ + break; + } + case GP_FILE_TYPE_METADATA: + if ((params->deviceinfo.VendorExtensionID == PTP_VENDOR_MICROSOFT) && + ptp_operation_issupported(params,PTP_OC_MTP_GetObjectPropsSupported) + ) + return ptp_mtp_render_metadata (params,params->handles.Handler[object_id],oi->ObjectFormat,file); + return (GP_ERROR_NOT_SUPPORTED); + default: { + unsigned char *ximage = NULL; + + /* we do not allow downloading unknown type files as in most + cases they are special file (like firmware or control) which + sometimes _cannot_ be downloaded. doing so we avoid errors.*/ + if (oi->ObjectFormat == PTP_OFC_Association || + (oi->ObjectFormat == PTP_OFC_Undefined && + oi->ThumbFormat == PTP_OFC_Undefined)) + return (GP_ERROR_NOT_SUPPORTED); + + if ((params->deviceinfo.VendorExtensionID == PTP_VENDOR_MICROSOFT) && + (oi->ObjectFormat == PTP_OFC_MTP_AbstractAudioVideoPlaylist)) + return mtp_get_playlist (camera, file, params->handles.Handler[object_id], context); + + size=oi->ObjectCompressedSize; + if (size) { + CPR (context, ptp_getobject(params, + params->handles.Handler[object_id], + &ximage)); + } else { + /* Do not download 0 sized files. + * It is not necessary and even breaks for some camera special files. + */ + ximage = malloc(1); + } + CR (gp_file_set_data_and_size (file, (char*)ximage, size)); + /* XXX does gp_file_set_data_and_size free() image ptr upon + failure?? */ + + /* clear the "new" flag on Canons */ + if ( (params->deviceinfo.VendorExtensionID == PTP_VENDOR_CANON) && + (params->canon_flags) && + (params->canon_flags[object_id] & 0x2000) && + ptp_operation_issupported(params,PTP_OC_CANON_SetObjectArchive) + ) { + /* seems just a byte (0x20 - new) */ + ptp_canon_setobjectarchive (params, params->handles.Handler[object_id], (params->canon_flags[object_id] &~0x2000)>>8); + params->canon_flags[object_id] &= ~0x2000; + } + break; + } + } + CR (set_mimetype (camera, file, oi->ObjectFormat)); + + return (GP_OK); +} + +static int +put_file_func (CameraFilesystem *fs, const char *folder, CameraFile *file, + void *data, GPContext *context) +{ + Camera *camera = data; + PTPObjectInfo oi; + const char *filename; + char *object; + uint32_t parent; + uint32_t storage; + uint32_t handle; + unsigned long intsize; + uint32_t size; + PTPParams* params=&camera->pl->params; + CameraFileType type; + + ((PTPData *) camera->pl->params.data)->context = context; + + if (!strcmp (folder, "/special")) { + int i; + + for (i=0;i<nrofspecial_files;i++) + if (!strcmp (special_files[i].name, filename)) + return special_files[i].putfunc (fs, folder, file, data, context); + return (GP_ERROR_BAD_PARAMETERS); /* file not found */ + } + + memset(&oi, 0, sizeof (PTPObjectInfo)); + gp_file_get_name (file, &filename); + gp_file_get_type (file, &type); + + if (type == GP_FILE_TYPE_METADATA) { + if ((params->deviceinfo.VendorExtensionID==PTP_VENDOR_MICROSOFT) && + ptp_operation_issupported(params,PTP_OC_MTP_GetObjectPropsSupported) + ) { + uint32_t object_id; + int n; + PTPObjectInfo *poi; + + /* compute storage ID value from folder patch */ + folder_to_storage(folder,storage); + + /* Get file number omiting storage pseudofolder */ + find_folder_handle(folder, storage, object_id, data); + object_id = find_child(filename, storage, object_id, camera); + if (object_id ==PTP_HANDLER_SPECIAL) { + gp_context_error (context, _("File '%s/%s' does not exist."), folder, filename); + return (GP_ERROR_BAD_PARAMETERS); + } + if ((n = handle_to_n(object_id, camera))==PTP_HANDLER_SPECIAL) { + gp_context_error (context, _("File '%s/%s' does not exist."), folder, filename); + return (GP_ERROR_BAD_PARAMETERS); + } + poi=¶ms->objectinfo[n]; + return ptp_mtp_parse_metadata (params,object_id,poi->ObjectFormat,file); + } + gp_context_error (context, _("Metadata only supported for MTP devices.")); + return GP_ERROR; + } + + gp_file_get_data_and_size (file, (const char **)&object, &intsize); + size=(uint32_t)intsize; + + /* compute storage ID value from folder patch */ + folder_to_storage(folder,storage); + + /* get parent folder id omiting storage pseudofolder */ + find_folder_handle(folder,storage,parent,data); + + /* if you desire to put file to root folder, you have to use + * 0xffffffff instead of 0x00000000 (which means responder decide). + */ + if (parent==PTP_HANDLER_ROOT) parent=PTP_HANDLER_SPECIAL; + + /* We don't really want a file to exist with the same name twice. */ + handle = folder_to_handle (filename, storage, parent, camera); + if (handle != PTP_HANDLER_SPECIAL) + return GP_ERROR_FILE_EXISTS; + + oi.Filename=(char *)filename; + oi.ObjectFormat = get_mimetype(camera, file); + oi.ObjectCompressedSize = size; + oi.ParentObject = parent; + gp_file_get_mtime(file, &oi.ModificationDate); + + if ((params->deviceinfo.VendorExtensionID == PTP_VENDOR_MICROSOFT) && + ( strstr(filename,".zpl") || strstr(filename, ".pla") )) + return mtp_put_playlist (camera, object, intsize, &oi, context); + + /* if the device is usign PTP_VENDOR_EASTMAN_KODAK extension try + * PTP_OC_EK_SendFileObject + */ + if ((params->deviceinfo.VendorExtensionID==PTP_VENDOR_EASTMAN_KODAK) && + (ptp_operation_issupported(params, PTP_OC_EK_SendFileObject))) + { + CPR (context, ptp_ek_sendfileobjectinfo (params, &storage, + &parent, &handle, &oi)); + CPR (context, ptp_ek_sendfileobject (params, (unsigned char*)object, size)); + } else if (ptp_operation_issupported(params, PTP_OC_SendObjectInfo)) { + CPR (context, ptp_sendobjectinfo (params, &storage, + &parent, &handle, &oi)); + CPR (context, ptp_sendobject (params, (unsigned char*)object, size)); + } else { + GP_DEBUG ("The device does not support uploading files!"); + return GP_ERROR_NOT_SUPPORTED; + } + /* update internal structures */ + add_object(camera, handle, context); + return (GP_OK); +} + +static int +delete_file_func (CameraFilesystem *fs, const char *folder, + const char *filename, void *data, GPContext *context) +{ + Camera *camera = data; + unsigned long object_id; + uint32_t storage; + PTPParams *params = &camera->pl->params; + + ((PTPData *) params->data)->context = context; + + if (!ptp_operation_issupported(params, PTP_OC_DeleteObject)) + return GP_ERROR_NOT_SUPPORTED; + + if (!strcmp (folder, "/special")) + return GP_ERROR_NOT_SUPPORTED; + + /* virtual file created by Nikon special capture */ + if ( ((params->deviceinfo.VendorExtensionID == PTP_VENDOR_NIKON) || + (params->deviceinfo.VendorExtensionID == PTP_VENDOR_CANON) ) && + !strncmp (filename, "capt", 4) + ) + return GP_OK; + + /* compute storage ID value from folder patch */ + folder_to_storage(folder,storage); + + /* Get file number omiting storage pseudofolder */ + find_folder_handle(folder, storage, object_id, data); + object_id = find_child(filename, storage, object_id, camera); + if ((object_id=handle_to_n(object_id, camera))==PTP_HANDLER_SPECIAL) + return (GP_ERROR_BAD_PARAMETERS); + + CPR (context, ptp_deleteobject(params, + params->handles.Handler[object_id],0)); + + /* On some Canon firmwares, a DeleteObject causes a ObjectRemoved event + * to be sent. At least on Digital IXUS II and PowerShot A85. But + * not on 350D. + */ + if (DELETE_SENDS_EVENT(camera->pl) && + ptp_event_issupported(params, PTP_EC_ObjectRemoved)) { + PTPContainer event; + int ret; + + do { + ret = params->event_check (params, &event); + if ( (ret == PTP_RC_OK) && + (event.Code == PTP_EC_ObjectRemoved) + ) + break; + } while (ret == PTP_RC_OK); + } + return (GP_OK); +} + +static int +remove_dir_func (CameraFilesystem *fs, const char *folder, + const char *foldername, void *data, GPContext *context) +{ + Camera *camera = data; + unsigned long object_id; + uint32_t storage; + PTPParams *params = &camera->pl->params; + + ((PTPData *) params->data)->context = context; + + if (!ptp_operation_issupported(params, PTP_OC_DeleteObject)) + return GP_ERROR_NOT_SUPPORTED; + + /* compute storage ID value from folder patch */ + folder_to_storage(folder,storage); + + /* Get file number omiting storage pseudofolder */ + find_folder_handle(folder, storage, object_id, data); + object_id = find_child(foldername, storage, object_id, camera); + if ((object_id=handle_to_n(object_id, camera))==PTP_HANDLER_SPECIAL) + return (GP_ERROR_BAD_PARAMETERS); + + CPR (context, ptp_deleteobject(params, params->handles.Handler[object_id],0)); + return (GP_OK); +} + +static int +get_info_func (CameraFilesystem *fs, const char *folder, const char *filename, + CameraFileInfo *info, void *data, GPContext *context) +{ + Camera *camera = data; + PTPObjectInfo *oi; + uint32_t object_id; + uint32_t storage; + PTPParams *params = &camera->pl->params; + + ((PTPData *) params->data)->context = context; + + if (!strcmp (folder, "/special")) + return (GP_ERROR_BAD_PARAMETERS); /* for now */ + + /* compute storage ID value from folder patch */ + folder_to_storage(folder,storage); + + /* Get file number omiting storage pseudofolder */ + find_folder_handle(folder, storage, object_id, data); + object_id = find_child(filename, storage, object_id, camera); + if ((object_id=handle_to_n(object_id, camera))==PTP_HANDLER_SPECIAL) + return (GP_ERROR_BAD_PARAMETERS); + + oi=¶ms->objectinfo[object_id]; + + info->file.fields = GP_FILE_INFO_SIZE|GP_FILE_INFO_TYPE|GP_FILE_INFO_MTIME; + + /* Avoid buffer overflows on long filenames, just don't copy it + * if it is too long. + */ + if (oi->Filename && (strlen(oi->Filename)+1 < sizeof(info->file.name))) { + strcpy(info->file.name, oi->Filename); + info->file.fields |= GP_FILE_INFO_NAME; + } + info->file.size = oi->ObjectCompressedSize; + + if ((params->deviceinfo.VendorExtensionID == PTP_VENDOR_CANON) && params->canon_flags) { + info->file.fields |= GP_FILE_INFO_STATUS; + if (params->canon_flags[object_id] & 0x2000) + info->file.status = GP_FILE_STATUS_NOT_DOWNLOADED; + else + info->file.status = GP_FILE_STATUS_DOWNLOADED; + } + + /* MTP playlists have their own size calculation */ + if ((params->deviceinfo.VendorExtensionID == PTP_VENDOR_MICROSOFT) && + (oi->ObjectFormat == PTP_OFC_MTP_AbstractAudioVideoPlaylist)) { + int ret, contentlen; + ret = mtp_get_playlist_string (camera, params->handles.Handler[object_id], NULL, &contentlen); + if (ret != GP_OK) return ret; + info->file.size = contentlen; + } + + strcpy_mime (info->file.type, oi->ObjectFormat); + if (oi->ModificationDate != 0) { + info->file.mtime = oi->ModificationDate; + } else { + info->file.mtime = oi->CaptureDate; + } + + /* if object is an image */ + if ((oi->ObjectFormat & 0x0800) != 0) { + info->preview.fields = 0; + strcpy_mime(info->preview.type, oi->ThumbFormat); + if (strlen(info->preview.type)) { + info->preview.fields |= GP_FILE_INFO_TYPE; + } + if (oi->ThumbCompressedSize) { + info->preview.size = oi->ThumbCompressedSize; + info->preview.fields |= GP_FILE_INFO_SIZE; + } + if (oi->ThumbPixWidth) { + info->preview.width = oi->ThumbPixWidth; + info->preview.fields |= GP_FILE_INFO_WIDTH; + } + if (oi->ThumbPixHeight) { + info->preview.height = oi->ThumbPixHeight; + info->preview.fields |= GP_FILE_INFO_HEIGHT; + } + if (oi->ImagePixWidth) { + info->file.width = oi->ImagePixWidth; + info->file.fields |= GP_FILE_INFO_WIDTH; + } + if (oi->ImagePixHeight) { + info->file.height = oi->ImagePixHeight; + info->file.fields |= GP_FILE_INFO_HEIGHT; + } + } + return (GP_OK); +} + +static int +make_dir_func (CameraFilesystem *fs, const char *folder, const char *foldername, + void *data, GPContext *context) +{ + Camera *camera = data; + PTPObjectInfo oi; + uint32_t parent; + uint32_t storage; + uint32_t handle; + PTPParams* params=&camera->pl->params; + + if (!strcmp (folder, "/special")) + return GP_ERROR_NOT_SUPPORTED; + + ((PTPData *) camera->pl->params.data)->context = context; + memset(&oi, 0, sizeof (PTPObjectInfo)); + + /* compute storage ID value from folder patch */ + folder_to_storage(folder,storage); + + /* get parent folder id omiting storage pseudofolder */ + find_folder_handle(folder,storage,parent,data); + + /* if you desire to make dir in 'root' folder, you have to use + * 0xffffffff instead of 0x00000000 (which means responder decide). + */ + if (parent==PTP_HANDLER_ROOT) parent=PTP_HANDLER_SPECIAL; + + handle = folder_to_handle (foldername, storage, parent, camera); + if (handle != PTP_HANDLER_SPECIAL) { + return GP_ERROR_DIRECTORY_EXISTS; + } + + oi.Filename=(char *)foldername; + + oi.ObjectFormat=PTP_OFC_Association; + oi.ProtectionStatus=PTP_PS_NoProtection; + oi.AssociationType=PTP_AT_GenericFolder; + + if ((params->deviceinfo.VendorExtensionID== + PTP_VENDOR_EASTMAN_KODAK) && + (ptp_operation_issupported(params, + PTP_OC_EK_SendFileObjectInfo))) + { + CPR (context, ptp_ek_sendfileobjectinfo (params, &storage, + &parent, &handle, &oi)); + } else if (ptp_operation_issupported(params, PTP_OC_SendObjectInfo)) { + CPR (context, ptp_sendobjectinfo (params, &storage, + &parent, &handle, &oi)); + } else { + GP_DEBUG ("The device does not support make folder!"); + return GP_ERROR_NOT_SUPPORTED; + } + /* update internal structures */ + add_object(camera, handle, context); + return (GP_OK); +} + +static int +init_ptp_fs (Camera *camera, GPContext *context) +{ + int i,id,nroot; + PTPParams *params = &camera->pl->params; + char buf[1024]; + uint16_t ret; + + ((PTPData *) params->data)->context = context; + memset (¶ms->handles, 0, sizeof(PTPObjectHandles)); + + /* Nikon supports a fast filesystem retrieval. + * Unfortunately this function returns a flat folder structure + * which cannot be changed to represent the actual FAT layout. + * So if you need to get access to _all_ files on the ptp fs, + * you can change the setting to "false" (gphoto2 --config or + * edit ~/.gphoto2/settings directly). + * A normal user does only download the images ... so the default + * is "fast". + */ + + if ((params->deviceinfo.VendorExtensionID == PTP_VENDOR_NIKON) && + (ptp_operation_issupported(params, PTP_OC_NIKON_GetFileInfoInBlock)) && + ((GP_OK != gp_setting_get("ptp2","nikon.fastfilesystem",buf)) || atoi(buf)) + ) + { + unsigned char *data,*curptr; + unsigned int size; + int i,guessedcnt,curhandle; + uint32_t generatedoid = 0x42420000; + uint32_t rootoid = generatedoid++; + int roothandle = -1; + uint16_t res; + PTPStorageIDs ids; + + /* To get the correct storage id for all the objects */ + res = ptp_getstorageids (params, &ids); + if (res != PTP_RC_OK) goto fallback; + if (ids.n != 1) { /* can't cope with this currently */ + gp_log (GP_LOG_DEBUG, "ptp", "more than 1 storage id present"); + free(ids.Storage); + goto fallback; + } + res = ptp_nikon_getfileinfoinblock(params, 1, 0xffffffff, 0xffffffff, &data, &size); + if (res != PTP_RC_OK) { + gp_log (GP_LOG_DEBUG, "ptp", "getfileinfoblock failed"); + free(ids.Storage); + goto fallback; + } + curptr = data; + if (*curptr != 0x01) { /* version of data format */ + gp_log (GP_LOG_DEBUG, "ptp", "version is 0x%02x, expected 0x01", *curptr); + free(ids.Storage); + free(data); + goto fallback; + } + guessedcnt = size/8; /* wild guess ... 4 byte type, at least 2 chars name, 2 more bytes */ + params->handles.Handler = malloc(sizeof(params->handles.Handler[0])*guessedcnt); + memset(params->handles.Handler,0,sizeof(params->handles.Handler[0])*guessedcnt); + params->objectinfo = (PTPObjectInfo*)malloc(sizeof(PTPObjectInfo)*guessedcnt); + memset(params->objectinfo,0,sizeof(PTPObjectInfo)*guessedcnt); + curhandle=0; + curptr++; + + /* This ptp command does not get a ready made directory structure, it + * gets a list of folders (flat) and its image related file contents. + * It does not get AUTPRNT.MRK for instance... + * It is however very fast since it is just one ptp command roundtrip. + */ + while (curptr-data < size) { /* loops over folders */ + int numents, namelen, dirhandle; + uint32_t diroid = generatedoid++; + + namelen = curptr[0]+(curptr[1]<<8)+(curptr[2]<<16)+(curptr[3]<<24); + curptr+=4; + if (!strcmp((char*)curptr,"DCIM")) { + /* to generated the /DCIM/NNNABCDEF/ structure, handle /DCIM/ + * differently */ + diroid = rootoid; + roothandle = curhandle; + params->handles.Handler[curhandle] = rootoid; + params->objectinfo[curhandle].ParentObject = 0; + } else { + if (roothandle == -1) { /* We must synthesize /DCIM... */ + roothandle = curhandle; + params->handles.Handler[curhandle] = rootoid; + params->objectinfo[curhandle].ParentObject = 0; + params->objectinfo[curhandle].StorageID = ids.Storage[0]; + params->objectinfo[curhandle].Filename = strdup("DCIM"); + params->objectinfo[curhandle].ObjectFormat = PTP_OFC_Association; + params->objectinfo[curhandle].AssociationType = PTP_AT_GenericFolder; + curhandle++; + } + params->handles.Handler[curhandle] = diroid; + params->objectinfo[curhandle].ParentObject = rootoid; + } + params->objectinfo[curhandle].ObjectFormat = PTP_OFC_Association; + params->objectinfo[curhandle].AssociationType = PTP_AT_GenericFolder; + params->objectinfo[curhandle].StorageID = ids.Storage[0]; + params->objectinfo[curhandle].Filename = strdup((char*)curptr); + + while (*curptr) curptr++; curptr++; + numents = curptr[0]+(curptr[1]<<8); curptr+=2; + dirhandle = curhandle; + curhandle++; + for (i=0;i<numents;i++) { + uint32_t oid, size, xtime; + + oid = curptr[0]+(curptr[1]<<8)+(curptr[2]<<16)+(curptr[3]<<24); + curptr += 4; + namelen = curptr[0]+(curptr[1]<<8)+(curptr[2]<<16)+(curptr[3]<<24); + curptr += 4; + params->handles.Handler[curhandle] = oid; + params->objectinfo[curhandle].StorageID = ids.Storage[0]; + params->objectinfo[curhandle].Filename = strdup((char*)curptr); + params->objectinfo[curhandle].ObjectFormat = PTP_OFC_Undefined; + if (NULL!=strstr((char*)curptr,".JPG")) + params->objectinfo[curhandle].ObjectFormat = PTP_OFC_EXIF_JPEG; + if (NULL!=strstr((char*)curptr,".MOV")) + params->objectinfo[curhandle].ObjectFormat = PTP_OFC_QT; + if (NULL!=strstr((char*)curptr,".AVI")) + params->objectinfo[curhandle].ObjectFormat = PTP_OFC_AVI; + if (NULL!=strstr((char*)curptr,".WAV")) + params->objectinfo[curhandle].ObjectFormat = PTP_OFC_WAV; + while (*curptr) curptr++; curptr++; + size = curptr[0]+(curptr[1]<<8)+(curptr[2]<<16)+(curptr[3]<<24); + params->objectinfo[curhandle].ObjectCompressedSize = size; + curptr += 4; + xtime = curptr[0]+(curptr[1]<<8)+(curptr[2]<<16)+(curptr[3]<<24); + if (xtime > 0x12cea600) /* Unknown files are 1.1.1980 */ + params->objectinfo[curhandle].CaptureDate = xtime; + curptr += 4; + /* Hack ... to find our directory oid, we just getobjectinfo + * the first file object. + */ + if (0 && !i) { + ptp_getobjectinfo(params, oid, ¶ms->objectinfo[curhandle]); + diroid = params->objectinfo[curhandle].ParentObject; + params->handles.Handler[dirhandle] = diroid; + if ((params->objectinfo[dirhandle].ParentObject & 0xffff0000) == 0x42420000) { + if (roothandle >= 0) { + ptp_getobjectinfo(params, diroid, ¶ms->objectinfo[dirhandle]); + rootoid = params->objectinfo[dirhandle].ParentObject; + params->handles.Handler[roothandle] = rootoid; + } + } + } + params->objectinfo[curhandle].ParentObject = diroid; + curhandle++; + } + } + params->handles.n = curhandle; + return PTP_RC_OK; + } + + /* CANON also has fast directory retrieval. And it is mostly complete, so we can use it as full replacement */ + if ((params->deviceinfo.VendorExtensionID == PTP_VENDOR_CANON) && + ptp_operation_issupported(params,PTP_OC_CANON_GetDirectory)) + + { + PTPObjectInfo *oinfos = NULL; + uint32_t *flags = NULL; + + ret = ptp_canon_get_directory (params, ¶ms->handles, &oinfos, &flags); + if ((ret == PTP_RC_OK) && params->handles.n) { + params->objectinfo = oinfos; + params->canon_flags = flags; + return PTP_RC_OK; + } + /* fallthrough */ + } + +fallback: + /* Get file handles array for filesystem */ + id = gp_context_progress_start (context, 100, _("Initializing Camera")); + /* get objecthandles of all objects from all stores */ + CPR (context, ptp_getobjecthandles + (params, 0xffffffff, 0x000000, 0x000000, ¶ms->handles)); + + gp_context_progress_update (context, id, 10); + /* wee need that for fileststem */ + params->objectinfo = (PTPObjectInfo*)malloc(sizeof(PTPObjectInfo)* + params->handles.n); + memset (params->objectinfo,0,sizeof(PTPObjectInfo)*params->handles.n); + + for (i = 0; i < params->handles.n; i++) { + CPR (context, ptp_getobjectinfo(params, + params->handles.Handler[i], + ¶ms->objectinfo[i])); +#if 1 + { + PTPObjectInfo *oi; + + oi=¶ms->objectinfo[i]; + GP_DEBUG ("ObjectInfo for '%s':", oi->Filename); + GP_DEBUG (" Object ID: 0x%08x", + params->handles.Handler[i]); + GP_DEBUG (" StorageID: 0x%08x", oi->StorageID); + GP_DEBUG (" ObjectFormat: 0x%04x", oi->ObjectFormat); + GP_DEBUG (" ProtectionStatus: 0x%04x", oi->ProtectionStatus); + GP_DEBUG (" ObjectCompressedSize: %d", + oi->ObjectCompressedSize); + GP_DEBUG (" ThumbFormat: 0x%04x", oi->ThumbFormat); + GP_DEBUG (" ThumbCompressedSize: %d", + oi->ThumbCompressedSize); + GP_DEBUG (" ThumbPixWidth: %d", oi->ThumbPixWidth); + GP_DEBUG (" ThumbPixHeight: %d", oi->ThumbPixHeight); + GP_DEBUG (" ImagePixWidth: %d", oi->ImagePixWidth); + GP_DEBUG (" ImagePixHeight: %d", oi->ImagePixHeight); + GP_DEBUG (" ImageBitDepth: %d", oi->ImageBitDepth); + GP_DEBUG (" ParentObject: 0x%08x", oi->ParentObject); + GP_DEBUG (" AssociationType: 0x%04x", oi->AssociationType); + GP_DEBUG (" AssociationDesc: 0x%08x", oi->AssociationDesc); + GP_DEBUG (" SequenceNumber: 0x%08x", oi->SequenceNumber); + } +#endif + gp_context_progress_update (context, id, + 10+(90*i)/params->handles.n); + } + gp_context_progress_stop (context, id); + + /* for older Canons we now retrieve their object flags, to allow + * "new" image handling. This is not yet a substitute for regular + * OI retrieval. + */ + if ((params->deviceinfo.VendorExtensionID == PTP_VENDOR_CANON) && + ptp_operation_issupported(params,PTP_OC_CANON_GetObjectInfoEx)) { + uint16_t ret; + int i; + + params->canon_flags = calloc(sizeof(params->canon_flags[0]),params->handles.n); + /* Look for all directories, since this function apparently only + * returns a directory full of entries and does not recurse + */ + for (i=0;i<params->handles.n;i++) { + int j; + PTPCANONFolderEntry *ents = NULL; + uint32_t numents = 0; + + /* only retrieve for directories */ + if (!params->objectinfo[i].AssociationType) + continue; + + ret = ptp_canon_getobjectinfo(params, + params->objectinfo[i].StorageID,0, + params->handles.Handler[i],0, + &ents,&numents + ); + if (ret != PTP_RC_OK) continue; + for (j=0;j<numents;j++) { + int k; + for (k=0;k<params->handles.n;k++) + if (params->handles.Handler[k] == ents[j].ObjectHandle) + break; + if (k == params->handles.n) + continue; + params->canon_flags[k] = ents[j].Flags << 8; + } + } + } + + if (DCIM_WRONG_PARENT_BUG(camera->pl)) + { + GP_DEBUG("PTPBUG_DCIM_WRONG_PARENT bug workaround"); + /* Count number of root directory objects */ + nroot = 0; + for (i = 0; i < params->handles.n; i++) + if (params->objectinfo[i].ParentObject == 0) + nroot++; + + GP_DEBUG("Found %d root directory objects", nroot); + + /* If no root directory objects, look for "DCIM". This way, we can + * handle cameras that report the wrong ParentObject ID for root + */ + if (nroot == 0 && params->handles.n > 0) { + for (i = 0; i < params->handles.n; i++) + { + PTPObjectInfo *oi = ¶ms->objectinfo[i]; + + if (strcmp(oi->Filename, "DCIM") == 0) + { + GP_DEBUG("Changing DCIM ParentObject ID from 0x%x to 0", + oi->ParentObject); + oi->ParentObject = 0; + nroot++; + } + } + } + /* Some cameras do not have a directory at all, just files or unattached + * directories. In this case associate all unattached to the 0 object. + */ + if (nroot == 0) { + /* look for entries with parentobjects that do not exist */ + for (i = 0; i < params->handles.n; i++) + { + int j; + PTPObjectInfo *oi = ¶ms->objectinfo[i]; + + for (j = 0;j < params->handles.n; j++) + if (oi->ParentObject == params->handles.Handler[j]) + break; + if (j == params->handles.n) + oi->ParentObject = 0; + } + } + } +#if 0 + add_dir (camera, 0x00000000, 0xff000000, "DIR1"); + add_dir (camera, 0x00000000, 0xff000001, "DIR20"); + add_dir (camera, 0xff000000, 0xff000002, "subDIR1"); + add_dir (camera, 0xff000002, 0xff000003, "subsubDIR1"); + move_object_by_number (camera, 0xff000002, 2); + move_object_by_number (camera, 0xff000001, 3); + move_object_by_number (camera, 0xff000002, 4); + /* Used for testing with my camera, which does not support subdirs */ +#endif + return (GP_OK); +} + +static CameraFilesystemFuncs fsfuncs = { + .file_list_func = file_list_func, + .folder_list_func = folder_list_func, + .get_info_func = get_info_func, + .get_file_func = get_file_func, + .del_file_func = delete_file_func, + .put_file_func = put_file_func, + .make_dir_func = make_dir_func, + .remove_dir_func = remove_dir_func +}; + +int +camera_init (Camera *camera, GPContext *context) +{ + CameraAbilities a; + int ret, i, retried = 0; + PTPParams *params; + char *curloc; + + /* Make sure our port is either USB or PTP/IP. */ + if ((camera->port->type != GP_PORT_USB) && (camera->port->type != GP_PORT_PTPIP)) { + gp_context_error (context, _("PTP is only implemented for " + "USB and PTP/IP cameras currently, port type %x"), camera->port->type); + return (GP_ERROR_UNKNOWN_PORT); + } + + camera->functions->about = camera_about; + camera->functions->exit = camera_exit; + camera->functions->capture = camera_capture; + camera->functions->capture_preview = camera_capture_preview; + camera->functions->summary = camera_summary; + camera->functions->get_config = camera_get_config; + camera->functions->set_config = camera_set_config; + camera->functions->wait_for_event = camera_wait_for_event; + + /* We need some data that we pass around */ + camera->pl = malloc (sizeof (CameraPrivateLibrary)); + if (!camera->pl) + return (GP_ERROR_NO_MEMORY); + memset (camera->pl, 0, sizeof (CameraPrivateLibrary)); + params = &camera->pl->params; + camera->pl->params.debug_func = ptp_debug_func; + camera->pl->params.error_func = ptp_error_func; + camera->pl->params.data = malloc (sizeof (PTPData)); + memset (camera->pl->params.data, 0, sizeof (PTPData)); + ((PTPData *) camera->pl->params.data)->camera = camera; + camera->pl->params.byteorder = PTP_DL_LE; + + switch (camera->port->type) { + case GP_PORT_USB: + camera->pl->params.sendreq_func = ptp_usb_sendreq; + camera->pl->params.senddata_func = ptp_usb_senddata; + camera->pl->params.getresp_func = ptp_usb_getresp; + camera->pl->params.getdata_func = ptp_usb_getdata; + camera->pl->params.event_wait = ptp_usb_event_wait; + camera->pl->params.event_check = ptp_usb_event_check; + + camera->pl->params.write_func = ptp_write_func; + camera->pl->params.read_func = ptp_read_func; + camera->pl->params.check_int_func = ptp_check_int; + camera->pl->params.check_int_fast_func = ptp_check_int_fast; + break; + case GP_PORT_PTPIP: { + GPPortInfo pinfo; + + ret = gp_port_get_info (camera->port, &pinfo); + if (ret != GP_OK) { + gp_log (GP_LOG_ERROR, "ptpip", "Failed to get port info?\n"); + return ret; + } + ret = ptp_ptpip_connect (&camera->pl->params, pinfo.path); + if (ret != GP_OK) { + gp_log (GP_LOG_ERROR, "ptpip", "Failed to connect.\n"); + return ret; + } + camera->pl->params.sendreq_func = ptp_ptpip_sendreq; + camera->pl->params.senddata_func = ptp_ptpip_senddata; + camera->pl->params.getresp_func = ptp_ptpip_getresp; + camera->pl->params.getdata_func = ptp_ptpip_getdata; + camera->pl->params.event_wait = ptp_ptpip_event_wait; + camera->pl->params.event_check = ptp_ptpip_event_check; + + camera->pl->params.write_func = NULL; + camera->pl->params.read_func = NULL; + camera->pl->params.check_int_func = NULL; + camera->pl->params.check_int_fast_func = NULL; + break; + } + default: + break; + } + + curloc = nl_langinfo (CODESET); + if (!curloc) curloc="UTF-8"; + camera->pl->params.cd_ucs2_to_locale = iconv_open(curloc, UCS_2_INTERNAL); + camera->pl->params.cd_locale_to_ucs2 = iconv_open(UCS_2_INTERNAL, curloc); + if ((camera->pl->params.cd_ucs2_to_locale == (iconv_t) -1) || + (camera->pl->params.cd_locale_to_ucs2 == (iconv_t) -1)) { + gp_log (GP_LOG_ERROR, "iconv", "Failed to create iconv converter.\n"); + return (GP_ERROR_OS_FAILURE); + } + + gp_camera_get_abilities(camera, &a); + for (i = 0; sizeof(models)/sizeof(models[0]); i++) { + if ((a.usb_vendor == models[i].usb_vendor) && + (a.usb_product == models[i].usb_product)){ + camera->pl->bugs = models[i].known_bugs; + break; + } + } + + /* Choose a shorter timeout on inital setup to avoid + * having the user wait too long. + */ + CR (gp_port_set_timeout (camera->port, USB_START_TIMEOUT)); + + /* Establish a connection to the camera */ + ((PTPData *) camera->pl->params.data)->context = context; + + retried = 0; + while (1) { + ret=ptp_opensession (&camera->pl->params, 1); + while (ret==PTP_RC_InvalidTransactionID) { + camera->pl->params.transaction_id+=10; + ret=ptp_opensession (&camera->pl->params, 1); + } + if (ret!=PTP_RC_SessionAlreadyOpened && ret!=PTP_RC_OK) { + if (retried < 2) { /* try again */ + retried++; + continue; + } + report_result(context, ret, camera->pl->params.deviceinfo.VendorExtensionID); + return (translate_ptp_result(ret)); + } + break; + } + /* We have cameras where a response takes 15 seconds(!), so make + * post init timeouts longer */ + CR (gp_port_set_timeout (camera->port, USB_NORMAL_TIMEOUT)); + + /* Seems HP does not like getdevinfo outside of session + although it's legal to do so */ + /* get device info */ + CPR(context, ptp_getdeviceinfo(&camera->pl->params, + &camera->pl->params.deviceinfo)); + + GP_DEBUG ("Device info:"); + GP_DEBUG ("Manufacturer: %s",camera->pl->params.deviceinfo.Manufacturer); + GP_DEBUG (" model: %s", camera->pl->params.deviceinfo.Model); + GP_DEBUG (" device version: %s", camera->pl->params.deviceinfo.DeviceVersion); + GP_DEBUG (" serial number: '%s'",camera->pl->params.deviceinfo.SerialNumber); + GP_DEBUG ("Vendor extension ID: 0x%08x",camera->pl->params.deviceinfo.VendorExtensionID); + GP_DEBUG ("Vendor extension description: %s",camera->pl->params.deviceinfo.VendorExtensionDesc); + GP_DEBUG ("Supported operations:"); + for (i=0; i<camera->pl->params.deviceinfo.OperationsSupported_len; i++) + GP_DEBUG (" 0x%04x", + camera->pl->params.deviceinfo.OperationsSupported[i]); + GP_DEBUG ("Events Supported:"); + for (i=0; i<camera->pl->params.deviceinfo.EventsSupported_len; i++) + GP_DEBUG (" 0x%04x", + camera->pl->params.deviceinfo.EventsSupported[i]); + GP_DEBUG ("Device Properties Supported:"); + for (i=0; i<camera->pl->params.deviceinfo.DevicePropertiesSupported_len; + i++) + GP_DEBUG (" 0x%04x", + camera->pl->params.deviceinfo.DevicePropertiesSupported[i]); + + /* init internal ptp objectfiles (required for fs implementation) */ + init_ptp_fs (camera, context); + + switch (camera->pl->params.deviceinfo.VendorExtensionID) { + case PTP_VENDOR_CANON: +#if 0 + if (ptp_operation_issupported(&camera->pl->params, PTP_OC_CANON_ThemeDownload)) { + add_special_file("startimage.jpg", canon_theme_get, canon_theme_put); + add_special_file("startsound.wav", canon_theme_get, canon_theme_put); + add_special_file("operation.wav", canon_theme_get, canon_theme_put); + add_special_file("shutterrelease.wav", canon_theme_get, canon_theme_put); + add_special_file("selftimer.wav", canon_theme_get, canon_theme_put); + } +#endif + break; + case PTP_VENDOR_NIKON: + if (ptp_operation_issupported(&camera->pl->params, PTP_OC_NIKON_CurveDownload)) + add_special_file("curve.ntc", nikon_curve_get, nikon_curve_put); + break; + default: + break; + } + CR (gp_filesystem_set_funcs (camera->fs, &fsfuncs, camera)); + return (GP_OK); +} |