static KeyFileData getDataFromKeyFile (XmlElement xml)
    {
        KeyFileData data;

        data.licensee = getLicensee (xml);
        data.email = getEmail (xml);
        data.appID = getAppID (xml);

        if (xml.hasAttribute ("expiryTime") && xml.hasAttribute ("expiring_mach"))
        {
            data.keyFileExpires = true;
            data.machineNumbers.addArray (getMachineNumbers (xml, "expiring_mach"));
            data.expiryTime = Time (xml.getStringAttribute ("expiryTime").getHexValue64());
        }
        else
        {
            data.keyFileExpires = false;
            data.machineNumbers.addArray (getMachineNumbers (xml, "mach"));
        }

        return data;
    }
void
GuiAppInstance::declareCurrentAppVariable_Python()
{
    std::string appIDStr = getAppIDString();
    /// define the app variable
    std::stringstream ss;

    ss << appIDStr << " = " << NATRON_GUI_PYTHON_MODULE_NAME << ".natron.getGuiInstance(" << getAppID() << ") \n";
    const KnobsVec& knobs = getProject()->getKnobs();
    for (KnobsVec::const_iterator it = knobs.begin(); it != knobs.end(); ++it) {
        ss << appIDStr << "." << (*it)->getName() << " = "  << appIDStr  << ".getProjectParam('" <<
        (*it)->getName() << "')\n";
    }

    std::string script = ss.str();
    std::string err;
    _imp->declareAppAndParamsString = script;
    bool ok = NATRON_PYTHON_NAMESPACE::interpretPythonScript(script, &err, 0);
    if (!ok) {
        throw std::runtime_error("GuiAppInstance::declareCurrentAppVariable_Python() failed!");
    }
}
void
GuiAppInstance::load(const CLArgs& cl,
                     bool makeEmptyInstance)
{
    if (getAppID() == 0) {
        appPTR->setLoadingStatus( QObject::tr("Creating user interface...") );
    }

    try {
        declareCurrentAppVariable_Python();
    } catch (const std::exception& e) {
        std::cerr << e.what() << std::endl;
    }

    _imp->_gui = new Gui(this);
    _imp->_gui->createGui();

    printAutoDeclaredVariable(_imp->declareAppAndParamsString);

    ///if the app is interactive, build the plugins toolbuttons from the groups we extracted off the plugins.
    const std::list<boost::shared_ptr<PluginGroupNode> > & _toolButtons = appPTR->getTopLevelPluginsToolButtons();
    for (std::list<boost::shared_ptr<PluginGroupNode>  >::const_iterator it = _toolButtons.begin(); it != _toolButtons.end(); ++it) {
        _imp->findOrCreateToolButtonRecursive(*it);
    }
    _imp->_gui->sortAllPluginsToolButtons();

    Q_EMIT pluginsPopulated();

    ///show the gui
    _imp->_gui->show();


    boost::shared_ptr<Settings> nSettings = appPTR->getCurrentSettings();
    QObject::connect( getProject().get(), SIGNAL(formatChanged(Format)), this, SLOT(projectFormatChanged(Format)) );

    {
        QSettings settings( QString::fromUtf8(NATRON_ORGANIZATION_NAME), QString::fromUtf8(NATRON_APPLICATION_NAME) );
        if ( !settings.contains( QString::fromUtf8("checkForUpdates") ) ) {
            StandardButtonEnum reply = Dialogs::questionDialog(tr("Updates").toStdString(),
                                                               tr("Do you want " NATRON_APPLICATION_NAME " to check for updates "
                                                                  "on launch of the application ?").toStdString(), false);
            bool checkForUpdates = reply == eStandardButtonYes;
            nSettings->setCheckUpdatesEnabled(checkForUpdates);
        }

        if ( nSettings->isCheckForUpdatesEnabled() ) {
            appPTR->setLoadingStatus( tr("Checking if updates are available...") );
            checkForNewVersion();
        }
    }

    if ( nSettings->isDefaultAppearanceOutdated() ) {
        StandardButtonEnum reply = Dialogs::questionDialog(tr("Appearance").toStdString(),
                                                           tr(NATRON_APPLICATION_NAME " default appearance changed since last version.\n"
                                                              "Would you like to set the new default appearance?").toStdString(), false);
        if (reply == eStandardButtonYes) {
            nSettings->restoreDefaultAppearance();
        }
    }

    /// Create auto-save dir if it does not exists
    QDir dir = Project::autoSavesDir();
    dir.mkpath( QString::fromUtf8(".") );


    if (getAppID() == 0) {
        appPTR->getCurrentSettings()->doOCIOStartupCheckIfNeeded();

        if ( !appPTR->isShorcutVersionUpToDate() ) {
            StandardButtonEnum reply = questionDialog(tr("Shortcuts").toStdString(),
                                                      tr("Default shortcuts for " NATRON_APPLICATION_NAME " have changed, "
                                                         "would you like to set them to their defaults ? "
                                                         "Clicking no will keep the old shortcuts hence if a new shortcut has been "
                                                         "set to something else than an empty shortcut you won't benefit of it.").toStdString(),
                                                      false,
                                                      StandardButtons(eStandardButtonYes | eStandardButtonNo),
                                                      eStandardButtonNo);
            if (reply == eStandardButtonYes) {
                appPTR->restoreDefaultShortcuts();
            }
        }
    }

    if (makeEmptyInstance) {
        return;
    }

    /// If this is the first instance of the software, try to load an autosave
    if ( (getAppID() == 0) && cl.getScriptFilename().isEmpty() ) {
        if ( findAndTryLoadUntitledAutoSave() ) {
            ///if we successfully loaded an autosave ignore the specified project in the launch args.
            return;
        }
    }


    QFileInfo info( cl.getScriptFilename() );

    if ( cl.getScriptFilename().isEmpty() || !info.exists() ) {
        getProject()->createViewer();
        execOnProjectCreatedCallback();

        const QString& imageFile = cl.getImageFilename();
        if ( !imageFile.isEmpty() ) {
            handleFileOpenEvent( imageFile.toStdString() );
        }
    } else {
        if ( info.suffix() == QString::fromUtf8("py") ) {
            appPTR->setLoadingStatus( tr("Loading script: ") + cl.getScriptFilename() );

            ///If this is a Python script, execute it
            loadPythonScript(info);
            execOnProjectCreatedCallback();
        } else if ( info.suffix() == QString::fromUtf8(NATRON_PROJECT_FILE_EXT) ) {
            ///Otherwise just load the project specified.
            QString name = info.fileName();
            QString path = info.path();
            Global::ensureLastPathSeparator(path);
            appPTR->setLoadingStatus(tr("Loading project: ") + path + name);
            getProject()->loadProject(path, name);
            ///remove any file open event that might have occured
            appPTR->setFileToOpen( QString() );
        } else {
            Dialogs::errorDialog( tr("Invalid file").toStdString(),
                                  tr(NATRON_APPLICATION_NAME " only accepts python scripts or .ntp project files").toStdString() );
            execOnProjectCreatedCallback();
        }
    }

    const QString& extraOnProjectCreatedScript = cl.getDefaultOnProjectLoadedScript();
    if ( !extraOnProjectCreatedScript.isEmpty() ) {
        QFileInfo cbInfo(extraOnProjectCreatedScript);
        if ( cbInfo.exists() ) {
            loadPythonScript(cbInfo);
        }
    }
} // load
void SailfishPlatform::newNotificationPin(watchfish::Notification *notification)
{
    qDebug() << "Got new notification from platform:" << notification->owner() << notification->summary();
    //HACK: ignore group notifications
    if (notification->category().endsWith(".group")) {
        qDebug() << "Skipping group notification.";
        return;
    }
    QJsonObject pin;
    QJsonObject layout;
    QJsonArray actions;

    AppID a = getAppID(notification);
    QStringList res = PlatformInterface::AppResMap.contains(a.type) ? PlatformInterface::AppResMap.value(a.type) : PlatformInterface::AppResMap.value("unknown");

    pin.insert("id",QString("%1.%2.%3").arg(a.sender).arg(notification->timestamp().toTime_t()).arg(notification->id()));
    QUuid guid = PlatformInterface::idToGuid(pin.value("id").toString());
    pin.insert("createTime", notification->timestamp().toUTC().toString(Qt::ISODate));
    pin.insert("guid",guid.toString().mid(1,36));
    pin.insert("type",QString("notification"));
    pin.insert("dataSource",QString("%1:%2").arg(a.srcId).arg(PlatformInterface::SysID));
    pin.insert("source",a.sender);
    if (!notification->icon().startsWith("/opt/alien/data/notificationIcon/")) //these are temporary, don't store them
        pin.insert("sourceIcon",notification->icon());
    if(res.count()>2 && !res.at(2).isEmpty()) {
        pin.insert("sourceName",res.at(2));
    }

    // Dismiss action is added implicitly by TimelinePin class
    if(m_cans.contains(a.srcId) && !m_cans.value(a.srcId).isEmpty()) {
        // We should have responses only for something we could do
        QJsonObject response;
        response.insert("type",QString("response"));
        response.insert("title",QString("Response"));
        response.insert("cannedResponse",QJsonArray::fromStringList(m_cans.value(a.srcId)));
        actions.append(response);
    }
    // Explicit open* will override implicit one
    foreach (const QString &actToken, notification->actions()) {
        if (actToken == "default") {
            qDebug() << "found action" << actToken;
            QJsonObject action;
            action.insert("type",QString("open:%1").arg(actToken));
            action.insert("title",QString("Open on Phone"));
            actions.append(action);
            // Sender is sent back as part of canned message response
            layout.insert("sender",actToken);
            break;
        }
    }
    pin.insert("actions",actions);

    layout.insert("type",QString("commNotification"));
    layout.insert("title",a.sender);
    layout.insert("subtitle",notification->summary());
    layout.insert("body",notification->body());

    layout.insert("tinyIcon",res.at(0));
    layout.insert("backgroundColor",res.at(1));

    pin.insert("layout",layout);

    connect(notification, &watchfish::Notification::closed, this, &SailfishPlatform::handleClosedNotification);
    m_notifs.insert(guid, notification); // keep for the action. TimelineManager will take care cleaning it up

    qDebug() << "Emitting new pin" << pin.value("id").toString() << pin.value("dataSource").toString() << pin.value("guid").toString();
    emit newTimelinePin(pin);
}