added function to save multiple result selection in restable into files

This commit is contained in:
Jean-Francois Dockes 2013-04-28 09:48:40 +02:00
parent 884234784d
commit caffe8b6f3
7 changed files with 288 additions and 46 deletions

140
src/qtgui/multisave.cpp Normal file
View File

@ -0,0 +1,140 @@
/* Copyright (C) 2005 J.F.Dockes
* 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 of the License, 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the
* Free Software Foundation, Inc.,
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include "autoconfig.h"
#include <stdio.h>
#include <string>
#include <set>
#include <sstream>
using namespace std;
#include <QWidget>
#include <QFileDialog>
#include <QMessageBox>
#include "recoll.h"
#include "multisave.h"
#include "smallut.h"
#include "debuglog.h"
#include "pathut.h"
#include "internfile.h"
const unsigned int maxlen = 200;
void multiSave(QWidget *p, vector<Rcl::Doc>& docs)
{
QFileDialog fdialog(p, p->tr("Create or choose save directory"));
fdialog.setAcceptMode(QFileDialog::AcceptSave);
fdialog.setFileMode(QFileDialog::Directory);
fdialog.setOption(QFileDialog::ShowDirsOnly);
if (fdialog.exec() == 0)
return;
QStringList dirl = fdialog.selectedFiles();
if (dirl.size() != 1) {
// Can't happen ?
QMessageBox::warning(0, "Recoll",
p->tr("Choose exactly one directory"));
return;
}
string dir((const char *)dirl[0].toLocal8Bit());
LOGDEB2(("multiSave: got dir %s\n", dir.c_str()));
/* Save doc to files in target directory. Issues:
- It is quite common to have docs in the array with the save
file names, e.g. all messages in a folder have the save file
name (the folder's).
- There is no warranty that the ipath is going to be acceptable
as a file name or interesting at all. We don't use it.
- We have to make sure the names don't end up too long.
If collisions occur, we add a numeric infix (e.g. somefile.23.pdf).
We never overwrite existing files and don't give the user an
option to do it (they can just as well save to an empty
directory and use the file manager to accomplish whatever they
want).
We don't try hard to protect against race-conditions
though. The existing file names are read before beginning the
save sequence, and collisions appearing after this are handled
by aborting. There is a window between existence check and creation
because idoctofile does not use O_EXCL
*/
set<string> existingNames;
string reason;
if (!readdir(dir, reason, existingNames)) {
QMessageBox::warning(0, "Recoll",
p->tr("Could not read directory: ") +
QString::fromLocal8Bit(reason.c_str()));
return;
}
set<string> toBeCreated;
vector<string> filenames;
for (vector<Rcl::Doc>::iterator it = docs.begin(); it != docs.end(); it++) {
string utf8fn;
it->getmeta(Rcl::Doc::keyfn, &utf8fn);
string suffix = path_suffix(utf8fn);
if (suffix.empty() || suffix.size() > 10) {
suffix = theconfig->getSuffixFromMimeType(it->mimetype);
}
string simple = path_basename(utf8fn, suffix);
if (simple.empty())
simple = "rclsave";
if (simple.size() > maxlen) {
simple = simple.substr(0, maxlen);
}
for (int vers = 0; ; vers++) {
ostringstream ss;
ss << simple;
if (vers)
ss << "." << vers;
if (!suffix.empty())
ss << suffix;
string fn =
(const char *)QString::fromUtf8(ss.str().c_str()).toLocal8Bit();
if (existingNames.find(fn) == existingNames.end() &&
toBeCreated.find(fn) == toBeCreated.end()) {
toBeCreated.insert(fn);
filenames.push_back(fn);
break;
}
}
}
for (unsigned int i = 0; i != docs.size(); i++) {
string fn = path_cat(dir, filenames[i]);
if (access(fn.c_str(), 0) == 0) {
QMessageBox::warning(0, "Recoll",
p->tr("Unexpected file name collision, "
"cancelling."));
return;
}
// There is still a race condition here, should we care ?
TempFile temp;// not used
if (!FileInterner::idocToFile(temp, fn, theconfig, docs[i])) {
QMessageBox::warning(0, "Recoll",
p->tr("Cannot extract document: ") +
QString::fromLocal8Bit(docs[i].url.c_str()) +
" | " +
QString::fromLocal8Bit(docs[i].ipath.c_str())
);
}
}
}

24
src/qtgui/multisave.h Normal file
View File

@ -0,0 +1,24 @@
/*
* 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 of the License, 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the
* Free Software Foundation, Inc.,
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#ifndef _MULTISAVE_W_H_INCLUDED_
#define _MULTISAVE_W_H_INCLUDED_
#include <QWidget>
extern void multiSave(QWidget *parent, vector<Rcl::Doc>& docs);
#endif /* _MULTISAVE_W_H_INCLUDED_ */

View File

