1 /*
2 For general Scribus (>=1.3.2) copyright and licensing information please refer
3 to the COPYING file provided with the program. Following this notice may exist
4 a copyright and/or license notice that predates the release of Scribus 1.3.2
5 for which a new license (GPL+exception) is in place.
6 */
7 #include "collect4output.h"
8 
9 #include "scribusdoc.h"
10 #include "scribuscore.h"
11 #include "util.h"
12 #include "prefscontext.h"
13 #include "prefsfile.h"
14 #include "prefsmanager.h"
15 #include "commonstrings.h"
16 #include "undomanager.h"
17 #include "filewatcher.h"
18 #include "pageitem.h"
19 #ifdef HAVE_OSG
20 	#include "pageitem_osgframe.h"
21 #endif
22 #include "scraction.h"
23 #include "scpattern.h"
24 #include "util_file.h"
25 
26 #include <QDebug>
27 #include <QDir>
28 #include <QMap>
29 #include <QMessageBox>
30 #include <QProgressBar>
31 #include <QString>
32 
CollectForOutput(ScribusDoc * doc,const QString & outputDirectory,bool withFonts,bool withProfiles,bool compressDoc)33 CollectForOutput::CollectForOutput(ScribusDoc* doc, const QString& outputDirectory, bool withFonts, bool withProfiles, bool compressDoc)
34 	: QObject(ScCore)
35 {
36 	m_Doc=doc;
37 	if (!outputDirectory.isEmpty())
38 		m_outputDirectory=outputDirectory;
39 	m_compressDoc = compressDoc;
40 	m_withFonts = withFonts;
41 	m_withProfiles = withProfiles;
42 	dirs = PrefsManager::instance().prefsFile->getContext("dirs");
43 	collectedFiles.clear();
44 
45 	if (m_withFonts)
46 		fontCount = m_Doc->UsedFonts.count();
47 	if (m_withProfiles)
48 		m_Doc->getUsedProfiles(docProfiles);
49 	profileCount = m_withProfiles ? docProfiles.count() : 0;
50 	itemCount= m_Doc->MasterItems.count()+m_Doc->DocItems.count()+m_Doc->FrameItems.count();
51 	patterns = m_Doc->getUsedPatterns();
52 	patternCount=patterns.count();
53 }
54 
newDirDialog()55 bool CollectForOutput::newDirDialog()
56 {
57 	if (ScCore->usingGUI())
58 	{
59 		QString wdir = ".";
60 		QString prefsDocDir = PrefsManager::instance().documentDir();
61 		if (!prefsDocDir.isEmpty())
62 			wdir = dirs->get("collect", prefsDocDir);
63 		else
64 			wdir = dirs->get("collect", ".");
65 		m_outputDirectory = ScCore->primaryMainWindow()->CFileDialog(wdir, tr("Choose a Directory"), "", "", fdDirectoriesOnly, &m_compressDoc, &m_withFonts, &m_withProfiles);
66 	}
67 	if (m_outputDirectory.isEmpty())
68 		return false;
69 	if (!m_outputDirectory.endsWith("/"))
70 		m_outputDirectory += "/";
71 
72 	docProfiles.clear();
73 	if (m_withProfiles)
74 		m_Doc->getUsedProfiles(docProfiles);
75 	fontCount = m_withFonts ? m_Doc->UsedFonts.count() : 0;
76 	profileCount = m_withProfiles ? docProfiles.count() : 0;
77 
78 	QStringList directories;
79 	directories.append(m_outputDirectory);
80 	directories.append(m_outputDirectory + "images/");
81 	if (m_withFonts)
82 		directories.append(m_outputDirectory + "fonts/");
83 	if (m_withProfiles)
84 		directories.append(m_outputDirectory + "profiles/");
85 
86 	for (int i = 0; i < directories.count(); ++i)
87 	{
88 		QDir dir(directories[i]);
89 		if (dir.exists()) continue;
90 
91 		bool created = dir.mkpath(directories[i]);
92 		if (!created)
93 		{
94 			ScMessageBox::warning(ScCore->primaryMainWindow(), CommonStrings::trWarning,
95 			                     "<qt>" + tr("Cannot create directory:\n%1").arg(directories[i]) + "</qt>");
96 			return false;
97 		}
98 	}
99 	return true;
100 }
101 
collect(QString & newFileName)102 QString CollectForOutput::collect(QString &newFileName)
103 {
104 	if (!newDirDialog())
105 		return "Collect cancelled or unable to create collect destination directory";
106 	ScCore->fileWatcher->forceScan();
107 	ScCore->fileWatcher->stop();
108 	dirs->set("collect", m_outputDirectory.left(m_outputDirectory.lastIndexOf("/",-2)));
109 	ScCore->primaryMainWindow()->setStatusBarInfoText( tr("Collecting..."));
110 
111 	if (!collectItems())
112 	{
113 		QString errorMsg( tr("Cannot collect all files for output for file:\n%1").arg(newName) );
114 		ScMessageBox::warning(ScCore->primaryMainWindow(), CommonStrings::trWarning,
115 							 "<qt>" + errorMsg + "</qt>");
116 		return errorMsg;
117 	}
118 
119 	if (m_withFonts)
120 		collectFonts();
121 	if (m_withProfiles)
122 		collectProfiles();
123 
124 	/* collect document must go last because of image paths changes in collectItems() */
125 	if (!collectDocument())
126 	{
127 		QString errorMsg( tr("Cannot collect the file: \n%1").arg(newName) );
128 		ScMessageBox::warning(ScCore->primaryMainWindow(), CommonStrings::trWarning, "<qt>" + errorMsg + "</qt>");
129 		return errorMsg;
130 	}
131 
132 	QDir::setCurrent(m_outputDirectory);
133 	ScCore->primaryMainWindow()->updateActiveWindowCaption(newName);
134 	UndoManager::instance()->renameStack(newName);
135 	ScCore->primaryMainWindow()->scrActions["fileSave"]->setEnabled(false);
136 	ScCore->primaryMainWindow()->scrActions["fileRevert"]->setEnabled(false);
137 	ScCore->primaryMainWindow()->updateRecent(newName);
138 	ScCore->primaryMainWindow()->setStatusBarInfoText("");
139 	ScCore->primaryMainWindow()->mainWindowProgressBar->reset();
140 	ScCore->fileWatcher->start();
141 	collectedFiles.clear();
142 	newFileName = newName;
143 
144 	return QString();
145 }
146 
collectDocument()147 bool CollectForOutput::collectDocument()
148 {
149 	QFileInfo fi = QFileInfo(m_outputDirectory);
150 	newName = m_outputDirectory;
151 	if (!fi.exists())
152 		return false;
153 	if (!fi.isDir() || !fi.isWritable())
154 		return false;
155 
156 	if ((m_Doc->hasName) && (!m_Doc->isConverted))
157 	{
158 		QFileInfo fis(m_Doc->documentFileName());
159 		newName += fis.fileName();
160 	}
161 	else
162 		newName += m_Doc->documentFileName() + ".sla";
163 
164 	m_Doc->hasName = true;
165 	if (m_compressDoc)
166 	{
167 		if (!newName.endsWith(".gz"))
168 			newName += ".gz";
169 	}
170 	else
171 	{
172 		if (newName.endsWith(".gz"))
173 			newName = newName.remove(".gz");
174 	}
175 
176 	if (!overwrite(ScCore->primaryMainWindow(), newName))
177 		return false;
178 	if (!ScCore->primaryMainWindow()->DoFileSave(newName))
179 		return false;
180 	return true;
181 }
182 
collectItems()183 bool CollectForOutput::collectItems()
184 {
185 	uint counter = 0;
186 	int c=0;
187 	QList<PageItem*> allItems;
188 	PageItem* ite = nullptr;
189 	for (uint lc = 0; lc < 2; ++lc)
190 	{
191 		switch (lc)
192 		{
193 			case 0:
194 				counter = m_Doc->MasterItems.count();
195 				break;
196 			case 1:
197 				counter = m_Doc->DocItems.count();
198 				break;
199 		}
200 		for (uint b = 0; b < counter; ++b)
201 		{
202 			switch (lc)
203 			{
204 				case 0:
205 					ite = m_Doc->MasterItems.at(b);
206 					break;
207 				case 1:
208 					ite = m_Doc->DocItems.at(b);
209 					break;
210 			}
211 			if (ite->isGroup())
212 				allItems = ite->getAllChildren();
213 			else
214 				allItems.append(ite);
215 			for (int ii = 0; ii < allItems.count(); ii++)
216 			{
217 				ite = allItems.at(ii);
218 				processItem(ite);
219 			}
220 			allItems.clear();
221 			if (uiCollect)
222 				emit itemsCollected(c++);
223 		}
224 	}
225 	for (auto itf = m_Doc->FrameItems.begin(); itf != m_Doc->FrameItems.end(); ++itf)
226 	{
227 		PageItem *it = itf.value();
228 		if (it->isGroup())
229 			allItems = it->asGroupFrame()->getAllChildren();
230 		else
231 			allItems.append(it);
232 		for (int ii = 0; ii < allItems.count(); ii++)
233 		{
234 			it = allItems.at(ii);
235 			processItem(it);
236 		}
237 		allItems.clear();
238 		if (uiCollect)
239 			emit itemsCollected(c++);
240 	}
241 	for (int c = 0; c < patterns.count(); ++c)
242 	{
243 		ScPattern pa = m_Doc->docPatterns[patterns[c]];
244 		for (int o = 0; o < pa.items.count(); o++)
245 		{
246 			ite = pa.items.at(o);
247 			if (ite->isGroup())
248 				allItems = ite->getAllChildren();
249 			else
250 				allItems.append(ite);
251 			for (int ii = 0; ii < allItems.count(); ii++)
252 			{
253 				ite = allItems.at(ii);
254 				processItem(ite);
255 			}
256 			allItems.clear();
257 		}
258 		if (uiCollect)
259 			emit patternsCollected(c);
260 	}
261 	return true;
262 }
263 
processItem(PageItem * ite)264 void CollectForOutput::processItem(PageItem *ite)
265 {
266 	if (ite->isImageFrame())
267 	{
268 		/* hack for subsequent c4o "./" -> "/doc/full/path" */
269 		if (!ite->isInlineImage)
270 		{
271 			QString ofName(ite->Pfile);
272 			QFileInfo itf = QFileInfo(ofName);
273 			if (!itf.exists())
274 			{
275 				ofName = QDir::toNativeSeparators(PrefsManager::instance().documentDir() + "/" + ofName);
276 				itf.setFile(ofName);
277 			}
278 		// end of hack
279 			if (itf.exists())
280 			{
281 				const QString& oldFile = ofName;
282 				ite->Pfile = collectFile(oldFile, itf.fileName());
283 				ScCore->fileWatcher->removeFile(oldFile);
284 				ScCore->fileWatcher->addFile(ite->Pfile);
285 			}
286 		}
287 	}
288 #ifdef HAVE_OSG
289 	if (ite->isOSGFrame())
290 	{
291 		PageItem_OSGFrame *osgframe = ite->asOSGFrame();
292 		QString ofName(osgframe->modelFile);
293 		QFileInfo itf = QFileInfo(ofName);
294 		if (!itf.exists())
295 		{
296 			ofName = QDir::toNativeSeparators(PrefsManager::instance().documentDir() + "/" + ofName);
297 			itf.setFile(ofName);
298 		}
299 		if (itf.exists())
300 		{
301 			QString oldFile = ofName;
302 			osgframe->modelFile = collectFile(oldFile, itf.fileName());
303 		}
304 	}
305 #endif
306 	if (ite->isTextFrame())
307 	{
308 		if (ite->isAnnotation())
309 		{
310 			QFileInfo itf;
311 			if (!ite->Pfile.isEmpty())
312 			{
313 				if (!ite->isInlineImage)
314 				{
315 					itf = QFileInfo(ite->Pfile);
316 					if (itf.exists())
317 					{
318 						QString oldFile = ite->Pfile;
319 						ite->Pfile = collectFile(oldFile, itf.fileName());
320 						ScCore->fileWatcher->removeFile(oldFile);
321 						ScCore->fileWatcher->addFile(ite->Pfile);
322 					}
323 				}
324 			}
325 			if (!ite->Pfile2.isEmpty())
326 			{
327 				itf = QFileInfo(ite->Pfile2);
328 				if (itf.exists())
329 					ite->Pfile2 = collectFile(ite->Pfile2, itf.fileName());
330 			}
331 			if (!ite->Pfile3.isEmpty())
332 			{
333 				itf = QFileInfo(ite->Pfile3);
334 				if (itf.exists())
335 					ite->Pfile3 = collectFile(ite->Pfile3, itf.fileName());
336 			}
337 		}
338 	}
339 }
340 
collectFonts()341 bool CollectForOutput::collectFonts()
342 {
343 	PrefsManager& prefsManager = PrefsManager::instance();
344 	QMap<QString,int>::Iterator it3;
345 	QMap<QString,int>::Iterator it3end = m_Doc->UsedFonts.end();
346 	int c=0;
347 	for (it3 = m_Doc->UsedFonts.begin(); it3 != it3end; ++it3)
348 	{
349 		QFileInfo itf(prefsManager.appPrefs.fontPrefs.AvailFonts[it3.key()].fontFilePath());
350 		QString oldFileITF(prefsManager.appPrefs.fontPrefs.AvailFonts[it3.key()].fontFilePath());
351 		QString outFileITF(m_outputDirectory + "fonts/" + itf.fileName());
352 		bool success = copyFileAtomic(oldFileITF, outFileITF);
353 		if (!success)
354 			qDebug()<<"CollectForOutput::collectFile copyFileAtomic failed for"<<oldFileITF<<"to"<<outFileITF;
355 #ifndef Q_OS_WIN32
356 		else
357 		{
358 			QFile of(outFileITF);
359 			if (of.exists())
360 			{
361 				bool permsSet=of.setPermissions(QFile::permissions(oldFileITF));
362 				if (!permsSet)
363 					qDebug()<<"Unable to set permissions successfully while collecting for output on"<<outFileITF;
364 			}
365 			else
366 				qDebug()<<"Unable to set permissions successfully while collecting for output on"<<outFileITF<<"as the file does not exist";
367 		}
368 #endif
369 		if (prefsManager.appPrefs.fontPrefs.AvailFonts[it3.key()].type() == ScFace::TYPE1)
370 		{
371 			QStringList metrics;
372 			QString fontDir  = itf.absolutePath();
373 			QString fontFile = itf.fileName();
374 			metrics += findFontMetrics(fontDir, fontFile);
375 			if ( metrics.empty() )
376 			{
377 				QDir dir;
378 				if (dir.exists(fontDir + "/AFMs"))
379 					metrics += findFontMetrics(fontDir + "/AFMs", fontFile);
380 				if (dir.exists(fontDir + "/afm") && metrics.empty())
381 					metrics += findFontMetrics(fontDir + "/afm", fontFile);
382 				if (dir.exists(fontDir + "/Pfm") && metrics.empty())
383 					metrics += findFontMetrics(fontDir + "/Pfm", fontFile);
384 				if (dir.exists(fontDir + "/pfm") && metrics.empty())
385 					metrics += findFontMetrics(fontDir + "/pfm", fontFile);
386 			}
387 			for (int a = 0; a < metrics.size(); a++)
388 			{
389 				QString origAFM = metrics[a];
390 				QFileInfo fi(origAFM);
391 				QString outFileAFM(m_outputDirectory + "fonts/" + fi.fileName());
392 				bool success = copyFileAtomic(origAFM, outFileAFM);
393 				if (!success)
394 					qDebug()<<"CollectForOutput::collectFile copyFileAtomic failed for"<<origAFM<<"to"<<outFileAFM;
395 #ifndef Q_OS_WIN32
396 				else
397 				{
398 					QFile of(outFileAFM);
399 					if (of.exists())
400 					{
401 						bool permsSet=of.setPermissions(QFile::permissions(origAFM));
402 						if (!permsSet)
403 							qDebug()<<"Unable to set permissions successfully while collecting for output on"<<outFileAFM;
404 					}
405 					else
406 						qDebug()<<"Unable to set permissions successfully while collecting for output on"<<outFileAFM<<"as the file does not exist";
407 				}
408 #endif
409 			}
410 		}
411 		if (uiCollect)
412 			emit fontsCollected(c++);
413 	}
414 	return true;
415 }
416 
findFontMetrics(const QString & baseDir,const QString & baseName) const417 QStringList CollectForOutput::findFontMetrics(const QString& baseDir, const QString& baseName) const
418 {
419 	QStringList metricsFiles;
420 	QString     basePath = baseDir + "/" + baseName;
421 	QString     afnm(basePath);
422 	afnm.chop(3);
423 
424 	// Look for afm files
425 	QString afmName(afnm+"afm");
426 	if (QFile::exists(afmName))
427 		metricsFiles.append(afmName);
428 	else
429 	{
430 		afmName = afnm+"Afm";
431 		if (QFile::exists(afmName))
432 			metricsFiles.append(afmName);
433 		else
434 		{
435 			afmName = afnm+"AFM";
436 			if (QFile::exists(afmName))
437 				metricsFiles.append(afmName);
438 		}
439 	}
440 	// Look for pfm files
441 	QString pfmName(afnm+"pfm");
442 	if (QFile::exists(pfmName))
443 		metricsFiles.append(pfmName);
444 	else
445 	{
446 		pfmName = afnm+"Pfm";
447 		if (QFile::exists(pfmName))
448 			metricsFiles.append(pfmName);
449 		else
450 		{
451 			pfmName = afnm+"PFM";
452 			if (QFile::exists(pfmName))
453 				metricsFiles.append(pfmName);
454 		}
455 	}
456 	return metricsFiles;
457 }
458 
collectProfiles()459 bool CollectForOutput::collectProfiles()
460 {
461 	int c = 0;
462 	ProfilesL::Iterator itend = docProfiles.end();
463 	for (ProfilesL::Iterator it = docProfiles.begin(); it != itend; ++it)
464 	{
465 		QString oldFile(it.value());
466 		QString outFile(m_outputDirectory + "profiles/" + QFileInfo(oldFile).fileName());
467 		bool success = copyFileAtomic(oldFile, outFile);
468 		if (!success)
469 			qDebug()<<"CollectForOutput::collectFile copyFileAtomic failed for"<<oldFile<<"to"<<outFile;
470 #ifndef Q_OS_WIN32
471 		else
472 		{
473 			QFile of(outFile);
474 			if (of.exists())
475 			{
476 				bool permsSet=of.setPermissions(QFile::permissions(oldFile));
477 				if (!permsSet)
478 					qDebug()<<"Unable to set permissions successfully while collecting for output on"<<outFile;
479 			}
480 			else
481 				qDebug()<<"Unable to set permissions successfully while collecting for output on"<<outFile<<"as the file does not exist";
482 		}
483 #endif
484 		if (uiCollect)
485 			emit profilesCollected(c++);
486 	}
487 	return true;
488 }
489 
collectFile(const QString & oldFile,QString newFile)490 QString CollectForOutput::collectFile(const QString& oldFile, QString newFile)
491 {
492 	uint cnt = 1;
493 	bool copy = true;
494 
495 	while (collectedFiles.contains(newFile))
496 	{
497 		// overwrite only different sources
498 		if (collectedFiles[newFile] == oldFile)
499 		{
500 			copy = false;
501 			break;
502 		}
503 		QFileInfo fi(newFile);
504 		QString basename = fi.baseName().left(fi.baseName().lastIndexOf("_"));
505 		newFile = QString("%1_%2.%3").arg(basename).arg(cnt).arg(fi.completeSuffix());
506 		++cnt;
507 	}
508 	if (copy)
509 	{
510 		QString outFile(m_outputDirectory + "images/" + newFile);
511 		bool success = copyFileAtomic(oldFile, outFile);
512 		if (!success)
513 			qDebug()<<"CollectForOutput::collectFile copyFileAtomic failed for"<<oldFile<<"to"<<outFile;
514 #ifndef Q_OS_WIN32
515 		else
516 		{
517 			QFile of(outFile);
518 			if (of.exists())
519 			{
520 				bool permsSet=of.setPermissions(QFile::permissions(oldFile));
521 				if (!permsSet)
522 					qDebug()<<"Unable to set permissions successfully while collecting for output on"<<outFile;
523 			}
524 			else
525 				qDebug()<<"Unable to set permissions successfully while collecting for output on"<<outFile<<"as the file does not exist";
526 		}
527 #endif
528 	}
529 	collectedFiles[newFile] = oldFile;
530 	return m_outputDirectory + "images/" + newFile;
531 }
532