Fix MS15103: Interface crashes on snap if snap dir is deleted

This commit is contained in:
Zach Fox 2018-05-16 09:52:19 -07:00
parent 1313d28b9d
commit 012ae741a5
7 changed files with 74 additions and 30 deletions

View file

@ -7590,8 +7590,9 @@ void Application::loadAvatarBrowser() const {
void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRatio, const QString& filename) { void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRatio, const QString& filename) {
postLambdaEvent([notify, includeAnimated, aspectRatio, filename, this] { postLambdaEvent([notify, includeAnimated, aspectRatio, filename, this] {
bool initialWriteFailed = false;
// Get a screenshot and save it // Get a screenshot and save it
QString path = Snapshot::saveSnapshot(getActiveDisplayPlugin()->getScreenshot(aspectRatio), filename, QString path = Snapshot::saveSnapshot(getActiveDisplayPlugin()->getScreenshot(aspectRatio), filename, initialWriteFailed,
TestScriptingInterface::getInstance()->getTestResultsLocation()); TestScriptingInterface::getInstance()->getTestResultsLocation());
// If we're not doing an animated snapshot as well... // If we're not doing an animated snapshot as well...
@ -7599,15 +7600,20 @@ void Application::takeSnapshot(bool notify, bool includeAnimated, float aspectRa
// Tell the dependency manager that the capture of the still snapshot has taken place. // Tell the dependency manager that the capture of the still snapshot has taken place.
emit DependencyManager::get<WindowScriptingInterface>()->stillSnapshotTaken(path, notify); emit DependencyManager::get<WindowScriptingInterface>()->stillSnapshotTaken(path, notify);
} else if (!SnapshotAnimated::isAlreadyTakingSnapshotAnimated()) { } else if (!SnapshotAnimated::isAlreadyTakingSnapshotAnimated()) {
if (initialWriteFailed) {
emit DependencyManager::get<WindowScriptingInterface>()->processingGifStarted(path, true);
} else {
// Get an animated GIF snapshot and save it // Get an animated GIF snapshot and save it
SnapshotAnimated::saveSnapshotAnimated(path, aspectRatio, qApp, DependencyManager::get<WindowScriptingInterface>()); SnapshotAnimated::saveSnapshotAnimated(path, aspectRatio, qApp, DependencyManager::get<WindowScriptingInterface>());
} }
}
}); });
} }
void Application::takeSecondaryCameraSnapshot(const QString& filename) { void Application::takeSecondaryCameraSnapshot(const QString& filename) {
postLambdaEvent([filename, this] { postLambdaEvent([filename, this] {
QString snapshotPath = Snapshot::saveSnapshot(getActiveDisplayPlugin()->getSecondaryCameraScreenshot(), filename, bool initialWriteFailed = false;
QString snapshotPath = Snapshot::saveSnapshot(getActiveDisplayPlugin()->getSecondaryCameraScreenshot(), filename, initialWriteFailed,
TestScriptingInterface::getInstance()->getTestResultsLocation()); TestScriptingInterface::getInstance()->getTestResultsLocation());
emit DependencyManager::get<WindowScriptingInterface>()->stillSnapshotTaken(snapshotPath, true); emit DependencyManager::get<WindowScriptingInterface>()->stillSnapshotTaken(snapshotPath, true);

View file

@ -594,9 +594,11 @@ signals:
* starting to be processed. * starting to be processed.
* @function Window.processingGifStarted * @function Window.processingGifStarted
* @param {string} pathStillSnapshot - The path and name of the still snapshot image file. * @param {string} pathStillSnapshot - The path and name of the still snapshot image file.
* @param {bool} stillWriteFailed - True if the still snapshot (taken before the GIF) failed to write to disk
* (this means the GIF won't capture).
* @returns {Signal} * @returns {Signal}
*/ */
void processingGifStarted(const QString& pathStillSnapshot); void processingGifStarted(const QString& pathStillSnapshot, const bool& stillWriteFailed);
/**jsdoc /**jsdoc
* Triggered when a GIF has been prepared of the snapshot images captured by {@link Window.takeSnapshot|takeSnapshot}. * Triggered when a GIF has been prepared of the snapshot images captured by {@link Window.takeSnapshot|takeSnapshot}.

View file

@ -74,10 +74,11 @@ SnapshotMetaData* Snapshot::parseSnapshotData(QString snapshotPath) {
return data; return data;
} }
QString Snapshot::saveSnapshot(QImage image, const QString& filename, const QString& pathname) { QString Snapshot::saveSnapshot(QImage image, const QString& filename, bool& initialWriteFailed, const QString& pathname) {
QFile* snapshotFile = savedFileForSnapshot(image, false, filename, pathname); QFile* snapshotFile = savedFileForSnapshot(image, false, initialWriteFailed, filename, pathname);
if (snapshotFile) {
// we don't need the snapshot file, so close it, grab its filename and delete it // we don't need the snapshot file, so close it, grab its filename and delete it
snapshotFile->close(); snapshotFile->close();
@ -88,12 +89,16 @@ QString Snapshot::saveSnapshot(QImage image, const QString& filename, const QStr
return snapshotPath; return snapshotPath;
} }
QTemporaryFile* Snapshot::saveTempSnapshot(QImage image) { return "";
// return whatever we get back from saved file for snapshot
return static_cast<QTemporaryFile*>(savedFileForSnapshot(image, true));
} }
QFile* Snapshot::savedFileForSnapshot(QImage & shot, bool isTemporary, const QString& userSelectedFilename, const QString& userSelectedPathname) { QTemporaryFile* Snapshot::saveTempSnapshot(QImage image) {
// return whatever we get back from saved file for snapshot
bool initialWriteFailed = false;
return static_cast<QTemporaryFile*>(savedFileForSnapshot(image, true, initialWriteFailed));
}
QFile* Snapshot::savedFileForSnapshot(QImage & shot, bool isTemporary, bool& initialWriteFailed, const QString& userSelectedFilename, const QString& userSelectedPathname) {
// adding URL to snapshot // adding URL to snapshot
QUrl currentURL = DependencyManager::get<AddressManager>()->currentPublicAddress(); QUrl currentURL = DependencyManager::get<AddressManager>()->currentPublicAddress();
@ -140,7 +145,21 @@ QFile* Snapshot::savedFileForSnapshot(QImage & shot, bool isTemporary, const QSt
snapshotFullPath.append(filename); snapshotFullPath.append(filename);
QFile* imageFile = new QFile(snapshotFullPath); QFile* imageFile = new QFile(snapshotFullPath);
imageFile->open(QIODevice::WriteOnly); while (!imageFile->open(QIODevice::WriteOnly)) {
initialWriteFailed = true;
snapshotFullPath = OffscreenUi::getExistingDirectory(nullptr, "Write Error - Choose New Snapshots Directory", QStandardPaths::writableLocation(QStandardPaths::DesktopLocation));
if (snapshotFullPath.isEmpty()) {
return NULL;
}
snapshotsLocation.set(snapshotFullPath);
if (!snapshotFullPath.endsWith(QDir::separator())) {
snapshotFullPath.append(QDir::separator());
}
snapshotFullPath.append(filename);
imageFile = new QFile(snapshotFullPath);
}
shot.save(imageFile, 0, IMAGE_QUALITY); shot.save(imageFile, 0, IMAGE_QUALITY);
imageFile->close(); imageFile->close();

View file

@ -38,7 +38,7 @@ class Snapshot : public QObject, public Dependency {
Q_OBJECT Q_OBJECT
SINGLETON_DEPENDENCY SINGLETON_DEPENDENCY
public: public:
static QString saveSnapshot(QImage image, const QString& filename, const QString& pathname = QString()); static QString saveSnapshot(QImage image, const QString& filename, bool& initialWriteFailed, const QString& pathname = QString());
static QTemporaryFile* saveTempSnapshot(QImage image); static QTemporaryFile* saveTempSnapshot(QImage image);
static SnapshotMetaData* parseSnapshotData(QString snapshotPath); static SnapshotMetaData* parseSnapshotData(QString snapshotPath);
@ -54,6 +54,7 @@ public slots:
private: private:
static QFile* savedFileForSnapshot(QImage& image, static QFile* savedFileForSnapshot(QImage& image,
bool isTemporary, bool isTemporary,
bool& initialWriteFailed,
const QString& userSelectedFilename = QString(), const QString& userSelectedFilename = QString(),
const QString& userSelectedPathname = QString()); const QString& userSelectedPathname = QString());
}; };

View file

@ -89,7 +89,7 @@ void SnapshotAnimated::captureFrames() {
// Notify the user that we're processing the snapshot // Notify the user that we're processing the snapshot
// This also pops up the "Share" dialog. The unprocessed GIF will be visualized as a loading icon until processingGifCompleted() is called. // This also pops up the "Share" dialog. The unprocessed GIF will be visualized as a loading icon until processingGifCompleted() is called.
emit SnapshotAnimated::snapshotAnimatedDM->processingGifStarted(SnapshotAnimated::snapshotStillPath); emit SnapshotAnimated::snapshotAnimatedDM->processingGifStarted(SnapshotAnimated::snapshotStillPath, false);
// Kick off the thread that'll pack the frames into the GIF // Kick off the thread that'll pack the frames into the GIF
QtConcurrent::run(processFrames); QtConcurrent::run(processFrames);
@ -103,18 +103,40 @@ void SnapshotAnimated::captureFrames() {
} }
} }
void SnapshotAnimated::clearTempVariables() {
// Clear out the frame and frame delay vectors.
// Also release the memory not required to store the items.
SnapshotAnimated::snapshotAnimatedFrameVector.clear();
SnapshotAnimated::snapshotAnimatedFrameVector.squeeze();
SnapshotAnimated::snapshotAnimatedFrameDelayVector.clear();
SnapshotAnimated::snapshotAnimatedFrameDelayVector.squeeze();
// Reset the current frame timestamp
SnapshotAnimated::snapshotAnimatedTimestamp = 0;
SnapshotAnimated::snapshotAnimatedFirstFrameTimestamp = 0;
}
void SnapshotAnimated::processFrames() { void SnapshotAnimated::processFrames() {
uint32_t width = SnapshotAnimated::snapshotAnimatedFrameVector[0].width(); uint32_t width = SnapshotAnimated::snapshotAnimatedFrameVector[0].width();
uint32_t height = SnapshotAnimated::snapshotAnimatedFrameVector[0].height(); uint32_t height = SnapshotAnimated::snapshotAnimatedFrameVector[0].height();
// Create the GIF from the temporary files // Create the GIF from the temporary files
// Write out the header and beginning of the GIF file // Write out the header and beginning of the GIF file
GifBegin( if (!GifBegin(
&(SnapshotAnimated::snapshotAnimatedGifWriter), &(SnapshotAnimated::snapshotAnimatedGifWriter),
qPrintable(SnapshotAnimated::snapshotAnimatedPath), qPrintable(SnapshotAnimated::snapshotAnimatedPath),
width, width,
height, height,
1); // "1" means "yes there is a delay" with this GifCreator library. 1)) { // "1" means "yes there is a delay" with this GifCreator library.
// We should never, ever get here. If we do, that means that writing a still JPG to the filesystem
// has succeeded, but that writing the tiny header to a GIF file in the same directory failed.
// If that happens, we _could_ throw up the "Folder Chooser" dialog like we do for still JPG images,
// but I have no way of testing whether or not that'll work or get properly exercised,
// so I'm not going to bother for now.
SnapshotAnimated::clearTempVariables();
qDebug() << "Animated snapshot header failed to write - aborting GIF processing.";
return;
}
for (int itr = 0; itr < SnapshotAnimated::snapshotAnimatedFrameVector.size(); itr++) { for (int itr = 0; itr < SnapshotAnimated::snapshotAnimatedFrameVector.size(); itr++) {
// Write each frame to the GIF // Write each frame to the GIF
GifWriteFrame(&(SnapshotAnimated::snapshotAnimatedGifWriter), GifWriteFrame(&(SnapshotAnimated::snapshotAnimatedGifWriter),
@ -126,16 +148,6 @@ void SnapshotAnimated::processFrames() {
// Write out the end of the GIF // Write out the end of the GIF
GifEnd(&(SnapshotAnimated::snapshotAnimatedGifWriter)); GifEnd(&(SnapshotAnimated::snapshotAnimatedGifWriter));
// Clear out the frame and frame delay vectors.
// Also release the memory not required to store the items.
SnapshotAnimated::snapshotAnimatedFrameVector.clear();
SnapshotAnimated::snapshotAnimatedFrameVector.squeeze();
SnapshotAnimated::snapshotAnimatedFrameDelayVector.clear();
SnapshotAnimated::snapshotAnimatedFrameDelayVector.squeeze();
// Reset the current frame timestamp
SnapshotAnimated::snapshotAnimatedTimestamp = 0;
SnapshotAnimated::snapshotAnimatedFirstFrameTimestamp = 0;
// Update the "Share" dialog with the processed GIF. // Update the "Share" dialog with the processed GIF.
emit SnapshotAnimated::snapshotAnimatedDM->processingGifCompleted(SnapshotAnimated::snapshotAnimatedPath); emit SnapshotAnimated::snapshotAnimatedDM->processingGifCompleted(SnapshotAnimated::snapshotAnimatedPath);
} }

View file

@ -49,6 +49,7 @@ private:
static void captureFrames(); static void captureFrames();
static void processFrames(); static void processFrames();
static void clearTempVariables();
public: public:
static void saveSnapshotAnimated(QString pathStill, float aspectRatio, Application* app, QSharedPointer<WindowScriptingInterface> dm); static void saveSnapshotAnimated(QString pathStill, float aspectRatio, Application* app, QSharedPointer<WindowScriptingInterface> dm);
static bool isAlreadyTakingSnapshotAnimated() { return snapshotAnimatedFirstFrameTimestamp != 0; }; static bool isAlreadyTakingSnapshotAnimated() { return snapshotAnimatedFirstFrameTimestamp != 0; };

View file

@ -574,7 +574,7 @@ function snapshotDirChanged(snapshotPath) {
} }
} }
function processingGifStarted(pathStillSnapshot) { function processingGifStarted(pathStillSnapshot, stillWriteFailed) {
Window.processingGifStarted.disconnect(processingGifStarted); Window.processingGifStarted.disconnect(processingGifStarted);
Window.processingGifCompleted.connect(processingGifCompleted); Window.processingGifCompleted.connect(processingGifCompleted);
isLoggedIn = Account.isLoggedIn(); isLoggedIn = Account.isLoggedIn();
@ -608,6 +608,9 @@ function processingGifStarted(pathStillSnapshot) {
image_data: imageData image_data: imageData
})); }));
}); });
if (stillWriteFailed) {
processingGifCompleted("");
}
} }
function processingGifCompleted(pathAnimatedSnapshot) { function processingGifCompleted(pathAnimatedSnapshot) {