@ -68,7 +68,6 @@ FORMS = \
firstidx.ui \
idxsched.ui \
listdialog.ui \
multisave.ui \
ptrans.ui \
rclmain.ui \
restable.ui \

View File

@ -47,6 +47,7 @@
#include "indexer.h"
#include "respopup.h"
#include "rclmain_w.h"
#include "multisave.h"
static const QKeySequence quitKeySeq("Ctrl+q");
static const QKeySequence closeKeySeq("Ctrl+w");
@ -759,6 +760,8 @@ void ResTable::createPopupMenu(const QPoint& pos)
QMenu *popup =
ResultPopup::create(this, m_ismainres? ResultPopup::isMain : 0,
m_model->getDocSource(), m_detaildoc);
popup->addAction(this->tr("Save selection to files"),
this, SLOT(menuSaveSelection()));
popup->popup(mapToGlobal(pos));
}
}
@ -780,6 +783,27 @@ void ResTable::menuSaveToFile()
emit docSaveToFileClicked(m_detaildoc);
}
void ResTable::menuSaveSelection()
{
if (m_model == 0 || m_model->getDocSource().isNull())
return;
QModelIndexList indexl = tableView->selectionModel()->selectedRows();
vector<Rcl::Doc> v;
for (int i = 0; i < indexl.size(); i++) {
Rcl::Doc doc;
if (m_model->getDocSource()->getDoc(indexl[i].row(), doc))
v.push_back(doc);
}
if (v.size() == 0) {
return;
} else if (v.size() == 1) {
emit docSaveToFileClicked(v[0]);
} else {
multiSave(this, v);
}
}
void ResTable::menuPreviewParent()
{
if (m_detaildocnum >= 0 && m_model &&

View File

@ -134,6 +134,7 @@ public slots:
virtual void createPopupMenu(const QPoint& pos);
virtual void menuPreview();
virtual void menuSaveToFile();
virtual void menuSaveSelection();
virtual void menuEdit();
virtual void menuCopyFN();
virtual void menuCopyURL();

View File

@ -21,6 +21,7 @@
#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <sys/param.h>
#include <pwd.h>
#include <math.h>
@ -48,11 +49,10 @@
#include <cstdlib>
#include <cstring>
#include <iostream>
#include <sstream>
#include <stack>
#ifndef NO_NAMESPACES
using std::string;
using std::stack;
#endif /* NO_NAMESPACES */
#include <set>
using namespace std;
#include "pathut.h"
#include "transcode.h"
@ -274,6 +274,14 @@ string path_basename(const string &s, const string &suff)
return simple;
}
string path_suffix(const string& s)
{
string::size_type dotp = s.rfind('.');
if (dotp == string::npos)
return string();
return s.substr(dotp);
}
string path_home()
{
uid_t uid = getuid();
@ -543,6 +551,48 @@ bool printableUrl(const string &fcharset, const string &in, string &out)
return true;
}
bool readdir(const string& dir, string& reason, set<string>& entries)
{
struct stat st;
int statret;
ostringstream msg;
DIR *d = 0;
statret = lstat(dir.c_str(), &st);
if (statret == -1) {
msg << "readdir: cant stat " << dir << " errno " << errno;
goto out;
}
if (!S_ISDIR(st.st_mode)) {
msg << "readdir: " << dir << " not a directory";
goto out;
}
if (access(dir.c_str(), R_OK) < 0) {
msg << "readdir: no read access to " << dir;
goto out;
}
d = opendir(dir.c_str());
if (d == 0) {
msg << "readdir: cant opendir " << dir << ", errno " << errno;
goto out;
}
struct dirent *ent;
while ((ent = readdir(d)) != 0) {
if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, ".."))
continue;
entries.insert(ent->d_name);
}
out:
if (d)
closedir(d);
reason = msg.str();
if (reason.empty())
return true;
return false;
}
// We do not want to mess with the pidfile content in the destructor:
// the lock might still be in use in a child process. In fact as much
// as we'd like to reset the pid inside the file when we're done, it

View File

