serializer.cpp 19 KB
Newer Older
1 2 3 4
/***************************************************************************
 *   Copyright 2007      Francesco Rossi <redsh@email.it>                  *
 *   Copyright 2006-2007 Mick Kappenburg <ksudoku@kappendburg.net>         *
 *   Copyright 2006-2007 Johannes Bergmeier <johannes.bergmeier@gmx.net>   *
5
 *   Copyright 2015      Ian Wadham <iandw.au@gmail.com>                   *
6 7 8 9 10 11 12 13 14 15 16 17 18 19
 *                                                                         *
 *   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.,                                       *
Johannes Bergmeier's avatar
Johannes Bergmeier committed
20
 *   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.         *
21 22
 ***************************************************************************/

23 24 25 26 27 28 29
#include "serializer.h"
#include "ksudokugame.h"
#include "puzzle.h"

#include <qdom.h>
//Added by qt3to4:
#include <QList>
30
#include <QTextStream>
31 32 33 34
#include <kurl.h>
#include <ktemporaryfile.h>
#include <kio/netaccess.h>
#include <qfile.h>
Laurent Montel's avatar
Laurent Montel committed
35
#include <KLocalizedString>
36

37 38

#include "ksudoku.h"
Johannes Bergmeier's avatar
Johannes Bergmeier committed
39
#include "symbols.h"
40
#include "settings.h"
41 42 43

