|
|
|
/* ============================================================
|
|
|
|
*
|
|
|
|
* This file is a part of digiKam project
|
|
|
|
* http://www.digikam.org
|
|
|
|
*
|
|
|
|
* Date : 2004-12-21
|
|
|
|
* Description : USB Mass Storage camera interface
|
|
|
|
*
|
|
|
|
* Copyright (C) 2004-2005 by Renchi Raju <renchi@pooh.tam.uiuc.edu>
|
|
|
|
* Copyright (C) 2005-2008 by Gilles Caulier <caulier dot gilles at gmail dot com>
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it
|
|
|
|
* and/or modify it under the terms of the GNU General
|
|
|
|
* Public License as published by the Free Software Foundation;
|
|
|
|
* either version 2, or (at your option)
|
|
|
|
* any later version.
|
|
|
|
*
|
|
|
|
* This program 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 General Public License for more details.
|
|
|
|
*
|
|
|
|
* ============================================================ */
|
|
|
|
|
|
|
|
// C Ansi includes.
|
|
|
|
|
|
|
|
extern "C"
|
|
|
|
{
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/stat.h>
|
|
|
|
#include <unistd.h>
|
|
|
|
#include <utime.h>
|
|
|
|
}
|
|
|
|
|
|
|
|
// TQt includes.
|
|
|
|
|
|
|
|
#include <tqdir.h>
|
|
|
|
#include <tqfileinfo.h>
|
|
|
|
#include <tqfile.h>
|
|
|
|
#include <tqstringlist.h>
|
|
|
|
#include <tqdeepcopy.h>
|
|
|
|
#include <tqwmatrix.h>
|
|
|
|
|
|
|
|
// KDE includes.
|
|
|
|
|
|
|
|
#include <klocale.h>
|
|
|
|
#include <kfilemetainfo.h>
|
|
|
|
|
|
|
|
// LibKDcraw includes.
|
|
|
|
|
|
|
|
#include <libkdcraw/kdcraw.h>
|
|
|
|
|
|
|
|
// Local includes.
|
|
|
|
|
|
|
|
#include "ddebug.h"
|
|
|
|
#include "dimg.h"
|
|
|
|
#include "dmetadata.h"
|
|
|
|
#include "umscamera.h"
|
|
|
|
|
|
|
|
namespace Digikam
|
|
|
|
{
|
|
|
|
|
|
|
|
UMSCamera::UMSCamera(const TQString& title, const TQString& model, const TQString& port, const TQString& path)
|
|
|
|
: DKCamera(title, model, port, path)
|
|
|
|
{
|
|
|
|
m_cancel = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
UMSCamera::~UMSCamera()
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
bool UMSCamera::doConnect()
|
|
|
|
{
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void UMSCamera::cancel()
|
|
|
|
{
|
|
|
|
// set the cancel flag
|
|
|
|
m_cancel = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void UMSCamera::getAllFolders(const TQString& folder, TQStringList& subFolderList)
|
|
|
|
{
|
|
|
|
m_cancel = false;
|
|
|
|
subFolderList.clear();
|
|
|
|
subFolderList.append(folder);
|
|
|
|
listFolders(folder, subFolderList);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool UMSCamera::getItemsInfoList(const TQString& folder, GPItemInfoList& infoList, bool getImageDimensions)
|
|
|
|
{
|
|
|
|
m_cancel = false;
|
|
|
|
infoList.clear();
|
|
|
|
|
|
|
|
TQDir dir(folder);
|
|
|
|
dir.setFilter(TQDir::Files);
|
|
|
|
|
|
|
|
const TQFileInfoList *list = dir.entryInfoList();
|
|
|
|
if (!list)
|
|
|
|
return false;
|
|
|
|
|
|
|
|
TQFileInfoListIterator it(*list);
|
|
|
|
TQFileInfo *fi;
|
|
|
|
TQFileInfo thmlo, thmup;
|
|
|
|
DMetadata meta;
|
|
|
|
|
|
|
|
while ((fi = it.current()) != 0 && !m_cancel)
|
|
|
|
{
|
|
|
|
++it;
|
|
|
|
|
|
|
|
TQString mime = mimeType(fi->extension(false).lower());
|
|
|
|
|
|
|
|
if (!mime.isEmpty())
|
|
|
|
{
|
|
|
|
TQSize dims;
|
|
|
|
TQDateTime dt;
|
|
|
|
GPItemInfo info;
|
|
|
|
thmlo.setFile(folder + TQString("/") + fi->baseName() + TQString(".thm"));
|
|
|
|
thmup.setFile(folder + TQString("/") + fi->baseName() + TQString(".THM"));
|
|
|
|
|
|
|
|
if (thmlo.exists())
|
|
|
|
{
|
|
|
|
// Try thumbnail sidecar files with lowercase extension.
|
|
|
|
meta.load(thmlo.filePath());
|
|
|
|
dt = meta.getImageDateTime();
|
|
|
|
dims = meta.getImageDimensions();
|
|
|
|
}
|
|
|
|
else if (thmup.exists())
|
|
|
|
{
|
|
|
|
// Try thumbnail sidecar files with uppercase extension.
|
|
|
|
meta.load(thmup.filePath());
|
|
|
|
dt = meta.getImageDateTime();
|
|
|
|
dims = meta.getImageDimensions();
|
|
|
|
}
|
|
|
|
else if (mime == TQString("image/x-raw"))
|
|
|
|
{
|
|
|
|
// If no thumbnail sidecar file available , try to load image metadata with Raw files.
|
|
|
|
meta.load(fi->filePath());
|
|
|
|
dt = meta.getImageDateTime();
|
|
|
|
dims = meta.getImageDimensions();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
meta.load(fi->filePath());
|
|
|
|
dt = meta.getImageDateTime();
|
|
|
|
dims = meta.getImageDimensions();
|
|
|
|
|
|
|
|
if (dims.isNull())
|
|
|
|
{
|
|
|
|
// In all others case, try KFileMetaInfo.
|
|
|
|
KFileMetaInfo kmeta(fi->filePath());
|
|
|
|
if (kmeta.isValid())
|
|
|
|
{
|
|
|
|
if (kmeta.containsGroup("Jpeg EXIF Data"))
|
|
|
|
dims = kmeta.group("Jpeg EXIF Data").item("Dimensions").value().toSize();
|
|
|
|
else if (kmeta.containsGroup("General"))
|
|
|
|
dims = kmeta.group("General").item("Dimensions").value().toSize();
|
|
|
|
else if (kmeta.containsGroup("Technical"))
|
|
|
|
dims = kmeta.group("Technical").item("Dimensions").value().toSize();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dt.isNull())
|
|
|
|
{
|
|
|
|
// If date is not found in metadata, use file time stamp
|
|
|
|
dt = fi->created();
|
|
|
|
}
|
|
|
|
|
|
|
|
info.name = fi->fileName();
|
|
|
|
info.folder = !folder.endsWith("/") ? folder + TQString("/") : folder;
|
|
|
|
info.mime = mime;
|
|
|
|
info.mtime = dt.toTime_t();
|
|
|
|
info.size = fi->size();
|
|
|
|
info.width = getImageDimensions ? dims.width() : -1;
|
|
|
|
info.height = getImageDimensions ? dims.height() : -1;
|
|
|
|
info.downloaded = GPItemInfo::DownloadUnknow;
|
|
|
|
info.readPermissions = fi->isReadable();
|
|
|
|
info.writePermissions = fi->isWritable();
|
|
|
|
|
|
|
|
infoList.append(info);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool UMSCamera::getThumbnail(const TQString& folder, const TQString& itemName, TQImage& thumbnail)
|
|
|
|
{
|
|
|
|
m_cancel = false;
|
|
|
|
|
|
|
|
// JPEG files: try to get thumbnail from Exif data.
|
|
|
|
|
|
|
|
DMetadata metadata(TQFile::encodeName(folder + TQString("/") + itemName));
|
|
|
|
thumbnail = metadata.getExifThumbnail(true);
|
|
|
|
if (!thumbnail.isNull())
|
|
|
|
return true;
|
|
|
|
|
|
|
|
// RAW files : try to extract embedded thumbnail using dcraw
|
|
|
|
|
|
|
|
KDcrawIface::KDcraw::loadDcrawPreview(thumbnail, TQString(folder + TQString("/") + itemName));
|
|
|
|
if (!thumbnail.isNull())
|
|
|
|
return true;
|
|
|
|
|
|
|
|
// THM files: try to get thumbnail from '.thm' files if we didn't manage to get
|
|
|
|
// thumbnail from Exif. Any cameras provides *.thm files like JPEG files with RAW/video files.
|
|
|
|
// Using this way speed up thumbnailization and limit data transfered between camera and computer.
|
|
|
|
// NOTE: the thumbnail extracted with this method can provide a poor quality preview.
|
|
|
|
|
|
|
|
TQFileInfo fi(folder + TQString("/") + itemName);
|
|
|
|
|
|
|
|
if (thumbnail.load(folder + TQString("/") + fi.baseName() + TQString(".thm"))) // Lowercase
|
|
|
|
{
|
|
|
|
if (!thumbnail.isNull())
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
else if (thumbnail.load(folder + TQString("/") + fi.baseName() + TQString(".THM"))) // Uppercase
|
|
|
|
{
|
|
|
|
if (!thumbnail.isNull())
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Finaly, we trying to get thumbnail using DImg API (slow).
|
|
|
|
|
|
|
|
DImg dimgThumb(TQCString(TQFile::encodeName(folder + TQString("/") + itemName)));
|
|
|
|
|
|
|
|
if (!dimgThumb.isNull())
|
|
|
|
{
|
|
|
|
thumbnail = dimgThumb.copyTQImage();
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool UMSCamera::getExif(const TQString&, const TQString&, char **, int&)
|
|
|
|
{
|
|
|
|
// not necessary to implement this. read it directly from the file
|
|
|
|
// (done in camera controller)
|
|
|
|
DWarning() << "exif implemented yet in camera controller" << endl;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool UMSCamera::downloadItem(const TQString& folder, const TQString& itemName, const TQString& saveFile)
|
|
|
|
{
|
|
|
|
m_cancel = false;
|
|
|
|
TQString src = folder + TQString("/") + itemName;
|
|
|
|
TQString dest = saveFile;
|
|
|
|
|
|
|
|
TQFile sFile(src);
|
|
|
|
TQFile dFile(dest);
|
|
|
|
|
|
|
|
if ( !sFile.open(IO_ReadOnly) )
|
|
|
|
{
|
|
|
|
DWarning() << "Failed to open source file for reading: "
|
|
|
|
<< src << endl;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !dFile.open(IO_WriteOnly) )
|
|
|
|
{
|
|
|
|
sFile.close();
|
|
|
|
DWarning() << "Failed to open dest file for writing: "
|
|
|
|
<< dest << endl;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const int MAX_IPC_SIZE = (1024*32);
|
|
|
|
char buffer[MAX_IPC_SIZE];
|
|
|
|
|
|
|
|
TQ_LONG len;
|
|
|
|
while ((len = sFile.readBlock(buffer, MAX_IPC_SIZE)) != 0 && !m_cancel)
|
|
|
|
{
|
|
|
|
if (len == -1 || dFile.writeBlock(buffer, (TQ_ULONG)len) != len)
|
|
|
|
{
|
|
|
|
sFile.close();
|
|
|
|
dFile.close();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sFile.close();
|
|
|
|
dFile.close();
|
|
|
|
|
|
|
|
// set the file modification time of the downloaded file to that
|
|
|
|
// of the original file
|
|
|
|
struct stat st;
|
|
|
|
::stat(TQFile::encodeName(src), &st);
|
|
|
|
|
|
|
|
struct utimbuf ut;
|
|
|
|
ut.modtime = st.st_mtime;
|
|
|
|
ut.actime = st.st_atime;
|
|
|
|
|
|
|
|
::utime(TQFile::encodeName(dest), &ut);
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool UMSCamera::setLockItem(const TQString& folder, const TQString& itemName, bool lock)
|
|
|
|
{
|
|
|
|
TQString src = folder + TQString("/") + itemName;
|
|
|
|
|
|
|
|
if (lock)
|
|
|
|
{
|
|
|
|
// Lock the file to set read only flag
|
|
|
|
if (::chmod(TQFile::encodeName(src), S_IREAD) == -1)
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// Unlock the file to set read/write flag
|
|
|
|
if (::chmod(TQFile::encodeName(src), S_IREAD | S_IWRITE) == -1)
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool UMSCamera::deleteItem(const TQString& folder, const TQString& itemName)
|
|
|
|
{
|
|
|
|
m_cancel = false;
|
|
|
|
|
|
|
|
// Any camera provide THM (thumbnail) file with real image. We need to remove it also.
|
|
|
|
|
|
|
|
TQFileInfo fi(folder + TQString("/") + itemName);
|
|
|
|
|
|
|
|
TQFileInfo thmLo(folder + TQString("/") + fi.baseName() + ".thm"); // Lowercase
|
|
|
|
|
|
|
|
if (thmLo.exists())
|
|
|
|
::unlink(TQFile::encodeName(thmLo.filePath()));
|
|
|
|
|
|
|
|
TQFileInfo thmUp(folder + TQString("/") + fi.baseName() + ".THM"); // Uppercase
|
|
|
|
|
|
|
|
if (thmUp.exists())
|
|
|
|
::unlink(TQFile::encodeName(thmUp.filePath()));
|
|
|
|
|
|
|
|
// Remove the real image.
|
|
|
|
return (::unlink(TQFile::encodeName(folder + TQString("/") + itemName)) == 0);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool UMSCamera::uploadItem(const TQString& folder, const TQString& itemName, const TQString& localFile,
|
|
|
|
GPItemInfo& info, bool getImageDimensions)
|
|
|
|
{
|
|
|
|
m_cancel = false;
|
|
|
|
TQString dest = folder + TQString("/") + itemName;
|
|
|
|
TQString src = localFile;
|
|
|
|
|
|
|
|
TQFile sFile(src);
|
|
|
|
TQFile dFile(dest);
|
|
|
|
|
|
|
|
if ( !sFile.open(IO_ReadOnly) )
|
|
|
|
{
|
|
|
|
DWarning() << "Failed to open source file for reading: "
|
|
|
|
<< src << endl;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( !dFile.open(IO_WriteOnly) )
|
|
|
|
{
|
|
|
|
sFile.close();
|
|
|
|
DWarning() << "Failed to open dest file for writing: "
|
|
|
|
<< dest << endl;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
const int MAX_IPC_SIZE = (1024*32);
|
|
|
|
char buffer[MAX_IPC_SIZE];
|
|
|
|
|
|
|
|
TQ_LONG len;
|
|
|
|
while ((len = sFile.readBlock(buffer, MAX_IPC_SIZE)) != 0 && !m_cancel)
|
|
|
|
{
|
|
|
|
if (len == -1 || dFile.writeBlock(buffer, (TQ_ULONG)len) == -1)
|
|
|
|
{
|
|
|
|
sFile.close();
|
|
|
|
dFile.close();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
sFile.close();
|
|
|
|
dFile.close();
|
|
|
|
|
|
|
|
// set the file modification time of the uploaded file to that
|
|
|
|
// of the original file
|
|
|
|
struct stat st;
|
|
|
|
::stat(TQFile::encodeName(src), &st);
|
|
|
|
|
|
|
|
struct utimbuf ut;
|
|
|
|
ut.modtime = st.st_mtime;
|
|
|
|
ut.actime = st.st_atime;
|
|
|
|
|
|
|
|
::utime(TQFile::encodeName(dest), &ut);
|
|
|
|
|
|
|
|
// Get new camera item information.
|
|
|
|
|
|
|
|
DMetadata meta;
|
|
|
|
TQFileInfo fi(dest);
|
|
|
|
TQString mime = mimeType(fi.extension(false).lower());
|
|
|
|
|
|
|
|
if (!mime.isEmpty())
|
|
|
|
{
|
|
|
|
TQSize dims;
|
|
|
|
TQDateTime dt;
|
|
|
|
|
|
|
|
if (mime == TQString("image/x-raw"))
|
|
|
|
{
|
|
|
|
// Try to load image metadata with Raw files.
|
|
|
|
meta.load(fi.filePath());
|
|
|
|
dt = meta.getImageDateTime();
|
|
|
|
dims = meta.getImageDimensions();
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
meta.load(fi.filePath());
|
|
|
|
dt = meta.getImageDateTime();
|
|
|
|
dims = meta.getImageDimensions();
|
|
|
|
|
|
|
|
if (dims.isNull())
|
|
|
|
{
|
|
|
|
// In all others case, try KFileMetaInfo.
|
|
|
|
KFileMetaInfo kmeta(fi.filePath());
|
|
|
|
if (kmeta.isValid())
|
|
|
|
{
|
|
|
|
if (kmeta.containsGroup("Jpeg EXIF Data"))
|
|
|
|
dims = kmeta.group("Jpeg EXIF Data").item("Dimensions").value().toSize();
|
|
|
|
else if (kmeta.containsGroup("General"))
|
|
|
|
dims = kmeta.group("General").item("Dimensions").value().toSize();
|
|
|
|
else if (kmeta.containsGroup("Technical"))
|
|
|
|
dims = kmeta.group("Technical").item("Dimensions").value().toSize();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (dt.isNull())
|
|
|
|
{
|
|
|
|
// If date is not found in metadata, use file time stamp
|
|
|
|
dt = fi.created();
|
|
|
|
}
|
|
|
|
|
|
|
|
info.name = fi.fileName();
|
|
|
|
info.folder = !folder.endsWith("/") ? folder + TQString("/") : folder;
|
|
|
|
info.mime = mime;
|
|
|
|
info.mtime = dt.toTime_t();
|
|
|
|
info.size = fi.size();
|
|
|
|
info.width = getImageDimensions ? dims.width() : -1;
|
|
|
|
info.height = getImageDimensions ? dims.height() : -1;
|
|
|
|
info.downloaded = GPItemInfo::DownloadUnknow;
|
|
|
|
info.readPermissions = fi.isReadable();
|
|
|
|
info.writePermissions = fi.isWritable();
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
void UMSCamera::listFolders(const TQString& folder, TQStringList& subFolderList)
|
|
|
|
{
|
|
|
|
if (m_cancel)
|
|
|
|
return;
|
|
|
|
|
|
|
|
TQDir dir(folder);
|
|
|
|
dir.setFilter(TQDir::Dirs|TQDir::Executable);
|
|
|
|
|
|
|
|
const TQFileInfoList *list = dir.entryInfoList();
|
|
|
|
if (!list)
|
|
|
|
return;
|
|
|
|
|
|
|
|
TQFileInfoListIterator it( *list );
|
|
|
|
TQFileInfo *fi;
|
|
|
|
|
|
|
|
while ((fi = it.current()) != 0 && !m_cancel)
|
|
|
|
{
|
|
|
|
++it;
|
|
|
|
|
|
|
|
if (fi->fileName() == "." || fi->fileName() == "..")
|
|
|
|
continue;
|
|
|
|
|
|
|
|
TQString subfolder = folder + TQString(folder.endsWith("/") ? "" : "/") + fi->fileName();
|
|
|
|
subFolderList.append(subfolder);
|
|
|
|
listFolders(subfolder, subFolderList);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool UMSCamera::cameraSummary(TQString& summary)
|
|
|
|
{
|
|
|
|
summary = TQString(i18n("<b>Mounted Camera</b> driver for USB/IEEE1394 mass storage cameras and "
|
|
|
|
"Flash disk card readers.<br><br>"));
|
|
|
|
|
|
|
|
summary.append(i18n("Title: %1<br>"
|
|
|
|
"Model: %2<br>"
|
|
|
|
"Port: %3<br>"
|
|
|
|
"Path: %4<br>")
|
|
|
|
.tqarg(title())
|
|
|
|
.tqarg(model())
|
|
|
|
.tqarg(port())
|
|
|
|
.tqarg(path()));
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool UMSCamera::cameraManual(TQString& manual)
|
|
|
|
{
|
|
|
|
manual = TQString(i18n("For more information about the <b>Mounted Camera</b> driver, "
|
|
|
|
"please read <b>Supported Digital Still "
|
|
|
|
"Cameras</b> section in the digiKam manual."));
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool UMSCamera::cameraAbout(TQString& about)
|
|
|
|
{
|
|
|
|
about = TQString(i18n("The <b>Mounted Camera</b> driver is a simple interface to a camera disk "
|
|
|
|
"mounted locally on your system.<br><br>"
|
|
|
|
"It doesn't use libgphoto2 drivers.<br><br>"
|
|
|
|
"To report any problems with this driver, please contact the digiKam team at:<br><br>"
|
|
|
|
"http://www.digikam.org/?q=contact"));
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
} // namespace Digikam
|