qtd3dservice: Support Appx device monitoring
authorAndrew Knight <andrew.knight@digia.com>
Thu, 6 Mar 2014 15:06:54 +0000 (17:06 +0200)
committerThe Qt Project <gerrit-noreply@qt-project.org>
Fri, 7 Mar 2014 07:44:53 +0000 (08:44 +0100)
Like Xap monitoring, local appx packages can be automatically monitored.
This is accomplished without polling by:
 - Waiting for changes in the package registry
 - Checking the list of developer apps from the Appx manager
 - Waiting for changes in the application's shader directory

Without polling, the service has 0% CPU utilization when idle, regardless
of the number of Appx packages monitored. This is compared to Xap devices,
which may incur several fractions of a percent of CPU time during
package polling.

Change-Id: I3b0fc6fc7a8beee854c2511295b6d33b102eee88
Reviewed-by: Oliver Wolff <oliver.wolff@digia.com>
src/qtd3dservice/appxhandler.cpp
src/qtd3dservice/d3dservice.cpp

index 281ffb8..c12790d 100644 (file)
@@ -43,6 +43,7 @@
 
 #include <QtCore/QDateTime>
 #include <QtCore/QDir>
+#include <QtCore/QDirIterator>
 #include <QtCore/QStandardPaths>
 #include <QtCore/QStringList>
 
@@ -56,6 +57,7 @@ using namespace Microsoft::WRL::Wrappers;
 using namespace ABI::Windows::Management::Deployment;
 using namespace ABI::Windows::ApplicationModel;
 using namespace ABI::Windows::Storage;
+using namespace ABI::Windows::Foundation::Collections;
 
 struct ComInitializer
 {
@@ -70,7 +72,7 @@ struct ComInitializer
         if (isValid())
             CoUninitialize();
     }
-    bool isValid()
+    bool isValid() const
     {
         return SUCCEEDED(m_hr);
     }
@@ -78,6 +80,87 @@ private:
     HRESULT m_hr;
 };
 