namespace ksudoku {

44
const char *     typeNames[] = {"Plain", "XSudoku", "Jigsaw", "Aztec",
45 46
				"Samurai", "TinySamurai", "Roxdoku",
				"Mathdoku", "KillerSudoku"};
47
const SudokuType types[]     = {Plain, XSudoku, Jigsaw, Aztec,
48 49
				Samurai, TinySamurai, Roxdoku,
				Mathdoku, KillerSudoku};
50

51 52 53 54 55
Game Serializer::deserializeGame(QDomElement element) {
	bool hasPuzzle = false;
	Puzzle* puzzle = 0;
	bool hasHistory = false;
	QList<HistoryEvent> history;
56

57
	bool hadHelp = static_cast<bool>(element.attribute("had-help", "0").toInt());
58
	int  msecsElapsed = element.attribute("msecs-elapsed", "0").toInt();
59

60 61 62 63 64 65 66 67
	QDomNode child = element.firstChild();
	while (!child.isNull()) {
		if(child.isElement()) {
			if(child.nodeName() == "puzzle") {
				if(hasPuzzle) {
					delete puzzle;
					return Game();
				}
68

69 70 71 72 73 74 75
				puzzle = deserializePuzzle(child.toElement());
				hasPuzzle = true;
			} else if(child.nodeName() == "history") {
				if(hasHistory) {
					delete puzzle;
					return Game();
				}
76

77 78 79 80 81 82
				history = deserializeHistory(child.toElement());
				hasHistory = true;
			}
		}
		child = child.nextSibling();
	}
83

84
	if(!puzzle) return Game();
85

86 87
	Game game(puzzle);
	game.setUserHadHelp(hadHelp);
88

89
	if(hasHistory) {
90
		for(int i = 0; i < history.count(); ++i)
91 92
			game.doEvent(history[i]);
	}
93

94
	game.setTime(msecsElapsed);
95 96 97 98
	return game;
}

Puzzle* Serializer::deserializePuzzle(QDomElement element) {
99 100
	bool hasGraph = false;
	SKGraph* graph = 0;
101 102 103 104
	bool hasValues = false;
	QString valuesStr;
	bool hasSolution = false;
	QString solutionStr;
105

106 107 108 109 110
	QString content;
	QDomNode child = element.firstChild();
	while (!child.isNull()) {
		if(child.isElement()) {
			if(child.nodeName() == "graph") {
111 112
				if(hasGraph) {
					delete graph;
113 114
					return 0;
				}
115

116 117
				graph = deserializeGraph(child.toElement());
				hasGraph = true;
118 119
			} else if(child.nodeName() == "values") {
				if(hasValues) {
120
					delete graph;
121 122
					return 0;
				}
123

124 125 126
				valuesStr = child.toElement().text();
				hasValues = true;
			} else if(child.nodeName() == "solution") {
127
				// TODO remove deserialization of solution, it is no longer required
128
				if(hasSolution) {
129
					delete graph;
130 131
					return 0;
				}
132

133 134 135 136 137 138
				solutionStr = child.toElement().text();
				hasSolution = true;
			}
		}
		child = child.nextSibling();
	}
139

140 141 142
	if(!graph) return 0;
	if(valuesStr.length() != graph->size()) {
		delete graph;
143 144
		return 0;
	}
145
	// TODO remove deserialization of solution, it is no longer required
146 147
	if(solutionStr.length() != 0 && solutionStr.length() != graph->size()) {
		delete graph;
148 149
		return 0;
	}
150

151
	Puzzle* puzzle = new Puzzle(graph, hasSolution);
152

153
	BoardContents values;
154 155
	values.resize(graph->size());
	for(int i = 0; i < graph->size(); ++i) {
Johannes Bergmeier's avatar
Johannes Bergmeier committed
156
		values[i] = Symbols::ioSymbol2Value(valuesStr[i]);
157
	}
158

159
	// TODO remove deserialization of solution, it is no longer required
160
	BoardContents solution;
161
	if(solutionStr.length() != 0) {
162 163
		solution.resize(graph->size());
		for(int i = 0; i < graph->size(); ++i) {
164
			solution[i] = Symbols::ioSymbol2Value(solutionStr[i]);
165 166
		}
	}
167

168
	puzzle->init(values);
169 170 171
	return puzzle;
}

Johannes Bergmeier's avatar
Johannes Bergmeier committed
172
static int readInt(QDomElement element, const QString& name, int* err)
173 174 175 176 177 178
{ //out of class, cannot be static
	*err = 1;
	QString Str = element.attribute(name);
	if(Str.isNull())
		return 0;
	bool noFailure = true;
179
	int num = Str.toInt(&noFailure, 0);
180 181 182 183 184 185
	if(!noFailure)
		return 0;
	*err = 0;
	return num;
}

186
SKGraph* Serializer::deserializeGraph(QDomElement element) {
187
	bool noFailure = true;
188

189 190 191
	QString orderStr = element.attribute("order");
	if(orderStr.isNull())
		return 0;
Ian Wadham's avatar
Ian Wadham committed
192
	// Allow symbolic values for Mathdoku, set from user-config dialog.
193 194 195
	int order = (orderStr == QString("Mathdoku")) ?
                     Settings::mathdokuSize() :
                     orderStr.toInt(&noFailure, 0);
196 197
	if(!noFailure)
		return 0;
198

199 200 201
	QString type = element.attribute("type");
	if(type.isNull())
		return 0;
202

203
	bool d3 = false;
204
	if(type == "sudoku") {
205 206
		SKGraph *graph = new SKGraph(order, TypeSudoku);
		graph->initSudoku();
207
		return graph;
208
	} else if(type == "roxdoku") {
209 210
		SKGraph *graph = new SKGraph(order, TypeRoxdoku);
		graph->initRoxdoku();
211
		return graph;
212
	} else if(type == "custom") {
213
		int err=0;
214 215 216 217 218 219 220 221 222 223
		int ncliques;
		int sizeX;
		int sizeY;
		int sizeZ;
		if (orderStr != QString("Mathdoku")) {
		    ncliques = readInt(element,"ncliques", &err);
		    sizeX = readInt(element,"sizeX",&err);
		    sizeY = readInt(element,"sizeY",&err);
		}
		else {
Ian Wadham's avatar
Ian Wadham committed
224
		    // In Mathdoku, there are row and column groups only.
225 226 227 228 229
		    ncliques = 2 * order;
		    sizeX = order;
		    sizeY = order;
		}
		sizeZ = readInt(element,"sizeZ",&err);
230

231 232 233 234 235 236 237 238 239 240 241
		QString name = element.attribute("name");
		QString typeName = element.attribute("specific-type");
		SudokuType puzzleType = Plain; // Default puzzle-type.
	        for (int n = 0; n < EndSudokuTypes; n++) {
		    QString lookup = QString (typeNames [n]);
	            if (QString::compare (typeName, lookup, Qt::CaseInsensitive)
			== 0) {
		        puzzleType = types [n];
	                break;
	            }
		}
242 243 244 245

		if(err==1) return 0;
		if(sizeX<1 || sizeY<1 || sizeZ<1) return 0;

246 247 248 249
		SKGraph* graph = new SKGraph(order, TypeCustom);
		graph->initCustom(name, puzzleType, order,
			    sizeX, sizeY, sizeZ, ncliques);

250
		QDomNode child = element.firstChild();
251 252 253 254 255 256
		while (!child.isNull()) {
			if(child.isElement()) {
			    QDomElement e   = child.toElement();
			    QString     tag = e.tagName();
			    if (tag == "clique") {
				QString sz = e.attribute("size");
257 258 259 260
				if(! deserializeClique(graph, sz, e.text())) {
				    delete graph;	// Error return.
				    return 0;
				}
261 262
			    }
			    else if (tag == "sudokugroups") {
263 264 265
				graph->initSudokuGroups(
					e.attribute("at", "0").toInt(),
					(e.attribute("withblocks","1") == "1"));
266 267
			    }
			    else if (tag == "roxdokugroups") {
268 269
				graph->initRoxdokuGroups(
					e.attribute("at", "0").toInt());
270
			    }
271 272 273 274 275 276
			    else if (tag == "cage") {
				if(! deserializeCage(graph, e)) {
				    delete graph;	// Error return.
				    return 0;
				}
			    }
277 278 279
			}
			child = child.nextSibling();
		}
280
		graph->endCustom();	// Finalise the structure of the graph.
281
		return graph;
282
	}
283
	return 0;
284 285
}

286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309
bool Serializer::deserializeClique(SKGraph * graph, const QString & size,
						    const QString & text) {
    // A group (or clique) should have a size followed by that number of
    // indices of cells that are members of the Group.  Normally size is
    // equal to m_order (e.g. 4, 9, 16, 25).

    int cellCount = 0;
    if(! size.isNull()) {
	cellCount = size.toInt();
    }
    if (cellCount <= 0) {
	return false;
    }

    QStringList  splitData = text.split(QString(" "), QString::SkipEmptyParts);
    QVector<int> data;
    data.clear();
    foreach (QString s, splitData) {
	--cellCount;
	data << s.toInt();
	if(cellCount <= 0) {
	    break;
	}
    }
310
    graph->addCliqueStructure(data);
311
    return true;
312 313
}

314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341
bool Serializer::deserializeCage(SKGraph * graph, const QDomElement & e) {
    QString sizeStr = e.attribute("size");
    QString text    = e.text();
    CageOperator op = (CageOperator) (e.attribute("operator").toInt());
    int target      = e.attribute("value").toInt();
    int size        = 0;
    QVector<int> cage;
    if(! sizeStr.isNull()) {
	size = sizeStr.toInt();
    }
    if (size <= 0) {
	return false;
    }

    QStringList cells = text.split(QString(" "), QString::SkipEmptyParts);
    cage.clear();
    Q_FOREACH (QString s, cells) {
	cage << s.toInt();
	size--;
	if (size <= 0) {
	    break;
	}
    }

    graph->addCage(cage, op, target);
    return true;
}

342 343
QList<HistoryEvent> Serializer::deserializeHistory(QDomElement element) {
	QList<HistoryEvent> history;
344

345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364
	QDomNode child = element.firstChild();
	while (!child.isNull()) {
		if(child.isElement()) {
			if(child.nodeName() == "simple-event") {
				history.append(deserializeSimpleHistoryEvent(child.toElement()));
			} else if(child.nodeName() == "complex-event") {
				history.append(deserializeComplexHistoryEvent(child.toElement()));
			}
		}
		child = child.nextSibling();
	}
	return history;
}

HistoryEvent Serializer::deserializeSimpleHistoryEvent(QDomElement element) {
	QString indexStr = element.attribute("index");
	QString markerStr = element.attribute("markers");
	QString valueStr = element.attribute("value");
	bool given = element.attribute("given") == "true";
	bool noFailure = true;
365

366
	int index = indexStr.toInt(&noFailure, 0);
367 368
	if(!noFailure)
		return HistoryEvent();
369 370


371 372
	if(markerStr.isNull() == valueStr.isNull())
		return HistoryEvent();
373 374


375 376
	if(!markerStr.isNull()) {
		QBitArray markers(markerStr.length());
377
		for(int i = 0; i < markerStr.length(); ++i)
378
			markers[i] = markerStr[i] != '0';
379

380 381
		return HistoryEvent(index, CellInfo(markers));
	} else {
382
		int value = valueStr.toInt(&noFailure, 0);
383 384 385 386 387 388 389 390 391
		if(!noFailure)
			return HistoryEvent();

		if(given) {
			return HistoryEvent(index, CellInfo(GivenValue, value));
		} else {
			return HistoryEvent(index, CellInfo(CorrectValue, value));
		}
	}
392

393 394 395
	return HistoryEvent();
}

396 397
HistoryEvent Serializer::deserializeComplexHistoryEvent(QDomElement /*element*/) {
	// TODO implement this
398 399 400
	return HistoryEvent();
}

401
SKGraph *Serializer::loadCustomShape(const KUrl &url, QWidget* window, QString *errorMsg) {
402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424
	if ( url.isEmpty() ) return 0;
	QString tmpFile;
	bool success = false;
	QDomDocument doc;
	if(KIO::NetAccess::download(url, tmpFile, window) ) {
		QFile file(tmpFile);
		if(file.open(QIODevice::ReadOnly)) {
			int errorLine;
			if(!doc.setContent(&file, 0, &errorLine)) {
				if(errorMsg)
					*errorMsg = i18n("Cannot read XML file on line %1", errorLine);

				return 0;
			}
			success = true;
		}
		KIO::NetAccess::removeTempFile(tmpFile);
	}
	if ( !success ) {
		if(errorMsg)
			*errorMsg = i18n("Cannot load file.");
		return 0;
	}
425

426 427 428 429 430 431 432 433 434
	QDomNode child = doc.documentElement().firstChild();
	while (!child.isNull()) {
		if(child.isElement()) {
			if(child.nodeName() == "graph") {
				return deserializeGraph(child.toElement());
			}
		}
		child = child.nextSibling();
	}
435

436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461
	return 0;
}

Game Serializer::load(const KUrl& url, QWidget* window, QString *errorMsg) {
	if ( url.isEmpty() ) return Game();
	QString tmpFile;
	bool success = false;
	QDomDocument doc;
	if(KIO::NetAccess::download(url, tmpFile, window) ) {
		QFile file(tmpFile);
		if(file.open(QIODevice::ReadOnly)) {
			int errorLine;
			if(!doc.setContent(&file, 0, &errorLine)) {
				if(errorMsg)
					*errorMsg = i18n("Cannot read XML file on line %1", errorLine);
				return Game();
			}
			success = true;
		}
		KIO::NetAccess::removeTempFile(tmpFile);
	}
	if ( !success ) {
		if(errorMsg)
			*errorMsg = i18n("Cannot load file.");
		return Game();
	}
462

463
	// used to ensure, that there is only one game
464 465
	bool hasGame = false;
	Game game;
466

467 468 469 470 471 472
	QDomNode child = doc.documentElement().firstChild();
	while (!child.isNull()) {
		if(child.isElement()) {
			if(child.nodeName() == "game") {
				if(hasGame)
					return Game();
473

474
				game = deserializeGame(child.toElement());
475
				hasGame = true;
476 477 478 479 480 481 482 483 484 485 486
			}
		}
		child = child.nextSibling();
	}

	return game;
}

bool Serializer::serializeGame(QDomElement& parent, const Game& game) {
	QDomElement element = parent.ownerDocument().createElement("game");
	element.setAttribute("had-help", game.userHadHelp());
487
	element.setAttribute("msecs-elapsed", game.msecsElapsed());
488 489 490 491 492 493 494 495
	serializePuzzle(element, game.puzzle());
	serializeHistory(element, game);
	parent.appendChild(element);
	return true;
}

bool Serializer::serializePuzzle(QDomElement& parent, const Puzzle* puzzle) {
	QString contentStr;
496

497 498
	QDomDocument doc = parent.ownerDocument();
	QDomElement element = doc.createElement("puzzle");
499
	serializeGraph(element, puzzle->graph());
500

501
	for(int i = 0; i < puzzle->size(); ++i) {
Johannes Bergmeier's avatar
Johannes Bergmeier committed
502
		contentStr += Symbols::ioValue2Symbol(puzzle->value(i));
503
	}
504

505 506 507
	QDomElement content = doc.createElement("values");
	content.appendChild(doc.createTextNode(contentStr));
	element.appendChild(content);
508

509 510
	if(puzzle->hasSolution()) {
		contentStr = QString();
511
		for(int i = 0; i < puzzle->size(); ++i) {
512
			contentStr += Symbols::ioValue2Symbol(puzzle->solution(i));
513 514 515 516 517
		}
		content = doc.createElement("solution");
		content.appendChild(doc.createTextNode(contentStr));
		element.appendChild(content);
	}
518

519 520 521 522
	parent.appendChild(element);
	return true;
}

523 524
bool Serializer::serializeGraph(QDomElement &parent, const SKGraph *graph)
{
525
	QDomElement element = parent.ownerDocument().createElement("graph");
526
	element.setAttribute("order", graph->order());
527

528
	GameType type = graph->type();
529 530
	element.setAttribute("type" , (type == TypeSudoku) ? "sudoku" :
				(type == TypeRoxdoku) ? "roxdoku" : "custom");
531 532 533 534 535 536

	int n = -1;
	SudokuType puzzleType = graph->specificType();
	for (n = 0; n < EndSudokuTypes; n++) {
	    if (puzzleType == types [n]) {
		break;
537
	    }
538 539 540
	}
	element.setAttribute("specific-type", (n < 0) ? "Plain" : typeNames[n]);

541 542 543 544 545 546 547 548 549 550 551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576
	if(type == TypeCustom) {
	    element.setAttribute("name", graph->name());
	    element.setAttribute("ncliques", (int) graph->cliqueCount());
	    element.setAttribute("sizeX", graph->sizeX());
	    element.setAttribute("sizeY", graph->sizeY());
	    element.setAttribute("sizeZ", graph->sizeZ());

	    for (int n = 0; n < graph->structureCount(); n++) {
		QDomElement e;
		SKGraph::StructureType sType = graph->structureType(n);
		switch (sType) {
		case SKGraph::SudokuGroups:
		    e = parent.ownerDocument().createElement("sudokugroups");
		    e.setAttribute("at", graph->structurePosition(n));
		    e.setAttribute("withblocks",
				    graph->structureHasBlocks(n) ? "1" : "0");
		    break;
		case SKGraph::RoxdokuGroups:
		    e = parent.ownerDocument().createElement("roxdokugroups");
		    e.setAttribute("at", graph->structurePosition(n));
		    break;
		case SKGraph::Clique:
		    e = parent.ownerDocument().createElement("clique");
		    int cNum  = graph->structurePosition(n);
		    int cSize = graph->clique(cNum).size();
		    e.setAttribute("size", cSize);

		    // Serialize the cell-numbers in the clique (or group).
		    QString contentStr = "";
		    for(int j=0; j < cSize; j++) {
			contentStr += QString::number
				    (graph->clique(cNum).at(j)) + ' ';
		    }
		    e.appendChild(parent.ownerDocument().
				    createTextNode(contentStr));
		    break;
577
		}
578 579
		element.appendChild(e);
	    }
580 581 582 583 584 585 586 587 588 589 590 591 592 593 594 595 596 597

	    // Add cages if this is a Mathdoku or Killer Sudoku puzzle.
	    for (int n = 0; n < graph->cageCount(); n++) {
		QDomElement e = parent.ownerDocument().createElement("cage");
		QVector<int> cage = graph->cage(n);
		e.setAttribute("operator", graph->cageOperator(n));
		e.setAttribute("value", graph->cageValue(n));
		e.setAttribute("size", cage.size());

		// Serialize the cell-numbers in the cage.
		QString contentStr = " ";
		Q_FOREACH (int cell, cage) {
		    contentStr += QString::number(cell) + ' ';
		}
		e.appendChild(parent.ownerDocument().
			        createTextNode(contentStr));
		element.appendChild(e);
	    }
598 599 600 601 602 603 604 605
	}

	parent.appendChild(element);
	return true;
}

bool Serializer::serializeHistory(QDomElement& parent, const Game& game) {
	QDomElement element = parent.ownerDocument().createElement("history");
606

607
	for(int i = 0; i < game.historyLength(); ++i) {
608 609 610
		if(!serializeHistoryEvent(element, game.historyEvent(i)))
			return false;
	}
611

612 613 614 615 616 617
	parent.appendChild(element);
	return true;
}

bool Serializer::serializeHistoryEvent(QDomElement& parent, const HistoryEvent& event) {
	QDomElement element;
618

619
	const QVector<int>& indices = event.cellIndices();
620
	const QVector<CellInfo>& changes = event.cellChanges();
621

622 623 624 625
	if(indices.count() == 0) {
		return true;
	} else if(indices.count() == 1) {
		element = parent.ownerDocument().createElement("simple-event");
626

627 628 629 630 631 632 633 634 635 636 637 638 639
		element.setAttribute("index", indices[0]);
		switch(changes[0].state()) {
			case GivenValue:
				element.setAttribute("given", "true");
				element.setAttribute("value", changes[0].value());
				break;
			case ObviouslyWrong:
			case WrongValue:
			case CorrectValue:
				element.setAttribute("value", changes[0].value());
				break;
			case Marker: {
				QString str;
640

641
				QBitArray markers = changes[0].markers();
642
				for(int j = 0; j < markers.size(); ++j) {
643 644
					str += markers[j] ? '1' : '0';
				}
645

646 647 648 649 650
				element.setAttribute("markers", str);
			} break;
		}
	} else {
		element = parent.ownerDocument().createElement("complex-event");
651
		for(int i = 0; i < indices.count(); ++i) {
652
			QDomElement subElement = parent.ownerDocument().createElement("simple-event");
653

654 655 656 657 658 659 660 661 662 663 664 665 666
			subElement.setAttribute("index", indices[i]);
			switch(changes[i].state()) {
				case GivenValue:
					subElement.setAttribute("given", "true");
					subElement.setAttribute("value", changes[i].value());
					break;
				case ObviouslyWrong:
				case WrongValue:
				case CorrectValue:
					subElement.setAttribute("value", changes[i].value());
					break;
				case Marker: {
					QString str;
667

668
					QBitArray markers = changes[i].markers();
669
					for(int j = 0; j < markers.size(); ++j) {
670 671
						str += markers[i] ? '1' : '0';
					}
672

673 674 675
					subElement.setAttribute("markers", str);
				} break;
			}
676

677 678 679
			element.appendChild(subElement);
		}
	}
680

681 682 683 684
	parent.appendChild(element);
	return true;
}

Johannes Bergmeier's avatar
Johannes Bergmeier committed
685
bool Serializer::store(const Game& game, const KUrl& url, QWidget* window) {
686 687
	QDomDocument doc( "ksudoku" );
	QDomElement root = doc.createElement( "ksudoku" );
688 689
	doc.appendChild( root );

690
	serializeGame(root, game);
691

Johannes Bergmeier's avatar
Johannes Bergmeier committed
692 693
	KTemporaryFile file;
	file.open();
694

Johannes Bergmeier's avatar
Johannes Bergmeier committed
695 696 697
	QTextStream stream(&file);
	stream << doc.toString();
	stream.flush();
698

Johannes Bergmeier's avatar
Johannes Bergmeier committed
699
	KIO::NetAccess::upload(file.fileName(), url, window);
700 701 702 703
	return true;
}

}