1 /*
2 Bacula(R) - The Network Backup Solution
3
4 Copyright (C) 2000-2020 Kern Sibbald
5
6 The original author of Bacula is Kern Sibbald, with contributions
7 from many others, a complete list can be found in the file AUTHORS.
8
9 You may use this file and others of this release according to the
10 license defined in the LICENSE file, which includes the Affero General
11 Public License, v3.0 ("AGPLv3") and some additional permissions and
12 terms pursuant to its AGPLv3 Section 7.
13
14 This notice must be preserved when any source code is
15 conveyed and/or propagated.
16
17 Bacula(R) is a registered trademark of Kern Sibbald.
18 */
19 /*
20 * Restore Wizard: File selection page
21 *
22 * Written by Norbert Bizet, May MMXVII
23 *
24 */
25 #include "common.h"
26 #include "fileselectwizardpage.h"
27 #include "filesmodel.h"
28 #include "ui_fileselectwizardpage.h"
29 #include "restorewizard.h"
30 #include "task.h"
31 #include <QStandardItemModel>
32 #include <QShortcut>
33
FileSelectWizardPage(QWidget * parent)34 FileSelectWizardPage::FileSelectWizardPage(QWidget *parent) :
35 QWizardPage(parent),
36 m_currentSourceId(0),
37 m_currentPathStr(""),
38 ui(new Ui::FileSelectWizardPage),
39 src_files_model(new FileSourceModel),
40 dest_files_model(new FileDestModel),
41 m_filterTimer(new QTimer),
42 res(NULL),
43 need_optimize(true)
44 {
45 ui->setupUi(this);
46
47 /* keep track of the current source view selection */
48 registerField("currentSourceId", this, "currentSourceId", "currentSourceIdChanged");
49 registerField("currentPathStr", this, "currentPathStr", "currentPathStrChanged");
50 registerField("fileFilter", ui->FilterEdit);
51 /* usefull to the following pages */
52 registerField("jobIds", this, "jobIds", "jobIdsChanged");
53 registerField("fileIds", this, "fileIds", "fileIdsChanged");
54 registerField("dirIds", this, "dirIds", "dirIdsChanged");
55 registerField("hardlinks", this, "hardlinks", "hardlinksChanged");
56 registerField("pluginIds", this, "pluginIds", "pluginIdsChanged");
57 registerField("pluginNames", this, "pluginNames", "pluginNamesChanged");
58
59 QStringList headers;
60 headers << tr("Name") << tr("Size") << tr("Date");
61
62 ui->sourceTableView->setModel(src_files_model);
63 //ui->sourceTableView->setLayoutMode(QListView::Batched);
64 //ui->sourceTableView->setBatchSize(BATCH_SIZE);
65 src_files_model->setHorizontalHeaderLabels(headers);
66 connect(ui->sourceTableView, SIGNAL(activated(const QModelIndex &)), this, SLOT(changeCurrentFolder(const QModelIndex&)));
67 connect(ui->sourceTableView, SIGNAL(activated(const QModelIndex &)), this, SLOT(updateSourceModel()));
68
69 ui->destTableView->setModel(dest_files_model);
70 dest_files_model->setHorizontalHeaderLabels(headers);
71 connect(dest_files_model, SIGNAL(rowsInserted(const QModelIndex &, int, int)), this, SIGNAL(completeChanged()));
72 connect(dest_files_model, SIGNAL(rowsRemoved(const QModelIndex &, int, int)), this, SIGNAL(completeChanged()));
73
74 connect(ui->filePathComboPath, SIGNAL(activated(const QString &)), this, SLOT(changeCurrentText(const QString &)));
75 connect(ui->filePathComboPath, SIGNAL(activated(const QString &)), this, SLOT(updateSourceModel()));
76
77 connect(ui->FilterEdit, SIGNAL(textChanged(const QString &)), this, SLOT(delayedFilter()));
78
79 m_filterTimer->setSingleShot(true);
80 connect(m_filterTimer, SIGNAL(timeout()), this, SLOT(unFreezeSrcView()));
81 connect(m_filterTimer, SIGNAL(timeout()), this, SLOT(updateSourceModel()));
82
83 QShortcut *shortcut = new QShortcut(QKeySequence(Qt::Key_Delete), this);
84 QObject::connect(shortcut, SIGNAL(activated()), this, SLOT(deleteDestSelection()));
85 }
86
~FileSelectWizardPage()87 FileSelectWizardPage::~FileSelectWizardPage()
88 {
89 delete ui;
90 }
91
initializePage()92 void FileSelectWizardPage::initializePage()
93 {
94 /* first request synchronous */
95 if (res) {
96 task t;
97 t.init(res, -1);
98
99 const char *p = field("fileFilter").toString().toLatin1().data();
100 POOL_MEM info2;
101 pm_strcpy(info2, p);
102 t.arg2 = info2.c_str();
103
104 p = currentPathStr().toLatin1().data();
105 POOL_MEM info3;
106 pm_strcpy(info3, p);
107 t.arg3 = info3.c_str();
108
109 t.model = src_files_model;
110
111 t.get_job_files(field("currentJob").toString().toLatin1().data(), currentSourceId());
112 }
113
114 need_optimize = true;
115 }
116
isComplete() const117 bool FileSelectWizardPage::isComplete() const
118 {
119 return dest_files_model->rowCount() != 0;
120 }
121
nextId() const122 int FileSelectWizardPage::nextId() const
123 {
124 /* if some plugins are used in current selection, move to plugin page, otherwise options */
125 if (field("pluginIds").toString().isEmpty()) {
126 return RestoreWizard::RW_ADVANCEDOPTIONS_PAGE;
127 }
128 return RestoreWizard::RW_PLUGIN_PAGE;
129 }
130
validatePage()131 bool FileSelectWizardPage::validatePage()
132 {
133 /* compute result fields based on destination model content */
134 QStringList fileids, jobids, dirids, findexes;
135 struct stat statp;
136 int32_t LinkFI;
137
138 for (int row=0; row < dest_files_model->rowCount(); ++row) {
139 QModelIndex idx = dest_files_model->index(row, 0);
140 if (idx.data(TypeRole) == TYPEROLE_FILE) {
141 fileids << idx.data(FileIdRole).toString();
142 jobids << idx.data(JobIdRole).toString();
143 decode_stat(idx.data(LStatRole).toString().toLocal8Bit().data(),
144 &statp, sizeof(statp), &LinkFI);
145 if (LinkFI) {
146 findexes << idx.data(JobIdRole).toString() + "," + QString().setNum(LinkFI);
147 }
148 } else /* TYPEROLE_DIRECTORY */
149 {
150 dirids << idx.data(PathIdRole).toString();
151 jobids << idx.data(JobIdRole).toString().split(","); /* Can have multiple jobids */
152 }
153 }
154
155 fileids.removeDuplicates();
156 jobids.removeDuplicates();
157 dirids.removeDuplicates();
158 findexes.removeDuplicates();
159
160 m_jobIds = jobids.join(",");
161 m_fileIds = fileids.join(",");
162 m_dirIds = dirids.join(",");
163 m_hardlinks = findexes.join(",");
164
165 /* plugin Ids and Names are retrieved now, so NextId() can decide to schedule the PluginPage before RestoreOptionsPage */
166 /* Stored as properties so thru the next Wizard Page can use them */
167 task t;
168 t.init(res, -1); /* pass res, ID is set to -1 because task method is called synchronously */
169 m_pluginIds = t.plugins_ids(m_jobIds);
170 m_pluginNames = t.plugins_names(m_jobIds);
171
172 return true;
173 }
174
updateSourceModel()175 void FileSelectWizardPage::updateSourceModel()
176 {
177 /* subsequent request async */
178 if (res) {
179 task *t = new task();
180
181 /* optimize size only once */
182 if (need_optimize) {
183 connect(t, SIGNAL(done(task*)), this, SLOT(optimizeSize()));
184 need_optimize = false;
185 }
186 connect(t, SIGNAL(done(task*)), t, SLOT(deleteLater()));
187
188 t->init(res, TASK_LIST_JOB_FILES);
189
190 const char *p = field("currentJob").toString().toLatin1().data();
191 POOL_MEM info;
192 pm_strcpy(info, p);
193 t->arg = info.c_str();
194
195 p = field("fileFilter").toString().toLatin1().data();
196 POOL_MEM info2;
197 pm_strcpy(info2, p);
198 t->arg2 = info2.c_str();
199
200 p = currentPathStr().toLatin1().data();
201 POOL_MEM info3;
202 pm_strcpy(info3, p);
203 t->arg3 = info3.c_str();
204
205 t->pathId = currentSourceId();
206 t->model = src_files_model;
207
208 res->wrk->queue(t);
209 }
210 }
211
optimizeSize()212 void FileSelectWizardPage::optimizeSize()
213 {
214 int w = ui->destTableView->width()/4;
215 ui->destTableView->horizontalHeader()->resizeSection( 0, w*2);
216 ui->destTableView->horizontalHeader()->resizeSection( 1, w);
217 ui->destTableView->horizontalHeader()->resizeSection( 2, w );
218 ui->destTableView->horizontalHeader()->setStretchLastSection(true);
219
220 w = ui->sourceTableView->width()/4;
221 ui->sourceTableView->horizontalHeader()->resizeSection( 0, w*2);
222 ui->sourceTableView->horizontalHeader()->resizeSection( 1, w);
223 ui->sourceTableView->horizontalHeader()->resizeSection( 2, w );
224 ui->sourceTableView->horizontalHeader()->setStretchLastSection(true);
225 }
226
changeCurrentFolder(const QModelIndex & current)227 void FileSelectWizardPage::changeCurrentFolder(const QModelIndex& current)
228 {
229 if (current.isValid()) {
230 QStandardItem *item = src_files_model->itemFromIndex(current);
231 if (item && item->data(TypeRole) == TYPEROLE_DIRECTORY) {
232 QString path = item->text();
233 m_currentSourceId = item->data(PathIdRole).toULongLong();
234 if (m_currentSourceId > 0) {
235 // Choose .. update current path to parent dir
236 if (path == "..") {
237 if (m_currentPathStr == "/") {
238 m_currentPathStr = "";
239 } else {
240 m_currentPathStr.remove(QRegExp("[^/]+/$"));
241 }
242
243 } else if (path == "/" && m_currentPathStr == "") {
244 m_currentPathStr += path;
245
246 } else if (path != "/") {
247 m_currentPathStr += path;
248 }
249 }
250
251 emit currentSourceIdChanged();
252 emit currentPathStrChanged();
253
254 int idx = ui->filePathComboPath->findText(m_currentPathStr);
255 if (idx >= 0) {
256 ui->filePathComboPath->setCurrentIndex(idx);
257 } else {
258 ui->filePathComboPath->insertItem(-1, m_currentPathStr, QVariant(m_currentSourceId));
259 ui->filePathComboPath->model()->sort(0);
260 ui->filePathComboPath->setCurrentIndex(ui->filePathComboPath->findText(m_currentPathStr));
261 }
262 }
263 }
264 }
265
changeCurrentText(const QString & current)266 void FileSelectWizardPage::changeCurrentText(const QString& current)
267 {
268 int idx = ui->filePathComboPath->findText(current);
269 m_currentSourceId = ui->filePathComboPath->itemData(idx).toULongLong();
270 m_currentPathStr = ui->filePathComboPath->itemText(idx);
271 emit currentSourceIdChanged();
272 }
273
deleteDestSelection()274 void FileSelectWizardPage::deleteDestSelection()
275 {
276 QMap<int, int> rows;
277 foreach (QModelIndex index, ui->destTableView->selectionModel()->selectedIndexes())
278 rows.insert(index.row(), 0);
279 QMapIterator<int, int> r(rows);
280 r.toBack();
281 while (r.hasPrevious()) {
282 r.previous();
283 ui->destTableView->model()->removeRow(r.key());
284 }
285 }
286
delayedFilter()287 void FileSelectWizardPage::delayedFilter()
288 {
289 freezeSrcView();
290 m_filterTimer->start( 100 );
291 }
292
freezeSrcView()293 void FileSelectWizardPage::freezeSrcView()
294 {
295 ui->sourceTableView->setUpdatesEnabled(false);
296 }
297
unFreezeSrcView()298 void FileSelectWizardPage::unFreezeSrcView()
299 {
300 ui->sourceTableView->setUpdatesEnabled(true);
301 ui->sourceTableView->update();
302 }
303