a b/src/qtgui/multisave.cpp
1
/* Copyright (C) 2005 J.F.Dockes
2
 *   This program is free software; you can redistribute it and/or modify
3
 *   it under the terms of the GNU General Public License as published by
4
 *   the Free Software Foundation; either version 2 of the License, or
5
 *   (at your option) any later version.
6
 *
7
 *   This program is distributed in the hope that it will be useful,
8
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
9
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10
 *   GNU General Public License for more details.
11
 *
12
 *   You should have received a copy of the GNU General Public License
13
 *   along with this program; if not, write to the
14
 *   Free Software Foundation, Inc.,
15
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
16
 */
17
#include "autoconfig.h"
18
19
#include <stdio.h>
20
21
#include <string>
22
#include <set>
23
#include <sstream>
24
using namespace std;
25
26
#include <QWidget>
27
#include <QFileDialog>
28
#include <QMessageBox>
29
30
#include "recoll.h"
31
#include "multisave.h"
32
#include "smallut.h"
33
#include "debuglog.h"
34
#include "pathut.h"
35
#include "internfile.h"
36
37
const unsigned int maxlen = 200;
38
39
void multiSave(QWidget *p, vector<Rcl::Doc>& docs)
40
{
41
    QFileDialog fdialog(p, p->tr("Create or choose save directory"));
42
    fdialog.setAcceptMode(QFileDialog::AcceptSave);
43
    fdialog.setFileMode(QFileDialog::Directory);
44
    fdialog.setOption(QFileDialog::ShowDirsOnly);
45
    if (fdialog.exec() == 0) 
46
  return;
47
    QStringList dirl = fdialog.selectedFiles();
48
    if (dirl.size() != 1) {
49
  // Can't happen ?
50
  QMessageBox::warning(0, "Recoll",
51
               p->tr("Choose exactly one directory"));
52
  return;
53
    }
54
    string dir((const char *)dirl[0].toLocal8Bit());
55
    LOGDEB2(("multiSave: got dir %s\n", dir.c_str()));
56
57
    /* Save doc to files in target directory. Issues:
58
       - It is quite common to have docs in the array with the save
59
         file names, e.g. all messages in a folder have the save file
60
         name (the folder's).
61
       - There is no warranty that the ipath is going to be acceptable
62
         as a file name or interesting at all. We don't use it. 
63
       - We have to make sure the names don't end up too long.
64
65
       If collisions occur, we add a numeric infix (e.g. somefile.23.pdf).
66
67
       We never overwrite existing files and don't give the user an
68
       option to do it (they can just as well save to an empty
69
       directory and use the file manager to accomplish whatever they
70
       want).
71
72
       We don't try hard to protect against race-conditions
73
       though. The existing file names are read before beginning the
74
       save sequence, and collisions appearing after this are handled
75
       by aborting. There is a window between existence check and creation
76
       because idoctofile does not use O_EXCL
77
    */
78
    set<string> existingNames;
79
    string reason;
80
    if (!readdir(dir, reason, existingNames)) {
81
  QMessageBox::warning(0, "Recoll",
82
               p->tr("Could not read directory: ") +
83
               QString::fromLocal8Bit(reason.c_str()));
84
  return;
85
    }
86
87
    set<string> toBeCreated;
88
    vector<string> filenames;
89
    for (vector<Rcl::Doc>::iterator it = docs.begin(); it != docs.end(); it++) {
90
  string utf8fn;
91
  it->getmeta(Rcl::Doc::keyfn, &utf8fn);
92
  string suffix = path_suffix(utf8fn);
93
  if (suffix.empty() || suffix.size() > 10) {
94
      suffix = theconfig->getSuffixFromMimeType(it->mimetype);
95
  }
96
  string simple = path_basename(utf8fn, suffix);
97
  if (simple.empty())
98
      simple = "rclsave";
99
  if (simple.size() > maxlen) {
100
      simple = simple.substr(0, maxlen);
101
  }
102
  for  (int vers = 0; ; vers++) {
103
      ostringstream ss;
104
      ss << simple;
105
      if (vers)
106
      ss << "." << vers;
107
      if (!suffix.empty())
108
      ss << suffix;
109
110
      string fn = 
111
      (const char *)QString::fromUtf8(ss.str().c_str()).toLocal8Bit();
112
      if (existingNames.find(fn) == existingNames.end() &&
113
      toBeCreated.find(fn) == toBeCreated.end()) {
114
      toBeCreated.insert(fn);
115
      filenames.push_back(fn);
116
      break;
117
      }
118
  }
119
    }
120
    
121
    for (unsigned int i = 0; i != docs.size(); i++) {
122
  string fn = path_cat(dir, filenames[i]);
123
  if (access(fn.c_str(), 0) == 0) {
124
      QMessageBox::warning(0, "Recoll",
125
               p->tr("Unexpected file name collision, "
126
                     "cancelling."));
127
      return;
128
  }
129
  // There is still a race condition here, should we care ?
130
  TempFile temp;// not used
131
  if (!FileInterner::idocToFile(temp, fn, theconfig, docs[i])) {
132
      QMessageBox::warning(0, "Recoll",
133
               p->tr("Cannot extract document: ") +
134
               QString::fromLocal8Bit(docs[i].url.c_str()) +
135
               " | " +
136
               QString::fromLocal8Bit(docs[i].ipath.c_str())
137
      );
138
  }
139
    }
140
}