1 /*
2     dspdfviewer - Dual Screen PDF Viewer for LaTeX-Beamer
3     Copyright (C) 2012  Danny Edel <mail@danny-edel.de>
4 
5     This program is free software; you can redistribute it and/or modify
6     it under the terms of the GNU General Public License as published by
7     the Free Software Foundation; either version 2 of the License, or
8     (at your option) any later version.
9 
10     This program is distributed in the hope that it will be useful,
11     but WITHOUT ANY WARRANTY; without even the implied warranty of
12     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13     GNU General Public License for more details.
14 
15     You should have received a copy of the GNU General Public License along
16     with this program; if not, write to the Free Software Foundation, Inc.,
17     51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
18 */
19 
20 
21 #include "pdfrenderfactory.h"
22 #include "renderthread.h"
23 #include "sconnect.h"
24 
25 #include <QMutexLocker>
26 #include <QThreadPool>
27 #include <stdexcept>
28 #include "debug.h"
29 #include "renderutils.h"
30 
31 #include <boost/cast.hpp>
32 
33 namespace {
34 	/** Estimates size in bytes of a rendered Page
35 	 *
36 	 * This currently assumes 32 bit (4 byte) per pixel, and no
37 	 * overhead.
38 	 */
cacheCost(const RenderedPage & renderedPage)39 	int cacheCost(const RenderedPage& renderedPage) {
40 		const QSize& imageSize = renderedPage.getImage().size();
41 		return boost::numeric_cast<int>(sizeof(RenderedPage)) +
42 			4 * imageSize.width() * imageSize.height();
43 	}
44 }
45 
pageThreadFinishedRendering(QSharedPointer<RenderedPage> renderedPage)46 void PdfRenderFactory::pageThreadFinishedRendering(QSharedPointer<RenderedPage> renderedPage)
47 {
48   {
49     QMutexLocker lock(&mutex);
50     const RenderingIdentifier ident( renderedPage->getIdentifier() );
51     // Ignore this incoming rendering if it was from an old version
52     if ( ident.theVersion != currentVersion )
53       return;
54 
55     renderedPages.insert(ident, new RenderedPage(*renderedPage), cacheCost(*renderedPage) );
56     currentlyRenderingPages.remove(ident);
57 	if ( renderedPages.contains(ident) ) {
58 		DEBUGOUT << "Stored" << ident << "into cache";
59 	} else {
60 		WARNINGOUT << "Unable to store" << ident << "into the cache! It would cost" <<
61 			cacheCost(*renderedPage) << "but Current load is" <<
62 			renderedPages.totalCost() << "/" << renderedPages.maxCost();
63 	}
64 
65 	DEBUGOUT << "Current cache fill is " <<
66 		renderedPages.totalCost() << "/" << renderedPages.maxCost() << '(' <<
67 		100.0 * renderedPages.totalCost() / renderedPages.maxCost() << "% )";
68   }
69 
70   emit pageRendered(renderedPage);
71 }
72 
rewatchFile()73 void PdfRenderFactory::rewatchFile()
74 {
75   if ( ! fileWatcher.files().contains( documentReference.filename() ) ) {
76     fileWatcher.addPath( documentReference.filename() );
77     // Check if it has been added (i.e. if it exists)
78     if ( fileWatcher.files().contains( documentReference.filename() ) ) {
79       // The file was created in the meantime. FileWatcher does not report this as a "change",
80       // So we have to check it manually.
81       fileOnDiskChanged(documentReference.filename());
82     }
83   }
84 }
85 
86 
87 
PdfRenderFactory(const RuntimeConfiguration & rc)88 PdfRenderFactory::PdfRenderFactory( const RuntimeConfiguration& rc):
89 	QObject(),
90 	documentReference(rc.filePathQString(), rc.cacheSetting()),
91 	fileWatcher(),
92 	fileWatcherRewatchTimer(),
93 	currentlyRenderingPages(),
94 	renderedPages( boost::numeric_cast<int>(rc.cacheSizeBytes()) ),
95 	mutex(),
96 	currentVersion(0),
97 	// Attempt to read the document to get the number of pages within.
98 	// This will throw an error if the document is unreadable.
99 	numberOfPages_(documentReference.popplerDocument()->numPages())
100 {
101 
102   rewatchFile();
103 
104   // register the on-change function
105   sconnect(&fileWatcher, SIGNAL(fileChanged(QString)), this, SLOT(fileOnDiskChanged(QString)));
106 
107   // Make sure it re-watches the file
108   fileWatcherRewatchTimer.setInterval(1000);
109   sconnect(&fileWatcherRewatchTimer, SIGNAL(timeout()), this, SLOT(rewatchFile()));
110   fileWatcherRewatchTimer.start();
111 }
112 
requestPageRendering(const RenderingIdentifier & originalIdentifier,QThread::Priority priority)113 void PdfRenderFactory::requestPageRendering(const RenderingIdentifier& originalIdentifier, QThread::Priority priority)
114 {
115   QMutexLocker lock(&mutex);
116 
117   RenderingIdentifier renderingIdentifier(originalIdentifier);
118 
119   renderingIdentifier.theVersion = currentVersion;
120 
121   if ( renderedPages.contains(renderingIdentifier) )
122   {
123     /* Page is ready. Take a copy and lets go. */
124     QSharedPointer<RenderedPage> page( new RenderedPage( * renderedPages.object(renderingIdentifier) ));
125     emit pageRendered(page);
126     return;
127   }
128   /* Page was not in cache. Check if its currently in the render stage. */
129   if ( currentlyRenderingPages.contains(renderingIdentifier) )
130   {
131     /* Page is already rendering, so there is nothing to do. */
132     return;
133   }
134   /* Nobody is working on the page right now. Lets create it. */
135 
136   RenderThread* t = new RenderThread( documentReference, renderingIdentifier );
137   sconnect(t, SIGNAL(renderingFinished(QSharedPointer<RenderedPage>)), this, SLOT(pageThreadFinishedRendering(QSharedPointer<RenderedPage>)));
138   currentlyRenderingPages.insert(renderingIdentifier);
139   QThreadPool::globalInstance()->start(t, priority);
140 
141 }
142 
fileOnDiskChanged(const QString & filename)143 void PdfRenderFactory::fileOnDiskChanged(const QString& filename)
144 {
145   DEBUGOUT << "File" << filename << "has changed on disk";
146 
147   if ( filename != documentReference.filename() ) {
148     DEBUGOUT << "Ignoring that file.";
149     return;
150   }
151 
152   // Add path back in case it was modified via "move temporary onto filename",
153   // which filewatcher treats as a remove and stops watching
154 
155   try {
156     emit pdfFileChanged();
157 
158     {
159 
160       // Lock mutex
161       QMutexLocker locker(&mutex);
162 
163       // Create a new File Reference
164       PDFDocumentReference newDoc(filename, documentReference.cacheOption());
165 
166       if ( documentReference.cacheOption() == PDFCacheOption::keepPDFinMemory ) {
167 	// If we keep them in memory, a byte-by-byte compare should be resonably fast.
168 	// If they are *identical*, we can skip the reloading.
169 
170 	if( documentReference == newDoc ) {
171 	  DEBUGOUT << "The new document compares identical to the old one, not doing anything.";
172 	  return;
173 	}
174 
175       }
176 	DEBUGOUT << "The file on disk has different contents. Trying to parse it as PDF.";
177 
178       // Verify poppler can read this
179       newDoc.popplerDocument();
180 
181       // replace the current reference with the new one
182       documentReference = newDoc;
183 
184       numberOfPages_ = documentReference.popplerDocument()->numPages();
185 	  DEBUGOUT << "New document has" << numberOfPages_ << "pages.";
186 
187       // clear the page cache
188       clearAllCaches();
189     }
190 
191     emit pdfFileRereadSuccesfully();
192   } catch( std::runtime_error& ) {
193     DEBUGOUT << "Unable to read the new reference. keeping the old one.";
194     emit pdfFileRereadFailed();
195   }
196 }
197 
clearAllCaches()198 void PdfRenderFactory::clearAllCaches()
199 {
200 
201   // Increment version, so that incoming "old" renders will get ignored
202   /// TODO: Send a termination signal to these lingering threads
203 
204   ++currentVersion;
205 
206   // No renders of the current version are taking place, incoming old renders
207   // will be ignored.
208   currentlyRenderingPages.clear();
209 
210   // Remove the caches. Since we use explicit copy semantics, its safe to empty
211   // these.
212   renderedPages.clear();
213 
214 }
215 
numberOfPages() const216 int PdfRenderFactory::numberOfPages() const
217 {
218   QMutexLocker lock(&mutex);
219   return numberOfPages_;
220 }
221 
~PdfRenderFactory()222 PdfRenderFactory::~PdfRenderFactory() {
223 	RenderUtils::notifyShutdown();
224 }
225