AUI Framework  master
Cross-platform base for C++ UI apps
Loading...
Searching...
No Matches
updater.h
    1
    2
    3
    4/* !!!!!!!!!!!!!!!! THIS FILE IS AUTOGENERATED !!!!!!!!!!!!!!!! */
    5
    6 }; }
   48 *     AFuture<void> downloadUpdateImpl(const APath& unpackedUpdateDir) override { return async { /* stub */ }; }
   49 * };
   50 *
   51 * AUI_ENTRY {
   52 *     auto updater = _new<MyUpdater>();
   53 *     updater->handleStartup(args);
   54 *
   55 *     // your program routines (i.e., open a window)
   56 *     _new<MainWindow>(updater)->show();
   57 *     return 0;
   58 * }
   59 * @endcode
   60 *
   61 *
   62 * You can pass updater instance to your window (as shown in the example) and display update information from
   63 * `AUpdater::status` and perform the update when requested.
   64 *
   65 * # Observing update progress
   66 * <b aui-src="aui.updater/tests/UpdaterTest.cpp#L191"></b>
   67 *
   68 * @copydetails AUpdater::status
   69 *
   70 * # Update process
   71 * <b aui-src="aui.updater/tests/UpdaterTest.cpp#L195"></b>
   72 *
   73 * ## Checking for updates
   74 * <b aui-src="aui.updater/tests/UpdaterTest.cpp#L197"></b>
   75 *
   76 * AUpdater expects @ref AUpdater::checkForUpdates to be called to check for updates. It can be called once per some
   77 * period of time. It calls user-defined @ref AUpdater::checkForUpdatesImpl to perform an update checking.
   78 *
   79 * The update steps are reported by changing `AUpdater::status` property.
   80 *
   81 * @msc
   82 * a[label = "Your App"],
   83 * u[label = "AUpdater", URL = "@ref AUpdater"];
   84 * a -> u [label = "handleStartup(...)", URL = "@ref AUpdater::handleStartup"];
   85 * a <- u [label = "status = AUpdater::StatusIdle", URL = "@ref AUpdater::StatusIdle"];
   86 * a <- u [label = "control flow"];
   87 *
   88 * --- [label="App Normal Lifecycle"];
   89 * ...;
   90 * a -> u [label = "checkForUpdates()", URL = "@ref AUpdater::checkForUpdates"];
   91 * a <- u [label = "status = AUpdater::StatusCheckingForUpdates", URL = "@ref AUpdater::StatusCheckingForUpdates"];
   92 * a <- u [label = "control flow"];
   93 * u box u [label = "checkForUpdatesImpl()", URL = "@ref AUpdater::checkForUpdatesImpl"];
   94 * a <- u [label = "status = AUpdater::StatusIdle", URL = "@ref AUpdater::StatusIdle"];
   95 *
   96 * ...;
   97 * --- [label="Update published"];
   98 * ...;
   99 * a -> u [label = "checkForUpdates()", URL = "@ref AUpdater::checkForUpdates"];
  100 * a <- u [label = "status = AUpdater::StatusCheckingForUpdates", URL = "@ref AUpdater::StatusCheckingForUpdates"];
  101 * a <- u [label = "control flow"];
  102 * u box u [label = "checkForUpdatesImpl()", URL = "@ref AUpdater::checkForUpdatesImpl"];
  103 * u box u [label = "update was found"];
  104 * a <- u [label = "status = AUpdater::StatusIdle", URL = "@ref AUpdater::StatusIdle"];
  105 * ...;
  106 * @endmsc
  107 *
  108 * You might want to store update check results (i.e., download url) in your implementation of
  109 * @ref AUpdater::checkForUpdatesImpl so your @ref AUpdater::downloadUpdateImpl might reuse this information.
  110 *
  111 * ## Downloading the update
  112 * <b aui-src="aui.updater/tests/UpdaterTest.cpp#L234"></b>
  113 *
  114 * When an update is found, your app should call @ref AUpdater::downloadUpdate to download and unpack the update. It is
  115 * up to you to decide when to download an update. If you wish, you can call @ref AUpdater::downloadUpdate in
  116 * @ref AUpdater::checkForUpdatesImpl to proceed to download process right after update was found (see
  117 * @ref UPDATER_WORKFLOWS for more information about update workflow decisions). It calls
  118 * user-defined @ref AUpdater::downloadUpdateImpl which might choose to call default
  119 * `AUpdater::downloadAndUnpack(<YOUR DOWNLOAD URL>, unpackedUpdateDir)`.
  120 *
  121 * @msc
  122 * a[label = "Your App"],
  123 * u[label = "AUpdater", URL = "@ref AUpdater"];
  124 * ...;
  125 * a -> u [label = "downloadUpdate()", URL = "@ref AUpdater::downloadUpdate"];
  126 * a <- u [label = "status = AUpdater::StatusDownloading", URL = "@ref AUpdater::StatusDownloading"];
  127 * u box u [label = "downloadUpdateImpl()", URL = "@ref AUpdater::downloadUpdateImpl"];
  128 * a <- u [label = "status = AUpdater::StatusWaitingForApplyAndRestart", URL = "@ref AUpdater::StatusWaitingForApplyAndRestart"];
  129 * --- [label="Your App Prompts User to Update"];
  130 * ...;
  131 * @endmsc
  132 *
  133 * ## Applying (deploying) the update
  134 * <b aui-src="aui.updater/tests/UpdaterTest.cpp#L255"></b>
  135 *
  136 * At this moment, AUpdater waits @ref AUpdater::applyUpdateAndRestart to be called. When
  137 * @ref AUpdater::applyUpdateAndRestart is called (i.e., when user accepted update installation), AUpdater executes the
  138 * newer copy of your app downloaded before with a special command line argument which is handled by
  139 * @ref AUpdater::handleStartup in that executable. The initial app process is finished, closing your app window as
  140 * well. From now, your app is in "downtime" state, so we need to apply the update and reopen app back again as quickly
  141 * as possible. This action is required to perform update installation. The copy then replaces old application (where it
  142 * actually installed) with itself (that is, the downloaded, newer copy). After operation is complete, it passes the
  143 * control back to the updated application executable. At last, the newly updated application performs a cleanup after
  144 * update.
  145 *
  146 * @msc
  147 * a[label = "Your App"],
  148 * u[label = "AUpdater", URL = "@ref AUpdater"],
  149 * da[label = "Newer Copy of Your App"],
  150 * du[label = "AUpdater in App Copy", URL = "@ref AUpdater"];
  151 * a :> u [label = "applyUpdateAndRestart()", URL = "@ref AUpdater::applyUpdateAndRestart"];
  152 * u :> da [label = "Execute with update arg"];
  153 * u box u [label = "exit(0)"];
  154 * a box u [label = "Process Finished"];
  155 * da box du [label = "Process Started"];
  156 * da -> du [label = "handleStartup", URL = "@ref AUpdater::handleStartup"];
  157 * du box du [label = "AUpdater::deployUpdate(...)", URL = "@ref AUpdater::deployUpdate"];
  158 * a <: du [label = "Execute"];
  159 * du box du [label = "exit(0)"];
  160 * da box du [label = "Process Finished"];
  161 * a box u [label = "Process Started"];
  162 * a -> u [label = "handleStartup", URL = "@ref AUpdater::handleStartup"];
  163 * u box u [label = "cleanup download dir"];
  164 * a box u [label="App Normal Lifecycle"];
  165 * ...;
  166 * @endmsc
  167 *
  168 * After these operations complete, your app is running in its normal lifecycle.
  169 *
  170 * AUpdater is an abstract class; it needs some functions to be implemented by you.
  171 *
  172 * In this example, let's implement auto update from GitHub release pages.
  173 * <b aui-src="aui.updater/tests/UpdaterTest.cpp#L298"></b>
  174 * <b aui-src="aui.updater/tests/UpdaterTest.cpp#L299"></b>
  175 * <b aui-src="aui.updater/tests/UpdaterTest.cpp#L300"></b>
  176 * <b aui-src="aui.updater/tests/UpdaterTest.cpp#L301"></b>
  177 * @code{cpp}
  178 *    static constexpr auto LOG_TAG = "MyUpdater";
  179 *    class MyUpdater: public AUpdater {
  180 *    public:
  181 *        ~MyUpdater() override = default;
  182 *
  183 *    protected:
  184 *        AFuture<void> checkForUpdatesImpl() override {
  185 *            return async {
  186 *                try {
  187 *                    auto githubLatestRelease = aui::updater::github::latestRelease("aui-framework", "example_app");
  188 *                    ALogger::info(LOG_TAG) << "Found latest release: " << githubLatestRelease.tag_name;
  189 *                    auto ourVersion = aui::updater::Semver::fromString(AUI_PP_STRINGIZE(AUI_CMAKE_PROJECT_VERSION));
  190 *                    auto theirVersion = aui::updater::Semver::fromString(githubLatestRelease.tag_name);
  191 *
  192 *                    if (theirVersion <= ourVersion) {
  193 *                        getThread()->enqueue([] {
  194 *                          AMessageBox::show(
  195 *                              nullptr, "No updates found", "You are running the latest version.", AMessageBox::Icon::INFO);
  196 *                        });
  197 *                        return;
  198 *                    }
  199 *                    aui::updater::AppropriatePortablePackagePredicate predicate {};
  200 *                    auto it = ranges::find_if(
  201 *                        githubLatestRelease.assets, predicate, &aui::updater::github::LatestReleaseResponse::Asset::name);
  202 *                    if (it == ranges::end(githubLatestRelease.assets)) {
  203 *                        ALogger::warn(LOG_TAG)
  204 *                            << "Newer version was found but a package appropriate for your platform is not available. "
  205 *                               "Expected: "
  206 *                            << predicate.getQualifierDebug() << ", got: "
  207 *                            << (githubLatestRelease.assets |
  208 *                                ranges::view::transform(&aui::updater::github::LatestReleaseResponse::Asset::name));
  209 *                        return;
  210 *                    }
  211 *                    ALogger::info(LOG_TAG) << "To download: " << (mDownloadUrl = it->browser_download_url);
  212 *
  213 *                    getThread()->enqueue([this, self = shared_from_this(), version = githubLatestRelease.tag_name] {
  214 *                        if (AMessageBox::show(
  215 *                                nullptr, "New version found!", "Found version: {}\n\nWould you like to update?"_format(version),
  216 *                                AMessageBox::Icon::INFO, AMessageBox::Button::YES_NO) != AMessageBox::ResultButton::YES) {
  217 *                            return;
  218 *                        }
  219 *
  220 *                        downloadUpdate();
  221 *                    });
  222 *
  223 *                } catch (const AException& e) {
  224 *                    ALogger::err(LOG_TAG) << "Can't check for updates: " << e;
  225 *                    getThread()->enqueue([] {
  226 *                        AMessageBox::show(
  227 *                            nullptr, "Oops!", "There is an error occurred while checking for updates. Please try again later.",
  228 *                            AMessageBox::Icon::CRITICAL);
  229 *                    });
  230 *                }
  231 *            };
  232 *        }
  233 *
  234 *        AFuture<void> downloadUpdateImpl(const APath& unpackedUpdateDir) override {
  235 *            return async {
  236 *              try {
  237 *                  AUI_ASSERTX(!mDownloadUrl.empty(), "make a successful call to checkForUpdates first");
  238 *                  downloadAndUnpack(mDownloadUrl, unpackedUpdateDir);
  239 *                  reportReadyToApplyAndRestart(makeDefaultInstallationCmdline());
  240 *              } catch (const AException& e) {
  241 *                  ALogger::err(LOG_TAG) << "Can't check for updates: " << e;
  242 *                  getThread()->enqueue([] {
  243 *                    AMessageBox::show(
  244 *                        nullptr, "Oops!", "There is an error occurred while downloading update. Please try again later.",
  245 *                        AMessageBox::Icon::CRITICAL);
  246 *                  });
  247 *              }
  248 *            };
  249 *        }
  250 *
  251 *    private:
  252 *        AString mDownloadUrl;
  253 *    };
  254 * @endcode
  255 *
  256 * # Updater workflows {#UPDATER_WORKFLOWS}
  257 * <b aui-src="aui.updater/tests/UpdaterTest.cpp#L397"></b>
  258 * When using AUpdater for your application, you need to consider several factors including usability, user experience,
  259 * system resources, and particular needs of your project.
  260 *
  261 * Either way, you might want to implement a way to disable auto update feature in your application.
  262 *
  263 * ## Prompt user on every step
  264 * <b aui-src="aui.updater/tests/UpdaterTest.cpp#L403"></b>
  265 *
  266 * This approach is implemented in AUI's @ref example_app_template.
  267 *
  268 * The updater checks for updater periodically or upon user request and informs the user that an update is available.
  269 * The user then decides whether to proceed with update or not. If they agree the application will download and install
  270 * the update.
  271 *
  272 * This way can be considered as better approach because the user may feel they control the situation and the
  273 * application never does things that user never asked to (trust concerns). On the other hand, such requirement of
  274 * additional user interaction to can distract them from doing their work, so these interactions should not be annoying.
  275 *
  276 * You should not use AMessageBox (unless user explicitly asked to check for update) as it literally interrupts the
  277 * user's workflow, opting them to make a decision before they can continue their work. A great example of a bad auto
  278 * update implementation is qBittorrent client on Windows: hence this application typically launches on OS startup,
  279 * it checks for updates in background and pops the message box if update was found, **even if user is focused on
  280 * another application or away from keyboard**.
  281 *
  282 * ## Silent download
  283 * <b aui-src="aui.updater/tests/UpdaterTest.cpp#L421"></b>
  284 *
  285 * This approach is implemented in @ref example_app_auigram, as well as in official Qt-based Telegram Desktop client.
  286 *
  287 * The updater silently downloads the update in the background while the user continues working within the application
  288 * or even other tasks. The update then is applied automatically upon restart.
  289 * Optionally, the application might show a button/message/notification bubble to restart and apply update.
  290 *
  291 * Despite user trust concerns, this approach allows seamless experience - users don't need to be interrupted during
  292 * their work. They even might not care about updates.
  293 *
  294 *
  295 */