+extern int appxAppNames(int deviceIndex, QSet<QString> &apps)
+{
+    if (deviceIndex) {
+        qCWarning(lcD3DService) << "Unsupported device index:" << deviceIndex;
+        return 1;
+    }
+
+    ComInitializer com;
+    if (!com.isValid())
+        return 1;
+
+    ComPtr<IPackageManager> packageManager;
+    HRESULT hr = RoActivateInstance(HString::MakeReference(RuntimeClass_Windows_Management_Deployment_PackageManager).Get(),
+                                    &packageManager);
+    if (FAILED(hr)) {
+        qCWarning(lcD3DService) << "Unable to instantiate package manager:"
+                                << qt_error_string(hr);
+        return 1;
+    }
+
+    ComPtr<IIterable<Package *>> packageCollection;
+    hr = packageManager->FindPackagesByUserSecurityId(NULL, &packageCollection);
+    if (FAILED(hr)) {
+        qCWarning(lcD3DService) << "Failed to find Appx packages:"
+                                << qt_error_string(hr);
+        return 1;
+    }
+    ComPtr<IIterator<Package *>> iterator;
+    hr = packageCollection->First(&iterator);
+    if (FAILED(hr)) {
+        qCWarning(lcD3DService) << "Failed to get package iterator:"
+                                << qt_error_string(hr);
+        return 1;
+    }
+    boolean hasCurrent;
+    hr = iterator->get_HasCurrent(&hasCurrent);
+    while (SUCCEEDED(hr) && hasCurrent) {
+        ComPtr<IPackage> package;
+        hr = iterator->get_Current(&package);
+        if (FAILED(hr)) {
+            qCWarning(lcD3DService) << qt_error_string(hr);
+            return 1;
+        }
+
+        ComPtr<IPackage2> package2;
+        hr = package.As(&package2);
+        if (FAILED(hr)) {
+            qCWarning(lcD3DService) << qt_error_string(hr);
+            return 1;
+        }
+
+        boolean isDevelopmentMode;
+        hr = package2->get_IsDevelopmentMode(&isDevelopmentMode);
+        if (FAILED(hr)) {
+            qCWarning(lcD3DService) << qt_error_string(hr);
+            return 1;
+        }
+        if (!isDevelopmentMode) {
+            hr = iterator->MoveNext(&hasCurrent);
+            continue;
+        }
+
+        ComPtr<IPackageId> id;
+        hr = package->get_Id(&id);
+        if (FAILED(hr)) {
+            qCWarning(lcD3DService) << qt_error_string(hr);
+            return 1;
+        }
+
+        HSTRING fullName;
+        hr = id->get_FullName(&fullName);
+        if (FAILED(hr)) {
+            qCWarning(lcD3DService) << qt_error_string(hr);
+            return 1;
+        }
+        apps.insert(QString::fromWCharArray(WindowsGetStringRawBuffer(fullName, Q_NULLPTR)));
+        hr = iterator->MoveNext(&hasCurrent);
+    }
+    return 0;
+}
+
 /* This function handles a worker thread servicing an Appx application */
 extern int handleAppxDevice(int deviceIndex, const QString &app, const QString &localBase, HANDLE runLock)
 {
@@ -134,7 +217,6 @@ extern int handleAppxDevice(int deviceIndex, const QString &app, const QString &
     const QString remoteSourcePath = remoteBase + QStringLiteral("\\source\\");
     const QString remoteBinaryPath = remoteBase + QStringLiteral("\\binary\\");
 
-    int round = 0;
     bool checkDirectories = true;
     forever {
         // If the run lock is signaled, it's time to quit
@@ -185,50 +267,59 @@ extern int handleAppxDevice(int deviceIndex, const QString &app, const QString &
             checkDirectories = false;
         }
 
-        // Update roughly every 30 seconds
-        if (round++ % 30 == 0) {
-            QFile file(remoteControlFile);
-            if (!file.open(QFile::WriteOnly)) {
-                qCWarning(lcD3DService) << "Could not create control file.";
-                Sleep(1000);
-                continue;
-            }
-            file.write("Qt D3D compilation service");
+        QFile file(remoteControlFile);
+        if (!file.open(QFile::WriteOnly)) {
+            qCWarning(lcD3DService) << "Could not create control file:"
+                                    << file.errorString();
+            checkDirectories = true;
+            Sleep(1000);
+            continue;
         }
+        file.write("Qt D3D compilation service");
 
         // Ok, ready to check for shaders
-        QDir remoteSourceDir(remoteSourcePath);
-        const QStringList shaderSources = remoteSourceDir.entryList(QDir::Files);
-        foreach (const QString &shaderSource, shaderSources) {
-            const QString remoteSource = remoteSourceDir.absoluteFilePath(shaderSource);
-            const QString shaderFileName = QFileInfo(remoteSource).fileName();
+        QDirIterator it(remoteSourcePath);
+        while (it.hasNext()) {
+            const QString remoteSource = it.next();
+            if (!it.fileInfo().isFile())
+                continue;
+            const QString shaderFileName = it.fileName();
             const QString localSource = localSourcePath + shaderFileName;
             const QString localBinary = localBinaryPath + shaderFileName;
 
             // Copy remote source to local
-            if (!QFile::copy(remoteSource, localSource)) {
+            if (QFile::exists(localSource))
+                QFile::remove(localSource);
+            QFile remoteSourceFile(remoteSource);
+            if (!remoteSourceFile.copy(localSource)) {
                 qCWarning(lcD3DService) << "Unable to copy shader source:" << remoteSource;
+                qCWarning(lcD3DService) << remoteSourceFile.errorString();
                 continue;
             }
 
             // Remove the remote file
-            if (!QFile::remove(remoteSource)) {
+            if (!remoteSourceFile.remove()) {
                 qCWarning(lcD3DService) << "Unable to remove shader source:" << remoteSource;
+                qCWarning(lcD3DService) << remoteSourceFile.errorString();
                 continue;
             }
 
             // Compile shader
             hr = D3DService::compileShader(localSource, localBinary);
             if (FAILED(hr)) {
-                qCWarning(lcD3DService) << "Unable to compile shader:" << shaderSource;
+                qCWarning(lcD3DService) << "Unable to compile shader:" << localSource;
                 qCWarning(lcD3DService) << qt_error_string(hr);
                 continue;
             }
 
             // All went well, copy the blob to the device
             const QString remoteBinary = remoteBinaryPath + shaderFileName;
-            if (!QFile::copy(localBinary, remoteBinary)) {
+            if (QFile::exists(remoteBinary))
+                QFile::remove(remoteBinary);
+            QFile localBinaryFile(localBinary);
+            if (!localBinaryFile.copy(remoteBinary)) {
                 qCWarning(lcD3DService) << "Unable to copy to remote: " << localBinary;
+                qCWarning(lcD3DService) << localBinaryFile.errorString();
                 continue;
             }
 
@@ -236,7 +327,35 @@ extern int handleAppxDevice(int deviceIndex, const QString &app, const QString &
                                   << "and uploaded to:" << remoteBinary;
         }
 
-        // Done, take a break.
-        Sleep(1000);
+        HANDLE notification = FindFirstChangeNotification(
+                    reinterpret_cast<LPCWSTR>(remoteSourcePath.utf16()),
+                    FALSE, FILE_NOTIFY_CHANGE_FILE_NAME);
+        if (!notification) {
+            qCCritical(lcD3DService) << "Failed to set up shader directory notification:"
+                                     << qt_error_string(GetLastError());
+            return 1;
+        }
+
+        // Sleep for up to 30 seconds; wake if a new shader appears
+        HANDLE waitHandles[] = { notification, runLock };
+        DWORD event = WaitForMultipleObjects(2, waitHandles, FALSE, 30000);
+        FindCloseChangeNotification(notification);
+        // Timeout or directory change; loop and update
+        if (event == WAIT_TIMEOUT || event == WAIT_OBJECT_0)
+            continue;
+        // runLock set; exit
+        if (event == WAIT_OBJECT_0 + 1)
+            return 0;
+
+        hr = GetLastError();
+        // If the app was uninstalled, this is expected
+        if (hr == ERROR_INVALID_HANDLE) {
+            qCDebug(lcD3DService) << "The wait handle was invalidated; worker exiting.";
+            return 1;
+        }
+
+        qCWarning(lcD3DService) << "Appx handler wait failed:"
+                                << qt_error_string(hr);
+        return 1;
     }
 }
index 127d04c..962cc4d 100644 (file)
@@ -70,6 +70,7 @@ extern int handleAppxDevice(int deviceIndex, const QString &app, const QString &
 extern int handleXapDevice(int deviceIndex, const QString &app, const QString &cacheDir, HANDLE runLock);
 typedef int (*AppListFunction)(int, QSet<QString> &);
 extern int xapAppNames(int deviceIndex, QSet<QString> &apps);
+extern int appxAppNames(int deviceIndex, QSet<QString> &app);
 
 extern QStringList xapDeviceNames();
 
@@ -438,7 +439,8 @@ void __stdcall run(DWORD argc, LPWSTR argv[])
     }
 
     // App monitoring threads
-    QVector<Worker *> workers;
+    QHash<QStringPair, Worker *> workers;
+    QHash<HANDLE, QStringPair> workerThreads;
 
     // Device monitoring threads - one per device
     QVector<Worker *> deviceWorkers(emulatorNames.size() + 1, NULL);
@@ -446,7 +448,8 @@ void __stdcall run(DWORD argc, LPWSTR argv[])
     // Static list of registrations
     foreach (const QStringPair &registration, D3DService::registrations()) {
         Worker *worker = new Worker(registration, &appWorker);
-        workers.append(worker);
+        workers.insert(registration, worker);
+        workerThreads.insert(worker->thread(), registration);
         waitHandles.append(worker->thread());
     }
 
@@ -462,6 +465,10 @@ void __stdcall run(DWORD argc, LPWSTR argv[])
         SetupDiDestroyDeviceInfoList(info);
     }
 
+    // Create a monitoring thread for local Appx packages
+    Worker appxWorker(qMakePair(QStringLiteral("local"), QString()), &deviceWorker);
+    Q_UNUSED(appxWorker);
+
     // Master loop
     // This loop handles incoming events from the service controller and
     // worker threads. It also creates new worker threads as needed.
@@ -488,8 +495,16 @@ void __stdcall run(DWORD argc, LPWSTR argv[])
                     // A new worker is in the queue
                     if (controlEvent == NewWorker) {
                         while (!d->workerQueue.isEmpty()) {
-                            Worker *worker = new Worker(d->workerQueue.takeFirst(), &appWorker);
-                            workers.append(worker);
+                            const QStringPair config = d->workerQueue.takeFirst();
+                            if (workers.contains(config)) { // The config is already running
+                                qCDebug(lcD3DService) << "Discarded worker configuration:"
+                                                      << config.first << config.second;
+                                continue;
+                            }
+
+                            Worker *worker = new Worker(config, &appWorker);
+                            workers.insert(config, worker);
+                            workerThreads.insert(worker->thread(), config);
                             waitHandles.append(worker->thread());
                             maxWorker = minWorker + workers.size() - 1;
                         }
@@ -575,8 +590,9 @@ void __stdcall run(DWORD argc, LPWSTR argv[])
             // A worker exited
             if (event >= minWorker && event <= maxWorker) {
                 // Delete the worker and clear the handle (TODO: remove it?)
-                waitHandles.remove(event - WAIT_OBJECT_0);
-                Worker *worker = workers.takeAt(event - minWorker);
+                HANDLE thread = waitHandles.takeAt(event - WAIT_OBJECT_0);
+                QStringPair config = workerThreads.take(thread);
+                Worker *worker = workers.take(config);
                 delete worker;
                 continue;
             }
@@ -611,9 +627,18 @@ DWORD __stdcall deviceWorker(LPVOID param)
     AppListFunction appList;
 
     int deviceIndex = 0;
+    HKEY waitKey = 0;
     if (args->deviceName.isEmpty() || args->deviceName == QStringLiteral("local")) {
-        // Not implemented
-        return 0;
+        appList = appxAppNames;
+        LONG result = RegOpenKeyEx(
+                    HKEY_CLASSES_ROOT,
+                    L"\\Local Settings\\Software\\Microsoft\\Windows\\CurrentVersion\\AppModel\\Repository\\Packages",
+                    0, KEY_NOTIFY, &waitKey);
+        if (result != ERROR_SUCCESS) {
+            qCWarning(lcD3DService) << "Unable to open registry key for Appx discovery:"
+                                    << qt_error_string(result);
+            waitKey = 0;
+        }
     } else {
         // CoreCon (Windows Phone)
         bool ok;
@@ -647,9 +672,37 @@ DWORD __stdcall deviceWorker(LPVOID param)
         }
 
         appNames = latestAppNames;
+
+        // If possible, wait for the registry event (otherwise, go straight to sleep)
+        if (waitKey) {
+            HANDLE waitEvent = CreateEvent(NULL, FALSE, FALSE, NULL);
+            LONG result = RegNotifyChangeKeyValue(waitKey, TRUE, REG_NOTIFY_CHANGE_NAME, waitEvent, TRUE);
+            if (result != ERROR_SUCCESS) {
+                qCWarning(lcD3DService) << "Unable to create registry notifier:"
+                                        << qt_error_string(result);
+                RegCloseKey(waitKey); // Revert to polling
+                waitKey = 0;
+            }
+
+            HANDLE waitHandles[] = { args->runLock, waitEvent };
+            result = WaitForMultipleObjects(2, waitHandles, FALSE, INFINITE);
+            CloseHandle(waitEvent);
+            if (result == WAIT_OBJECT_0) // runLock, exit
+                return 0;
+            if (result == WAIT_OBJECT_0 + 1) { // registry changed, reset app list
+                appNames.clear();
+            } else {
+                qCWarning(lcD3DService) << "Unexpected wait result:" << result
+                                        << qt_error_string(GetLastError());
+                RegCloseKey(waitKey); // Revert to polling
+                waitKey = 0;
+            }
+        }
         Sleep(1000);
     }
 
+    if (waitKey)
+        RegCloseKey(waitKey);
     return 0;
 }
 
@@ -682,7 +735,7 @@ DWORD __stdcall appWorker(LPVOID param)
         handleDevice = &handleXapDevice;
     }
 
-    return handleXapDevice(deviceIndex, args->app, cachePath, args->runLock);
+    return handleDevice(deviceIndex, args->app, cachePath, args->runLock);
 }
 
 // Service controller