@@ -277,6 +277,97 @@ await DownloadFileAsync(
277277 }
278278 }
279279
280+ /// <summary>
281+ /// Updates the plugin to the latest version available from its source.
282+ /// </summary>
283+ /// <param name="silentUpdate">If true, do not show any messages when there is no udpate available.</param>
284+ /// <param name="usePrimaryUrlOnly">If true, only use the primary URL for updates.</param>
285+ /// <param name="token">Cancellation token to cancel the update operation.</param>
286+ /// <returns></returns>
287+ public static async Task UpdatePluginAsync ( bool silentUpdate = true , bool usePrimaryUrlOnly = false , CancellationToken token = default )
288+ {
289+ // Update the plugin manifest
290+ await API . UpdatePluginManifestAsync ( usePrimaryUrlOnly , token ) ;
291+
292+ // Get all plugins that can be updated
293+ var resultsForUpdate = (
294+ from existingPlugin in API . GetAllPlugins ( )
295+ join pluginUpdateSource in API . GetPluginManifest ( )
296+ on existingPlugin . Metadata . ID equals pluginUpdateSource . ID
297+ where string . Compare ( existingPlugin . Metadata . Version , pluginUpdateSource . Version ,
298+ StringComparison . InvariantCulture ) <
299+ 0 // if current version precedes version of the plugin from update source (e.g. PluginsManifest)
300+ && ! API . PluginModified ( existingPlugin . Metadata . ID )
301+ select
302+ new
303+ {
304+ existingPlugin . Metadata . ID ,
305+ pluginUpdateSource . Name ,
306+ pluginUpdateSource . Author ,
307+ CurrentVersion = existingPlugin . Metadata . Version ,
308+ NewVersion = pluginUpdateSource . Version ,
309+ existingPlugin . Metadata . IcoPath ,
310+ PluginExistingMetadata = existingPlugin . Metadata ,
311+ PluginNewUserPlugin = pluginUpdateSource
312+ } ) . ToList ( ) ;
313+
314+ // No updates
315+ if ( ! resultsForUpdate . Any ( ) )
316+ {
317+ if ( ! silentUpdate )
318+ {
319+ API . ShowMsg ( API . GetTranslation ( "updateNoResultTitle" ) , API . GetTranslation ( "updateNoResultSubtitle" ) ) ;
320+ }
321+ return ;
322+ }
323+
324+ // If all plugins are modified, just return
325+ if ( resultsForUpdate . All ( x => API . PluginModified ( x . ID ) ) )
326+ {
327+ return ;
328+ }
329+
330+ if ( API . ShowMsgBox (
331+ string . Format ( API . GetTranslation ( "updateAllPluginsSubtitle" ) ,
332+ Environment . NewLine , string . Join ( ", " , resultsForUpdate . Select ( x => x . PluginExistingMetadata . Name ) ) ) ,
333+ API . GetTranslation ( "updateAllPluginsTitle" ) ,
334+ MessageBoxButton . YesNo ) == MessageBoxResult . No )
335+ {
336+ return ;
337+ }
338+
339+ // Update all plugins
340+ await Task . WhenAll ( resultsForUpdate . Select ( async plugin =>
341+ {
342+ var downloadToFilePath = Path . Combine ( Path . GetTempPath ( ) , $ "{ plugin . Name } -{ plugin . NewVersion } .zip") ;
343+
344+ try
345+ {
346+ using var cts = new CancellationTokenSource ( ) ;
347+
348+ await DownloadFileAsync (
349+ $ "{ API . GetTranslation ( "DownloadingPlugin" ) } { plugin . PluginNewUserPlugin . Name } ",
350+ plugin . PluginNewUserPlugin . UrlDownload , downloadToFilePath , cts ) ;
351+
352+ // check if user cancelled download before installing plugin
353+ if ( cts . IsCancellationRequested )
354+ {
355+ return ;
356+ }
357+
358+ if ( ! await API . UpdatePluginAsync ( plugin . PluginExistingMetadata , plugin . PluginNewUserPlugin , downloadToFilePath ) )
359+ {
360+ return ;
361+ }
362+ }
363+ catch ( Exception e )
364+ {
365+ API . LogException ( ClassName , "Failed to update plugin" , e ) ;
366+ API . ShowMsgError ( API . GetTranslation ( "ErrorUpdatingPlugin" ) ) ;
367+ }
368+ } ) ) ;
369+ }
370+
280371 /// <summary>
281372 /// Downloads a file from a URL to a local path, optionally showing a progress box and handling cancellation.
282373 /// </summary>
0 commit comments