* Add support for QuickTake 100/150 - Summary - Get/Set configuration - Get preview - Get PPM files - Get raw files (QTK format) Serial protocol reference: https://www.colino.net/wordpress/en/archives/2023/10/29/the-apple-quicktake-100-serial-communication-protocol/ QTKT / QTKN decoding algorithms from dcraw (GPL licensed). * Fix theorical integer overflows * Fix license after careful re-reading of dcraw's license * Fix quality mode index * Fix Quicktake 150 thumbnails * Fix unused variables and document thumbnail formats
1166 lines
27 KiB
C
1166 lines
27 KiB
C
/** \file
|
|
*
|
|
* \author Copyright 2000 Scott Fritzinger
|
|
*
|
|
* \note
|
|
* 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.
|
|
*
|
|
* \note
|
|
* 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.
|
|
*
|
|
* \note
|
|
* 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., 51 Franklin Street, Fifth Floor,
|
|
* Boston, MA 02110-1301 USA
|
|
*
|
|
* \note
|
|
* This file contains internal functions. Use of these functions from
|
|
* external software modules is considered <strong>deprecated</strong>.
|
|
*/
|
|
#define _POSIX_SOURCE
|
|
#define _DEFAULT_SOURCE
|
|
#define _DARWIN_C_SOURCE
|
|
#define _XOPEN_SOURCE 500
|
|
|
|
#include "config.h"
|
|
#include <gphoto2/gphoto2-file.h>
|
|
|
|
#include <stdlib.h>
|
|
#include <stdio.h>
|
|
#include <errno.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#include <utime.h>
|
|
#include <unistd.h>
|
|
|
|
#include <gphoto2/gphoto2-port-log.h>
|
|
#include <gphoto2/gphoto2-port-portability.h>
|
|
|
|
#include <gphoto2/gphoto2-result.h>
|
|
|
|
#define CHECK_RESULT(result) {int r = (result); if (r < 0) return (r);}
|
|
|
|
/* length of one path component */
|
|
#ifndef MAX_PATH
|
|
# define MAX_PATH 256
|
|
#endif
|
|
|
|
/*! The internals of the CameraFile struct are private.
|
|
* \internal
|
|
*/
|
|
struct _CameraFile {
|
|
char mime_type [64];
|
|
char name [MAX_PATH];
|
|
int ref_count;
|
|
time_t mtime;
|
|
|
|
CameraFileAccessType accesstype;
|
|
|
|
/* for GP_FILE_ACCESSTYPE_MEMORY files */
|
|
unsigned long size;
|
|
unsigned char *data;
|
|
unsigned long offset; /* read pointer */
|
|
|
|
/* for GP_FILE_ACCESSTYPE_FD files */
|
|
int fd;
|
|
|
|
/* for GP_FILE_ACCESSTYPE_HANDLER files */
|
|
CameraFileHandler*handler;
|
|
void *private;
|
|
};
|
|
|
|
|
|
/*! Create new #CameraFile object.
|
|
*
|
|
* \param file a pointer to a #CameraFile
|
|
* \return a gphoto2 error code.
|
|
*/
|
|
int
|
|
gp_file_new (CameraFile **file)
|
|
{
|
|
C_PARAMS (file);
|
|
|
|
C_MEM (*file = calloc (1, sizeof (CameraFile)));
|
|
|
|
strcpy ((*file)->mime_type, "unknown/unknown");
|
|
(*file)->ref_count = 1;
|
|
(*file)->accesstype = GP_FILE_ACCESSTYPE_MEMORY;
|
|
return (GP_OK);
|
|
}
|
|
|
|
|
|
/*! Create new #CameraFile object from a UNIX filedescriptor.
|
|
*
|
|
* This function takes ownership of the filedescriptor and will close it when closing the CameraFile.
|
|
*
|
|
* \param file a pointer to a #CameraFile
|
|
* \param fd a UNIX filedescriptor
|
|
* \return a gphoto2 error code.
|
|
*/
|
|
int
|
|
gp_file_new_from_fd (CameraFile **file, int fd)
|
|
{
|
|
C_PARAMS (file);
|
|
|
|
C_MEM (*file = calloc (1, sizeof (CameraFile)));
|
|
|
|
strcpy ((*file)->mime_type, "unknown/unknown");
|
|
(*file)->ref_count = 1;
|
|
(*file)->accesstype = GP_FILE_ACCESSTYPE_FD;
|
|
(*file)->fd = fd;
|
|
return (GP_OK);
|
|
}
|
|
|
|
/*! Create new #CameraFile object using a programmatic handler.
|
|
*
|
|
* \param file a pointer to a #CameraFile
|
|
* \param handler a #CameraFileHandler
|
|
* \param private a private pointer for frontend use
|
|
* \return a gphoto2 error code.
|
|
*/
|
|
int
|
|
gp_file_new_from_handler (CameraFile **file, CameraFileHandler* handler, void*private)
|
|
{
|
|
C_PARAMS (file);
|
|
|
|
C_MEM (*file = calloc (1, sizeof (CameraFile)));
|
|
|
|
strcpy ((*file)->mime_type, "unknown/unknown");
|
|
(*file)->ref_count = 1;
|
|
(*file)->accesstype = GP_FILE_ACCESSTYPE_HANDLER;
|
|
(*file)->handler = handler;
|
|
(*file)->private = private;
|
|
return (GP_OK);
|
|
}
|
|
|
|
|
|
|
|
/*! \brief descruct a #CameraFile object.
|
|
* @param file a #CameraFile
|
|
* @return a gphoto2 error code.
|
|
*
|
|
**/
|
|
int gp_file_free (CameraFile *file)
|
|
{
|
|
C_PARAMS (file);
|
|
|
|
CHECK_RESULT (gp_file_clean (file));
|
|
|
|
if (file->accesstype == GP_FILE_ACCESSTYPE_FD)
|
|
close (file->fd);
|
|
|
|
free (file);
|
|
return (GP_OK);
|
|
}
|
|
|
|
|
|
/*! \brief Increase reference counter for #CameraFile object
|
|
*
|
|
* \param file a #CameraFile
|
|
* \return a gphoto2 error code.
|
|
*/
|
|
int
|
|
gp_file_ref (CameraFile *file)
|
|
{
|
|
C_PARAMS (file);
|
|
|
|
file->ref_count += 1;
|
|
|
|
return (GP_OK);
|
|
}
|
|
|
|
|
|
/*! \brief Decrease reference counter for #CameraFile object
|
|
*
|
|
* \param file a #CameraFile
|
|
* \return a gphoto2 error code.
|
|
*
|
|
**/
|
|
int
|
|
gp_file_unref (CameraFile *file)
|
|
{
|
|
C_PARAMS (file);
|
|
|
|
file->ref_count -= 1;
|
|
|
|
if (file->ref_count == 0)
|
|
CHECK_RESULT (gp_file_free (file));
|
|
|
|
return (GP_OK);
|
|
}
|
|
|
|
|
|
/**
|
|
* @param file a #CameraFile
|
|
* @param data
|
|
* @param size
|
|
* @return a gphoto2 error code.
|
|
*
|
|
**/
|
|
int
|
|
gp_file_append (CameraFile *file, const char *data,
|
|
unsigned long int size)
|
|
{
|
|
C_PARAMS (file);
|
|
|
|
switch (file->accesstype) {
|
|
case GP_FILE_ACCESSTYPE_MEMORY:
|
|
C_MEM (file->data = realloc (file->data, sizeof (char) * (file->size + size)));
|
|
memcpy (&file->data[file->size], data, size);
|
|
file->size += size;
|
|
break;
|
|
case GP_FILE_ACCESSTYPE_FD: {
|
|
unsigned long int curwritten = 0;
|
|
while (curwritten < size) {
|
|
ssize_t res = write (file->fd, data+curwritten, size-curwritten);
|
|
if (res == -1) {
|
|
GP_LOG_E ("Encountered error %d writing to fd.", errno);
|
|
return GP_ERROR_IO_WRITE;
|
|
}
|
|
if (!res) { /* no progress is bad too */
|
|
GP_LOG_E ("Encountered 0 bytes written to fd.");
|
|
return GP_ERROR_IO_WRITE;
|
|
}
|
|
curwritten += res;
|
|
}
|
|
break;
|
|
}
|
|
case GP_FILE_ACCESSTYPE_HANDLER: {
|
|
uint64_t xsize = size;
|
|
/* FIXME: assume we write one blob */
|
|
C_PARAMS (file->handler->write);
|
|
return file->handler->write (file->private, (unsigned char*)data, &xsize);
|
|
}
|
|
default:
|
|
GP_LOG_E ("Unknown file access type %d", file->accesstype);
|
|
return GP_ERROR;
|
|
}
|
|
return (GP_OK);
|
|
}
|
|
|
|
/**
|
|
* @param file a #CameraFile
|
|
* @param data
|
|
* @param size
|
|
* @return a gphoto2 error code.
|
|
*
|
|
* Internal.
|
|
**/
|
|
int
|
|
gp_file_slurp (CameraFile *file, char *data,
|
|
size_t size, size_t *readlen
|
|
) {
|
|
C_PARAMS (file);
|
|
|
|
switch (file->accesstype) {
|
|
case GP_FILE_ACCESSTYPE_MEMORY:
|
|
if (size > file->size-file->offset)
|
|
size = file->size - file->offset;
|
|
memcpy (data, &file->data[file->offset], size);
|
|
file->offset += size;
|
|
if (readlen) *readlen = size;
|
|
break;
|
|
case GP_FILE_ACCESSTYPE_FD: {
|
|
unsigned long int curread = 0;
|
|
while (curread < size) {
|
|
ssize_t res = read (file->fd, data+curread, size-curread);
|
|
if (res == -1) {
|
|
GP_LOG_E ("Encountered error %d reading from fd.", errno);
|
|
return GP_ERROR_IO_READ;
|
|
}
|
|
if (!res) { /* no progress is bad too */
|
|
GP_LOG_E ("Encountered 0 bytes reading from fd.");
|
|
return GP_ERROR_IO_READ;
|
|
}
|
|
curread += res;
|
|
if (readlen)
|
|
*readlen = curread;
|
|
}
|
|
break;
|
|
}
|
|
case GP_FILE_ACCESSTYPE_HANDLER: {
|
|
uint64_t xsize = size;
|
|
int ret;
|
|
|
|
C_PARAMS (file->handler->read);
|
|
ret = file->handler->read (file->private, (unsigned char*)data, &xsize);
|
|
*readlen = xsize;
|
|
if (ret != GP_OK)
|
|
GP_LOG_E ("File handler read returned %d", ret);
|
|
return ret;
|
|
}
|
|
default:
|
|
GP_LOG_E ("Unknown file access type %d", file->accesstype);
|
|
return GP_ERROR;
|
|
}
|
|
return (GP_OK);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
* @param file a #CameraFile
|
|
* @param data
|
|
* @param size
|
|
* @return a gphoto2 error code.
|
|
*
|
|
**/
|
|
int
|
|
gp_file_set_data_and_size (CameraFile *file, char *data,
|
|
unsigned long int size)
|
|
{
|
|
C_PARAMS (file);
|
|
|
|
switch (file->accesstype) {
|
|
case GP_FILE_ACCESSTYPE_MEMORY:
|
|
free (file->data);
|
|
file->data = (unsigned char*)data;
|
|
file->size = size;
|
|
break;
|
|
case GP_FILE_ACCESSTYPE_FD: {
|
|
unsigned int curwritten = 0;
|
|
|
|
/* truncate */
|
|
if (-1 == lseek (file->fd, 0, SEEK_SET)) {
|
|
GP_LOG_E ("Encountered error %d lseeking to 0.", errno);
|
|
/* might happen on pipes ... just ignore it */
|
|
}
|
|
if (-1 == ftruncate (file->fd, 0)) {
|
|
GP_LOG_E ("Encountered error %d ftruncating to 0.", errno);
|
|
/* might happen on pipes ... just ignore it */
|
|
}
|
|
while (curwritten < size) {
|
|
ssize_t res = write (file->fd, data+curwritten, size-curwritten);
|
|
if (res == -1) {
|
|
GP_LOG_E ("Encountered error %d writing to fd.", errno);
|
|
return GP_ERROR_IO_WRITE;
|
|
}
|
|
if (!res) { /* no progress is bad too */
|
|
GP_LOG_E ("Encountered 0 bytes written to fd.");
|
|
return GP_ERROR_IO_WRITE;
|
|
}
|
|
curwritten += res;
|
|
}
|
|
/* This function takes over the responsibility for "data", aka
|
|
* it has to free it. So we do.
|
|
*/
|
|
free (data);
|
|
break;
|
|
}
|
|
case GP_FILE_ACCESSTYPE_HANDLER: {
|
|
uint64_t xsize = size;
|
|
int ret;
|
|
|
|
C_PARAMS (file->handler->write);
|
|
/* FIXME: handle multiple blob writes */
|
|
ret = file->handler->write (file->private, (unsigned char*)data, &xsize);
|
|
if (ret != GP_OK) {
|
|
GP_LOG_E ("Handler data() returned %d", ret);
|
|
return ret;
|
|
}
|
|
/* This function takes over the responsibility for "data", aka
|
|
* it has to free it. So we do.
|
|
*/
|
|
free (data);
|
|
return GP_OK;
|
|
}
|
|
default:
|
|
GP_LOG_E ("Unknown file access type %d", file->accesstype);
|
|
return GP_ERROR;
|
|
}
|
|
return (GP_OK);
|
|
}
|
|
|
|
|
|
/**
|
|
* Get a pointer to the data and the file's size.
|
|
*
|
|
* @param file a #CameraFile
|
|
* @param data
|
|
* @param size
|
|
* @return a gphoto2 error code.
|
|
*
|
|
* Both data and size can be NULL and will then be ignored.
|
|
*
|
|
* For regular CameraFiles, the pointer to data that is returned is
|
|
* still owned by libgphoto2 and its lifetime is the same as the #file.
|
|
*
|
|
* For filedescriptor or handler based CameraFile types, the returned
|
|
* data pointer is owned by the caller and needs to be free()d to avoid
|
|
* memory leaks.
|
|
*
|
|
**/
|
|
int
|
|
gp_file_get_data_and_size (CameraFile *file, const char **data,
|
|
unsigned long int *size)
|
|
{
|
|
C_PARAMS (file);
|
|
|
|
switch (file->accesstype) {
|
|
case GP_FILE_ACCESSTYPE_MEMORY:
|
|
if (data)
|
|
*data = (char*)file->data;
|
|
if (size)
|
|
*size = file->size;
|
|
break;
|
|
case GP_FILE_ACCESSTYPE_FD: {
|
|
off_t offset;
|
|
off_t curread = 0;
|
|
|
|
if (-1 == lseek (file->fd, 0, SEEK_END)) {
|
|
if (errno == EBADF) return GP_ERROR_IO;
|
|
/* Might happen for pipes or sockets. Umm. Hard. */
|
|
/* FIXME */
|
|
}
|
|
if (-1 == (offset = lseek (file->fd, 0, SEEK_CUR))) {
|
|
/* should not happen if we passed the above case */
|
|
GP_LOG_E ("Encountered error %d lseekin to CUR.", errno);
|
|
return GP_ERROR_IO_READ;
|
|
}
|
|
if (-1 == lseek (file->fd, 0, SEEK_SET)) {
|
|
/* should not happen if we passed the above cases */
|
|
GP_LOG_E ("Encountered error %d lseekin to CUR.", errno);
|
|
return GP_ERROR_IO_READ;
|
|
}
|
|
if (size) *size = offset;
|
|
if (!data) /* just the size... */
|
|
return GP_OK;
|
|
C_MEM (*data = malloc (offset));
|
|
while (curread < offset) {
|
|
ssize_t res = read (file->fd, (char*)((*data)+curread), offset-curread);
|
|
if (res == -1) {
|
|
free ((char*)*data);
|
|
GP_LOG_E ("Encountered error %d reading.", errno);
|
|
return GP_ERROR_IO_READ;
|
|
}
|
|
if (res == 0) {
|
|
free ((char*)*data);
|
|
GP_LOG_E ("No progress during reading.");
|
|
return GP_ERROR_IO_READ;
|
|
}
|
|
curread += res;
|
|
}
|
|
break;
|
|
}
|
|
case GP_FILE_ACCESSTYPE_HANDLER: {
|
|
uint64_t xsize = 0;
|
|
int ret;
|
|
|
|
C_PARAMS (file->handler->read);
|
|
ret = file->handler->size (file->private, &xsize);
|
|
if (ret != GP_OK) {
|
|
GP_LOG_E ("Encountered error %d querying size().", ret);
|
|
return ret;
|
|
}
|
|
if (size) *size = xsize;
|
|
if (!data) /* just the size... */
|
|
return GP_OK;
|
|
C_MEM (*data = malloc (xsize));
|
|
ret = file->handler->read (file->private, (unsigned char*)*data, &xsize);
|
|
if (ret != GP_OK) {
|
|
GP_LOG_E ("Encountered error %d getting data().", ret);
|
|
free ((char*)*data);
|
|
*data = NULL;
|
|
}
|
|
return ret;
|
|
}
|
|
default:
|
|
GP_LOG_E ("Unknown file access type %d", file->accesstype);
|
|
return GP_ERROR;
|
|
}
|
|
return (GP_OK);
|
|
}
|
|
|
|
|
|
/**
|
|
* @param file a #CameraFile
|
|
* @param filename
|
|
* @return a gphoto2 error code.
|
|
*
|
|
**/
|
|
int
|
|
gp_file_save (CameraFile *file, const char *filename)
|
|
{
|
|
FILE *fp;
|
|
struct utimbuf u;
|
|
|
|
C_PARAMS (file && filename);
|
|
|
|
switch (file->accesstype) {
|
|
case GP_FILE_ACCESSTYPE_MEMORY:
|
|
if (!(fp = fopen (filename, "wb")))
|
|
return GP_ERROR;
|
|
if (fwrite (file->data, (size_t)sizeof(char), (size_t)file->size, fp) != (size_t)file->size) {
|
|
GP_LOG_E ("Not enough space on device in order to save '%s'.", filename);
|
|
fclose (fp);
|
|
unlink (filename);
|
|
return GP_ERROR;
|
|
}
|
|
fclose (fp);
|
|
break;
|
|
case GP_FILE_ACCESSTYPE_FD: {
|
|
char *data;
|
|
off_t curread = 0;
|
|
off_t offset;
|
|
|
|
if (-1 == lseek (file->fd, 0, SEEK_END))
|
|
return GP_ERROR_IO;
|
|
if (-1 == (offset = lseek (file->fd, 0, SEEK_CUR))) {
|
|
/* should not happen if we passed the above case */
|
|
GP_LOG_E ("Encountered error %d lseekin to CUR.", errno);
|
|
return GP_ERROR_IO_READ;
|
|
}
|
|
if (-1 == lseek (file->fd, 0, SEEK_SET)) {
|
|
/* should not happen if we passed the above case */
|
|
GP_LOG_E ("Encountered error %d lseekin to BEGIN.", errno);
|
|
return GP_ERROR_IO_READ;
|
|
}
|
|
C_MEM (data = malloc(65536));
|
|
if (!(fp = fopen (filename, "wb"))) {
|
|
free (data);
|
|
return GP_ERROR;
|
|
}
|
|
while (curread < offset) {
|
|
int toread, res;
|
|
|
|
toread = 65536;
|
|
if (toread > (offset-curread))
|
|
toread = offset-curread;
|
|
res = read (file->fd, data, toread);
|
|
if (res <= 0) {
|
|
free (data);
|
|
fclose (fp);
|
|
unlink (filename);
|
|
return GP_ERROR_IO_READ;
|
|
}
|
|
if ((int)fwrite (data, 1, res, fp) != res) {
|
|
GP_LOG_E ("Not enough space on device in order to save '%s'.", filename);
|
|
free (data);
|
|
fclose (fp);
|
|
unlink (filename);
|
|
return GP_ERROR;
|
|
}
|
|
curread += res;
|
|
}
|
|
free (data);
|
|
fclose (fp);
|
|
break;
|
|
}
|
|
default:
|
|
GP_LOG_E ("Unknown file access type %d", file->accesstype);
|
|
return GP_ERROR;
|
|
}
|
|
|
|
if (file->mtime) {
|
|
u.actime = file->mtime;
|
|
u.modtime = file->mtime;
|
|
utime (filename, &u);
|
|
}
|
|
|
|
return (GP_OK);
|
|
}
|
|
|
|
|
|
/*
|
|
* mime types that cannot be determined by the filename
|
|
* extension. Better hack would be to use library that examine
|
|
* file content instead, like gnome-vfs mime handling, or
|
|
* gnome-mime, whatever.
|
|
* See also the GP_MIME_* definitions.
|
|
*/
|
|
static const char *mime_table[] = {
|
|
"bmp", GP_MIME_BMP,
|
|
"jpg", GP_MIME_JPEG,
|
|
"tif", GP_MIME_TIFF,
|
|
"ppm", GP_MIME_PPM,
|
|
"pgm", GP_MIME_PGM,
|
|
"pnm", GP_MIME_PNM,
|
|
"png", GP_MIME_PNG,
|
|
"wav", GP_MIME_WAV,
|
|
"avi", GP_MIME_AVI,
|
|
"mp3", GP_MIME_MP3,
|
|
"wma", GP_MIME_WMA,
|
|
"asf", GP_MIME_ASF,
|
|
"ogg", GP_MIME_OGG,
|
|
"mpg", GP_MIME_MPEG,
|
|
"raw", GP_MIME_RAW,
|
|
"mts", GP_MIME_AVCHD,
|
|
"m2ts", GP_MIME_AVCHD,
|
|
"arw", GP_MIME_ARW,
|
|
"raf", GP_MIME_RAF,
|
|
"txt", GP_MIME_TXT,
|
|
"qtk", GP_MIME_QTKT,
|
|
"qtk", GP_MIME_QTKN,
|
|
NULL};
|
|
/**
|
|
* @param file a #CameraFile
|
|
* @param filename
|
|
* @return a gphoto2 error code.
|
|
*
|
|
**/
|
|
int
|
|
gp_file_open (CameraFile *file, const char *filename)
|
|
{
|
|
FILE *fp;
|
|
char *name, *dot;
|
|
long size, size_read;
|
|
int i;
|
|
struct stat s;
|
|
|
|
|
|
C_PARAMS (file && filename);
|
|
|
|
CHECK_RESULT (gp_file_clean (file));
|
|
|
|
fp = fopen(filename, "r");
|
|
if (!fp)
|
|
return (GP_ERROR);
|
|
fseek (fp, 0, SEEK_END);
|
|
size = ftell (fp);
|
|
rewind (fp);
|
|
|
|
switch (file->accesstype) {
|
|
case GP_FILE_ACCESSTYPE_MEMORY:
|
|
file->data = malloc (sizeof(char)*(size + 1));
|
|
if (!file->data) {
|
|
fclose (fp);
|
|
return (GP_ERROR_NO_MEMORY);
|
|
}
|
|
size_read = fread (file->data, (size_t)sizeof(char), (size_t)size, fp);
|
|
if (ferror(fp)) {
|
|
gp_file_clean (file);
|
|
fclose (fp);
|
|
return (GP_ERROR);
|
|
}
|
|
fclose(fp);
|
|
file->size = size_read;
|
|
file->data[size_read] = 0;
|
|
break;
|
|
case GP_FILE_ACCESSTYPE_FD: {
|
|
if (file->fd == -1) {
|
|
file->fd = dup(fileno(fp));
|
|
fclose (fp);
|
|
break;
|
|
}
|
|
GP_LOG_E ("Needs to be initialized with fd=-1 to work");
|
|
fclose (fp);
|
|
return GP_ERROR;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
|
|
name = strrchr (filename, '/');
|
|
if (name)
|
|
strncpy (file->name, name + 1, sizeof (file->name));
|
|
else
|
|
strncpy (file->name, filename, sizeof (file->name));
|
|
|
|
/* MIME lookup */
|
|
dot = strrchr (filename, '.');
|
|
if (dot) {
|
|
for (i = 0; mime_table[i] ; i+=2)
|
|
if (!strcasecmp (mime_table[i], dot+1)) {
|
|
strncpy (file->mime_type, mime_table[i+1], sizeof(file->mime_type));
|
|
break;
|
|
}
|
|
if (!mime_table[i])
|
|
/*
|
|
* We did not found the type in the lookup table,
|
|
* so we use the file suffix as mime type.
|
|
* Note: This should probably use GP_MIME_UNKNOWN instead
|
|
* of returning a non-standard type.
|
|
*/
|
|
sprintf(file->mime_type, "image/%s", dot + 1);
|
|
} else
|
|
/*
|
|
* Damn, no filename suffix...
|
|
*/
|
|
strncpy (file->mime_type, GP_MIME_UNKNOWN,
|
|
sizeof (file->mime_type));
|
|
|
|
if (stat (filename, &s) != -1) {
|
|
file->mtime = s.st_mtime;
|
|
} else {
|
|
file->mtime = time (NULL);
|
|
}
|
|
|
|
return (GP_OK);
|
|
}
|
|
|
|
|
|
/**
|
|
* @param file a #CameraFile
|
|
* @return a gphoto2 error code.
|
|
*
|
|
**/
|
|
int
|
|
gp_file_clean (CameraFile *file)
|
|
{
|
|
/*
|
|
* Frees a CameraFile's components, not the CameraFile itself.
|
|
* This is used to prep a CameraFile struct to be filled.
|
|
*/
|
|
|
|
C_PARAMS (file);
|
|
|
|
switch (file->accesstype) {
|
|
case GP_FILE_ACCESSTYPE_MEMORY:
|
|
free (file->data);
|
|
file->data = NULL;
|
|
file->size = 0;
|
|
break;
|
|
case GP_FILE_ACCESSTYPE_FD:
|
|
break;
|
|
default:break;
|
|
}
|
|
strcpy (file->name, "");
|
|
return (GP_OK);
|
|
}
|
|
|
|
/**
|
|
* @param destination a #CameraFile
|
|
* @param source a #CameraFile
|
|
* @return a gphoto2 error code.
|
|
*
|
|
**/
|
|
int
|
|
gp_file_copy (CameraFile *destination, CameraFile *source)
|
|
{
|
|
C_PARAMS (destination && source);
|
|
|
|
GP_LOG_D ("Copying '%s' onto '%s'...", source->name, destination->name);
|
|
|
|
/* struct members we can just copy. All generic ones, but not refcount. */
|
|
memcpy (destination->name, source->name, sizeof (source->name));
|
|
memcpy (destination->mime_type, source->mime_type, sizeof (source->mime_type));
|
|
destination->mtime = source->mtime;
|
|
|
|
if ((destination->accesstype == GP_FILE_ACCESSTYPE_MEMORY) &&
|
|
(source->accesstype == GP_FILE_ACCESSTYPE_MEMORY)) {
|
|
free (destination->data);
|
|
destination->data = NULL;
|
|
destination->size = source->size;
|
|
C_MEM (destination->data = malloc (sizeof (char) * source->size));
|
|
memcpy (destination->data, source->data, source->size);
|
|
return (GP_OK);
|
|
}
|
|
if ( (destination->accesstype == GP_FILE_ACCESSTYPE_MEMORY) &&
|
|
(source->accesstype == GP_FILE_ACCESSTYPE_FD)
|
|
) {
|
|
off_t offset;
|
|
off_t curread = 0;
|
|
|
|
free (destination->data);
|
|
destination->data = NULL;
|
|
|
|
if (-1 == lseek (source->fd, 0, SEEK_END)) {
|
|
if (errno == EBADF) return GP_ERROR_IO;
|
|
/* Might happen for pipes or sockets. Umm. Hard. */
|
|
/* FIXME */
|
|
}
|
|
if (-1 == (offset = lseek (source->fd, 0, SEEK_CUR))) {
|
|
/* should not happen if we passed the above case */
|
|
GP_LOG_E ("Encountered error %d lseekin to CUR.", errno);
|
|
return GP_ERROR_IO_READ;
|
|
}
|
|
if (-1 == lseek (source->fd, 0, SEEK_SET)) {
|
|
/* should not happen if we passed the above cases */
|
|
GP_LOG_E ("Encountered error %d lseekin to CUR.", errno);
|
|
return GP_ERROR_IO_READ;
|
|
}
|
|
destination->size = offset;
|
|
C_MEM (destination->data = malloc (offset));
|
|
while (curread < offset) {
|
|
ssize_t res = read (source->fd, destination->data+curread, offset-curread);
|
|
if (res == -1) {
|
|
free (destination->data);
|
|
GP_LOG_E ("Encountered error %d reading.", errno);
|
|
return GP_ERROR_IO_READ;
|
|
}
|
|
if (res == 0) {
|
|
free (destination->data);
|
|
GP_LOG_E ("No progress during reading.");
|
|
return GP_ERROR_IO_READ;
|
|
}
|
|
curread += res;
|
|
}
|
|
return GP_OK;
|
|
}
|
|
if ( (destination->accesstype == GP_FILE_ACCESSTYPE_FD) &&
|
|
(source->accesstype == GP_FILE_ACCESSTYPE_FD)
|
|
) {
|
|
char *data;
|
|
|
|
lseek (destination->fd, 0, SEEK_SET);
|
|
if (-1 == ftruncate (destination->fd, 0))
|
|
perror("ftruncate");
|
|
lseek (source->fd, 0, SEEK_SET);
|
|
C_MEM (data = malloc (65536));
|
|
while (1) {
|
|
ssize_t curwritten = 0;
|
|
ssize_t res;
|
|
|
|
res = read (source->fd, data, 65536);
|
|
if (res == -1) {
|
|
free (data);
|
|
return GP_ERROR_IO_READ;
|
|
}
|
|
if (res == 0)
|
|
break;
|
|
while (curwritten < res) {
|
|
ssize_t res2 = write (destination->fd, data+curwritten, res-curwritten);
|
|
if (res2 == -1) {
|
|
free (data);
|
|
return GP_ERROR_IO_WRITE;
|
|
}
|
|
if (res2 == 0)
|
|
break;
|
|
curwritten += res2;
|
|
}
|
|
if (res < 65536) /* end of file */
|
|
break;
|
|
}
|
|
free (data);
|
|
return GP_OK;
|
|
}
|
|
if ( (destination->accesstype == GP_FILE_ACCESSTYPE_FD) &&
|
|
(source->accesstype == GP_FILE_ACCESSTYPE_MEMORY)
|
|
) {
|
|
unsigned long curwritten = 0;
|
|
while (curwritten < source->size) {
|
|
int res = write (destination->fd, source->data+curwritten, source->size-curwritten);
|
|
|
|
if (res == -1)
|
|
return GP_ERROR_IO_WRITE;
|
|
if (!res) /* no progress? */
|
|
return GP_ERROR_IO_WRITE;
|
|
curwritten += res;
|
|
}
|
|
return GP_OK;
|
|
}
|
|
if ( (destination->accesstype == GP_FILE_ACCESSTYPE_HANDLER) &&
|
|
(source->accesstype == GP_FILE_ACCESSTYPE_MEMORY)
|
|
) {
|
|
uint64_t xsize = source->size;
|
|
unsigned long curwritten = 0;
|
|
|
|
destination->handler->size (destination->private, &xsize);
|
|
while (curwritten < source->size) {
|
|
uint64_t tmpsize = source->size - curwritten;
|
|
int res = destination->handler->write (destination->private, source->data+curwritten, &tmpsize);
|
|
|
|
if (res < GP_OK)
|
|
return res;
|
|
if (!tmpsize) /* no progress? */
|
|
return GP_ERROR_IO_WRITE;
|
|
curwritten += tmpsize;
|
|
}
|
|
return GP_OK;
|
|
}
|
|
GP_LOG_E ("Unhandled cases in gp_copy_file. Bad!");
|
|
return GP_ERROR;
|
|
}
|
|
|
|
/**
|
|
* @param file a #CameraFile
|
|
* @param name a pointer to a name string
|
|
* @return a gphoto2 error code.
|
|
*
|
|
**/
|
|
int
|
|
gp_file_get_name (CameraFile *file, const char **name)
|
|
{
|
|
C_PARAMS (file && name);
|
|
|
|
*name = file->name;
|
|
|
|
return (GP_OK);
|
|
}
|
|
|
|
/**
|
|
* @param file a #CameraFile
|
|
* @param basename the basename of the file
|
|
* @param type the gphoto type of the file
|
|
* @param newname the new name generated
|
|
* @return a gphoto2 error code.
|
|
*
|
|
* This function takes the basename and generates a filename out of
|
|
* it depending on the gphoto filetype and the mime type in the file.
|
|
* The gphoto filetype will be converted to a prefix, like
|
|
* thumb_ or raw_, the mimetype will replace the current suffix by
|
|
* a different one (if necessary).
|
|
*
|
|
* This can be used so that saving thumbnails or metadata will not
|
|
* overwrite the normal files.
|
|
**/
|
|
int
|
|
gp_file_get_name_by_type (CameraFile *file, const char *basename, CameraFileType type, char **newname)
|
|
{
|
|
char *prefix = NULL, *s, *new, *slash = NULL;
|
|
const char *suffix = NULL;
|
|
int i;
|
|
|
|
C_PARAMS (file && basename && newname);
|
|
*newname = NULL;
|
|
|
|
/* the easy case, always map 1:1, if it has a suffix already. */
|
|
if ((type == GP_FILE_TYPE_NORMAL) && strchr(basename,'.')) {
|
|
C_MEM (*newname = strdup (basename));
|
|
return GP_OK;
|
|
}
|
|
|
|
for (i=0;mime_table[i];i+=2) {
|
|
if (!strcmp (mime_table[i+1],file->mime_type)) {
|
|
suffix = mime_table[i];
|
|
break;
|
|
}
|
|
}
|
|
s = strrchr(basename,'.');
|
|
slash = strrchr(basename,'/');
|
|
if (slash > s) /* --filename foo.bar/foo */
|
|
s = NULL;
|
|
switch (type) {
|
|
case GP_FILE_TYPE_RAW: prefix = "raw_";break;
|
|
case GP_FILE_TYPE_EXIF: prefix = "exif_";break;
|
|
case GP_FILE_TYPE_PREVIEW: prefix = "thumb_";break;
|
|
case GP_FILE_TYPE_METADATA: prefix = "meta_";break;
|
|
case GP_FILE_TYPE_AUDIO: prefix = "audio_";break;
|
|
default: prefix = ""; break;
|
|
}
|
|
if (s) {
|
|
int xlen;
|
|
|
|
if (!suffix)
|
|
suffix = s+1;
|
|
|
|
C_MEM (new = calloc (strlen(prefix) + (s-basename+1) + strlen (suffix) + 1, 1));
|
|
|
|
xlen = strlen (prefix);
|
|
if (slash) {
|
|
memcpy (new, basename, slash-basename+1);
|
|
strcat (new, prefix);
|
|
memcpy (new + strlen (new), slash+1, s-slash+1);
|
|
} else {
|
|
strcpy (new, prefix);
|
|
memcpy (new + strlen (new), basename, s-basename+1);
|
|
}
|
|
new[xlen+(s-basename)+1]='\0';
|
|
strcat (new, suffix);
|
|
} else { /* no dot in basename? */
|
|
if (!suffix) suffix = "";
|
|
C_MEM (new = calloc (strlen(prefix) + strlen(basename) + 1 + strlen (suffix) + 1, 1));
|
|
if (slash) {
|
|
memcpy (new, basename, slash-basename+1); /* with / */
|
|
strcat (new, prefix);
|
|
strcat (new, slash+1);
|
|
} else {
|
|
strcpy (new, prefix);
|
|
strcat (new, basename);
|
|
}
|
|
if (strlen(suffix)) {
|
|
strcat (new, ".");
|
|
strcat (new, suffix);
|
|
}
|
|
}
|
|
*newname = new;
|
|
return (GP_OK);
|
|
}
|
|
|
|
|
|
/**
|
|
* @param file a #CameraFile
|
|
* @param mime_type a pointer to a MIME type string
|
|
* @return a gphoto2 error code.
|
|
*
|
|
**/
|
|
int
|
|
gp_file_get_mime_type (CameraFile *file, const char **mime_type)
|
|
{
|
|
C_PARAMS (file && mime_type);
|
|
|
|
*mime_type = file->mime_type;
|
|
|
|
return (GP_OK);
|
|
}
|
|
|
|
|
|
/**
|
|
* @param file a #CameraFile
|
|
* @param name a pointer to a MIME type string
|
|
* @return a gphoto2 error code.
|
|
*
|
|
**/
|
|
int
|
|
gp_file_set_name (CameraFile *file, const char *name)
|
|
{
|
|
C_PARAMS (file && name);
|
|
|
|
strncpy (file->name, name, sizeof (file->name));
|
|
|
|
return (GP_OK);
|
|
}
|
|
|
|
|
|
/**
|
|
* @param file a #CameraFile
|
|
* @param mime_type a MIME type string
|
|
* @return a gphoto2 error code.
|
|
*
|
|
**/
|
|
int
|
|
gp_file_set_mime_type (CameraFile *file, const char *mime_type)
|
|
{
|
|
C_PARAMS (file && mime_type);
|
|
|
|
strncpy (file->mime_type, mime_type, sizeof (file->mime_type));
|
|
|
|
return (GP_OK);
|
|
}
|
|
|
|
|
|
/**
|
|
* @param file a #CameraFile
|
|
* @return a gphoto2 error code.
|
|
*
|
|
**/
|
|
int
|
|
gp_file_detect_mime_type (CameraFile *file)
|
|
{
|
|
const char TIFF_SOI_MARKER[] = {(char) 0x49, (char) 0x49, (char) 0x2A,
|
|
(char) 0x00, (char) 0x08, '\0' };
|
|
const char JPEG_SOI_MARKER[] = {(char) 0xFF, (char) 0xD8, '\0' };
|
|
|
|
C_PARAMS (file);
|
|
|
|
switch (file->accesstype) {
|
|
case GP_FILE_ACCESSTYPE_MEMORY:
|
|
/* image/tiff */
|
|
if ((file->size >= 5) && !memcmp (file->data, TIFF_SOI_MARKER, 5))
|
|
CHECK_RESULT (gp_file_set_mime_type (file, GP_MIME_TIFF))
|
|
|
|
/* image/jpeg */
|
|
else if ((file->size >= 2) && !memcmp (file->data, JPEG_SOI_MARKER, 2))
|
|
CHECK_RESULT (gp_file_set_mime_type (file, GP_MIME_JPEG))
|
|
else
|
|
CHECK_RESULT (gp_file_set_mime_type (file, GP_MIME_RAW));
|
|
return GP_OK;
|
|
case GP_FILE_ACCESSTYPE_FD: {
|
|
char data[5];
|
|
off_t offset;
|
|
int res;
|
|
|
|
offset = lseek (file->fd, 0, SEEK_SET);
|
|
res = read (file->fd, data, sizeof(data));
|
|
if (res == -1)
|
|
return GP_ERROR_IO_READ;
|
|
/* image/tiff */
|
|
if ((res >= 5) && !memcmp (data, TIFF_SOI_MARKER, 5))
|
|
CHECK_RESULT (gp_file_set_mime_type (file, GP_MIME_TIFF))
|
|
|
|
/* image/jpeg */
|
|
else if ((res >= 2) && !memcmp (data, JPEG_SOI_MARKER, 2))
|
|
CHECK_RESULT (gp_file_set_mime_type (file, GP_MIME_JPEG))
|
|
else
|
|
CHECK_RESULT (gp_file_set_mime_type (file, GP_MIME_RAW));
|
|
lseek (file->fd, offset, SEEK_SET);
|
|
break;
|
|
}
|
|
default:
|
|
break;
|
|
}
|
|
return GP_OK;
|
|
}
|
|
|
|
|
|
/**
|
|
* @param file a #CameraFile
|
|
* @return a gphoto2 error code.
|
|
*
|
|
**/
|
|
int
|
|
gp_file_adjust_name_for_mime_type (CameraFile *file)
|
|
{
|
|
int x;
|
|
char *suffix;
|
|
const char *table[] = {
|
|
GP_MIME_RAW, "raw",
|
|
GP_MIME_JPEG, "jpg",
|
|
GP_MIME_NEF, "nef",
|
|
GP_MIME_CRW, "crw",
|
|
GP_MIME_CR2, "cr2",
|
|
GP_MIME_CR3, "cr3",
|
|
GP_MIME_PNG, "png",
|
|
GP_MIME_PPM, "ppm",
|
|
GP_MIME_PGM, "pgm",
|
|
GP_MIME_PNM, "pnm",
|
|
GP_MIME_TIFF, "tif",
|
|
GP_MIME_WAV, "wav",
|
|
GP_MIME_BMP, "bmp",
|
|
GP_MIME_AVI, "avi",
|
|
GP_MIME_ARW, "arw",
|
|
GP_MIME_RAF, "raf",
|
|
GP_MIME_TXT, "txt",
|
|
GP_MIME_QTKT, "qtk",
|
|
GP_MIME_QTKN, "qtk",
|
|
NULL};
|
|
|
|
C_PARAMS (file);
|
|
|
|
GP_LOG_D ("Adjusting file name for mime type '%s'...", file->mime_type);
|
|
for (x = 0; table[x]; x += 2)
|
|
if (!strcmp (file->mime_type, table[x])) {
|
|
|
|
/* Search the current suffix and erase it */
|
|
suffix = strrchr (file->name, '.');
|
|
if (suffix++)
|
|
*suffix = '\0';
|
|
strcat (file->name, table[x + 1]);
|
|
break;
|
|
}
|
|
GP_LOG_D ("Name adjusted to '%s'.", file->name);
|
|
|
|
return (GP_OK);
|
|
}
|
|
|
|
|
|
/**
|
|
* @param file a #CameraFile
|
|
* @param mtime
|
|
* @return a gphoto2 error code.
|
|
*
|
|
**/
|
|
int
|
|
gp_file_get_mtime (CameraFile *file, time_t *mtime)
|
|
{
|
|
C_PARAMS (file && mtime);
|
|
|
|
*mtime = file->mtime;
|
|
|
|
return (GP_OK);
|
|
}
|
|
|
|
|
|
/**
|
|
* @param file a #CameraFile
|
|
* @param mtime
|
|
* @return a gphoto2 error code.
|
|
*
|
|
**/
|
|
int
|
|
gp_file_set_mtime (CameraFile *file, time_t mtime)
|
|
{
|
|
C_PARAMS (file);
|
|
|
|
file->mtime = mtime;
|
|
|
|
return (GP_OK);
|
|
}
|