@ -20,79 +20,83 @@
#include <string>
#include <vector>
#include <set>
#include "refcntr.h"
#ifndef NO_NAMESPACES
using std::string;
using std::vector;
#endif
/// Add a / at the end if none there yet.
extern void path_catslash(string &s);
extern void path_catslash(std::string &s);
/// Concatenate 2 paths
extern string path_cat(const string &s1, const string &s2);
extern std::string path_cat(const std::string &s1, const std::string &s2);
/// Get the simple file name (get rid of any directory path prefix
extern string path_getsimple(const string &s);
extern std::string path_getsimple(const std::string &s);
/// Simple file name + optional suffix stripping
extern string path_basename(const string &s, const string &suff=string());
extern std::string path_basename(const std::string &s,
const std::string &suff = std::string());
/// Component after last '.'
extern std::string path_suffix(const std::string &s);
/// Get the father directory
extern string path_getfather(const string &s);
extern std::string path_getfather(const std::string &s);
/// Get the current user's home directory
extern string path_home();
/// Expand ~ at the beginning of string
extern string path_tildexpand(const string &s);
extern std::string path_home();
/// Expand ~ at the beginning of std::string
extern std::string path_tildexpand(const std::string &s);
/// Use getcwd() to make absolute path if needed. Beware: ***this can fail***
/// we return an empty path in this case.
extern string path_absolute(const string &s);
extern std::string path_absolute(const std::string &s);
/// Clean up path by removing duplicated / and resolving ../ + make it absolute
extern string path_canon(const string &s);
extern std::string path_canon(const std::string &s);
/// Use glob(3) to return the file names matching pattern inside dir
extern vector<string> path_dirglob(const string &dir,
const string pattern);
extern std::vector<std::string> path_dirglob(const std::string &dir,
const std::string pattern);
/// Encode according to rfc 1738
extern string url_encode(const string& url,
string::size_type offs = 0);
extern std::string url_encode(const std::string& url,
std::string::size_type offs = 0);
/// Transcode to utf-8 if possible or url encoding, for display.
extern bool printableUrl(const string &fcharset,
const string &in, string &out);
extern bool printableUrl(const std::string &fcharset,
const std::string &in, std::string &out);
//// Convert to file path if url is like file://. This modifies the
//// input (and returns a copy for convenience)
extern string fileurltolocalpath(string url);
extern std::string fileurltolocalpath(std::string url);
/// Test for file:/// url
extern bool urlisfileurl(const string& url);
extern bool urlisfileurl(const std::string& url);
/// Return the host+path part of an url. This is not a general
/// routine, it does the right thing only in the recoll context
extern string url_gpath(const string& url);
extern std::string url_gpath(const std::string& url);
/// Stat parameter and check if it's a directory
extern bool path_isdir(const string& path);
extern bool path_isdir(const std::string& path);
/// Dump directory
extern bool readdir(const std::string& dir, std::string& reason,
std::set<std::string>& entries);
/** A small wrapper around statfs et al, to return percentage of disk
occupation */
bool fsocc(const string &path, int *pc, // Percent occupied
bool fsocc(const std::string &path, int *pc, // Percent occupied
long *avmbs = 0 // Mbs available to non-superuser
);
/// Retrieve the temp dir location: $RECOLL_TMPDIR else $TMPDIR else /tmp
extern const string& tmplocation();
extern const std::string& tmplocation();
/// Create temporary directory (inside the temp location)
extern bool maketmpdir(string& tdir, string& reason);
extern bool maketmpdir(std::string& tdir, std::string& reason);
/// mkdir -p
extern bool makepath(const string& path);
extern bool makepath(const std::string& path);
/// Temporary file class
class TempFileInternal {
public:
TempFileInternal(const string& suffix);
TempFileInternal(const std::string& suffix);
~TempFileInternal();
const char *filename()
{
return m_filename.c_str();
}
const string &getreason()
const std::string &getreason()
{
return m_reason;
}
@ -105,8 +109,8 @@ public:
return !m_filename.empty();
}
private:
string m_filename;
string m_reason;
std::string m_filename;
std::string m_reason;
bool m_noremove;
};
@ -118,13 +122,13 @@ public:
TempDir();
~TempDir();
const char *dirname() {return m_dirname.c_str();}
const string &getreason() {return m_reason;}
const std::string &getreason() {return m_reason;}
bool ok() {return !m_dirname.empty();}
/// Recursively delete contents but not self.
bool wipe();
private:
string m_dirname;
string m_reason;
std::string m_dirname;
std::string m_reason;
TempDir(const TempDir &) {}
TempDir& operator=(const TempDir &) {return *this;};
};
@ -134,7 +138,7 @@ private:
/// the freebsd code if it was available elsewhere
class Pidfile {
public:
Pidfile(const string& path) : m_path(path), m_fd(-1) {}
Pidfile(const std::string& path) : m_path(path), m_fd(-1) {}
~Pidfile();
/// Open/create the pid file.
/// @return 0 if ok, > 0 for pid of existing process, -1 for other error.
@ -146,11 +150,11 @@ public:
int close();
/// Delete the pid file
int remove();
const string& getreason() {return m_reason;}
const std::string& getreason() {return m_reason;}
private:
string m_path;
std::string m_path;
int m_fd;
string m_reason;
std::string m_reason;
pid_t read_pid();
int flopen();
};
@ -160,7 +164,7 @@ private:
// Freedesktop thumbnail standard path routine
// On return, path will have the appropriate value in all cases,
// returns true if the file already exists
extern bool thumbPathForUrl(const string& url, int size, string& path);
extern bool thumbPathForUrl(const std::string& url, int size, std::string& path);
// Must be called in main thread before starting other threads
extern void pathut_init_mt();