Represents a value that will be available at some point in the future.
Definition AFuture.h:621
An add-on to AString with functions for working with the path.
Definition APath.h:128
Updater class.
Definition AUpdater.h:30
virtual void applyUpdateAndRestart()
Deploy the downloaded update.
AProperty< std::any > status
State of the updater.
Definition AUpdater.h:207
virtual AFuture< void > downloadUpdateImpl(const APath &unpackedUpdateDir)=0
Performs update delivery to the specified directory.
virtual void handleStartup(const AStringVector &applicationArguments)
Performs a pre-application AUpdater routine.
virtual AFuture< void > checkForUpdatesImpl()=0
Check for updates user's implementation.
void checkForUpdates()
Sets status to StatusCheckingForUpdates and calls checkForUpdatesImpl, implemented by user.
void downloadUpdate()
Sets status to StatusDownloading and calls downloadUpdateImpl, implemented by user.
void downloadAndUnpack(AString downloadUrl, const APath &unpackedUpdateDir)
Typical download and unpack implementation.
type_of< T > t
Selects views that are of the specified C++ types.
Definition type_of.h:71
#define let
Performs multiple operations on a single object without repeating its name (in place) This function c...
Definition kAUI.h:262
#define AUI_ENTRY
Application entry point.
Definition Entry.h:90
#define async
Executes following {} block asynchronously in the global thread pool. Unlike asyncX,...
Definition kAUI.h:329
@ URL
Optimize for URLs.
Definition ATextInputType.h:48
Displaying native modal message dialogs.
Definition AMessageBox.h:46