6
6
using System . Text . Json ;
7
7
using System . Threading ;
8
8
using System . Threading . Tasks ;
9
+ using System . Windows ;
9
10
using CommunityToolkit . Mvvm . DependencyInjection ;
10
11
using Flow . Launcher . Core . ExternalPlugins ;
11
12
using Flow . Launcher . Infrastructure ;
@@ -24,6 +25,8 @@ public static class PluginManager
24
25
{
25
26
private static readonly string ClassName = nameof ( PluginManager ) ;
26
27
28
+ private static readonly Settings FlowSettings = Ioc . Default . GetRequiredService < Settings > ( ) ;
29
+
27
30
private static IEnumerable < PluginPair > _contextMenuPlugins ;
28
31
private static IEnumerable < PluginPair > _homePlugins ;
29
32
@@ -547,6 +550,177 @@ public static async Task UninstallPluginAsync(PluginMetadata plugin, bool remove
547
550
await UninstallPluginAsync ( plugin , removePluginFromSettings , removePluginSettings , true ) ;
548
551
}
549
552
553
+ public static async Task InstallPluginAndCheckRestartAsync ( UserPlugin newPlugin )
554
+ {
555
+ if ( API . ShowMsgBox (
556
+ string . Format (
557
+ API . GetTranslation ( "InstallPromptSubtitle" ) ,
558
+ newPlugin . Name , newPlugin . Author , Environment . NewLine ) ,
559
+ API . GetTranslation ( "InstallPromptTitle" ) ,
560
+ button : MessageBoxButton . YesNo ) != MessageBoxResult . Yes ) return ;
561
+
562
+ try
563
+ {
564
+ // at minimum should provide a name, but handle plugin that is not downloaded from plugins manifest and is a url download
565
+ var downloadFilename = string . IsNullOrEmpty ( newPlugin . Version )
566
+ ? $ "{ newPlugin . Name } -{ Guid . NewGuid ( ) } .zip"
567
+ : $ "{ newPlugin . Name } -{ newPlugin . Version } .zip";
568
+
569
+ var filePath = Path . Combine ( Path . GetTempPath ( ) , downloadFilename ) ;
570
+
571
+ using var cts = new CancellationTokenSource ( ) ;
572
+
573
+ if ( ! newPlugin . IsFromLocalInstallPath )
574
+ {
575
+ await DownloadFileAsync (
576
+ $ "{ API . GetTranslation ( "DownloadingPlugin" ) } { newPlugin . Name } ",
577
+ newPlugin . UrlDownload , filePath , cts ) ;
578
+ }
579
+ else
580
+ {
581
+ filePath = newPlugin . LocalInstallPath ;
582
+ }
583
+
584
+ // check if user cancelled download before installing plugin
585
+ if ( cts . IsCancellationRequested )
586
+ {
587
+ return ;
588
+ }
589
+ else
590
+ {
591
+ if ( ! File . Exists ( filePath ) )
592
+ {
593
+ throw new FileNotFoundException ( $ "Plugin { newPlugin . ID } zip file not found at { filePath } ", filePath ) ;
594
+ }
595
+
596
+ API . InstallPlugin ( newPlugin , filePath ) ;
597
+
598
+ if ( ! newPlugin . IsFromLocalInstallPath )
599
+ {
600
+ File . Delete ( filePath ) ;
601
+ }
602
+ }
603
+ }
604
+ catch ( Exception e )
605
+ {
606
+ API . LogException ( ClassName , "Failed to install plugin" , e ) ;
607
+ API . ShowMsgError ( API . GetTranslation ( "ErrorInstallingPlugin" ) ) ;
608
+ return ; // don’t restart on failure
609
+ }
610
+
611
+ if ( FlowSettings . AutoRestartAfterChanging )
612
+ {
613
+ API . RestartApp ( ) ;
614
+ }
615
+ else
616
+ {
617
+ API . ShowMsg (
618
+ API . GetTranslation ( "installbtn" ) ,
619
+ string . Format (
620
+ API . GetTranslation (
621
+ "InstallSuccessNoRestart" ) ,
622
+ newPlugin . Name ) ) ;
623
+ }
624
+ }
625
+
626
+ public static async Task UninstallPluginAndCheckRestartAsync ( PluginMetadata oldPlugin )
627
+ {
628
+ if ( API . ShowMsgBox (
629
+ string . Format (
630
+ API . GetTranslation ( "UninstallPromptSubtitle" ) ,
631
+ oldPlugin . Name , oldPlugin . Author , Environment . NewLine ) ,
632
+ API . GetTranslation ( "UninstallPromptTitle" ) ,
633
+ button : MessageBoxButton . YesNo ) != MessageBoxResult . Yes ) return ;
634
+
635
+ var removePluginSettings = API . ShowMsgBox (
636
+ API . GetTranslation ( "KeepPluginSettingsSubtitle" ) ,
637
+ API . GetTranslation ( "KeepPluginSettingsTitle" ) ,
638
+ button : MessageBoxButton . YesNo ) == MessageBoxResult . No ;
639
+
640
+ try
641
+ {
642
+ await API . UninstallPluginAsync ( oldPlugin , removePluginSettings ) ;
643
+ }
644
+ catch ( Exception e )
645
+ {
646
+ API . LogException ( ClassName , "Failed to uninstall plugin" , e ) ;
647
+ API . ShowMsgError ( API . GetTranslation ( "ErrorUninstallingPlugin" ) ) ;
648
+ return ; // don’t restart on failure
649
+ }
650
+
651
+ if ( FlowSettings . AutoRestartAfterChanging )
652
+ {
653
+ API . RestartApp ( ) ;
654
+ }
655
+ else
656
+ {
657
+ API . ShowMsg (
658
+ API . GetTranslation ( "uninstallbtn" ) ,
659
+ string . Format (
660
+ API . GetTranslation (
661
+ "UninstallSuccessNoRestart" ) ,
662
+ oldPlugin . Name ) ) ;
663
+ }
664
+ }
665
+
666
+ public static async Task UpdatePluginAndCheckRestartAsync ( UserPlugin newPlugin , PluginMetadata oldPlugin )
667
+ {
668
+ if ( API . ShowMsgBox (
669
+ string . Format (
670
+ API . GetTranslation ( "UpdatePromptSubtitle" ) ,
671
+ oldPlugin . Name , oldPlugin . Author , Environment . NewLine ) ,
672
+ API . GetTranslation ( "UpdatePromptTitle" ) ,
673
+ button : MessageBoxButton . YesNo ) != MessageBoxResult . Yes ) return ;
674
+
675
+ try
676
+ {
677
+ var filePath = Path . Combine ( Path . GetTempPath ( ) , $ "{ newPlugin . Name } -{ newPlugin . Version } .zip") ;
678
+
679
+ using var cts = new CancellationTokenSource ( ) ;
680
+
681
+ if ( ! newPlugin . IsFromLocalInstallPath )
682
+ {
683
+ await DownloadFileAsync (
684
+ $ "{ API . GetTranslation ( "DownloadingPlugin" ) } { newPlugin . Name } ",
685
+ newPlugin . UrlDownload , filePath , cts ) ;
686
+ }
687
+ else
688
+ {
689
+ filePath = newPlugin . LocalInstallPath ;
690
+ }
691
+
692
+ // check if user cancelled download before installing plugin
693
+ if ( cts . IsCancellationRequested )
694
+ {
695
+ return ;
696
+ }
697
+ else
698
+ {
699
+ await API . UpdatePluginAsync ( oldPlugin , newPlugin , filePath ) ;
700
+ }
701
+ }
702
+ catch ( Exception e )
703
+ {
704
+ API . LogException ( ClassName , "Failed to update plugin" , e ) ;
705
+ API . ShowMsgError ( API . GetTranslation ( "ErrorUpdatingPlugin" ) ) ;
706
+ return ; // don’t restart on failure
707
+ }
708
+
709
+ if ( FlowSettings . AutoRestartAfterChanging )
710
+ {
711
+ API . RestartApp ( ) ;
712
+ }
713
+ else
714
+ {
715
+ API . ShowMsg (
716
+ API . GetTranslation ( "updatebtn" ) ,
717
+ string . Format (
718
+ API . GetTranslation (
719
+ "UpdateSuccessNoRestart" ) ,
720
+ newPlugin . Name ) ) ;
721
+ }
722
+ }
723
+
550
724
#endregion
551
725
552
726
#region Internal functions
@@ -694,6 +868,41 @@ internal static async Task UninstallPluginAsync(PluginMetadata plugin, bool remo
694
868
}
695
869
}
696
870
871
+ internal static async Task DownloadFileAsync ( string prgBoxTitle , string downloadUrl , string filePath , CancellationTokenSource cts , bool deleteFile = true , bool showProgress = true )
872
+ {
873
+ if ( deleteFile && File . Exists ( filePath ) )
874
+ File . Delete ( filePath ) ;
875
+
876
+ if ( showProgress )
877
+ {
878
+ var exceptionHappened = false ;
879
+ await API . ShowProgressBoxAsync ( prgBoxTitle ,
880
+ async ( reportProgress ) =>
881
+ {
882
+ if ( reportProgress == null )
883
+ {
884
+ // when reportProgress is null, it means there is expcetion with the progress box
885
+ // so we record it with exceptionHappened and return so that progress box will close instantly
886
+ exceptionHappened = true ;
887
+ return ;
888
+ }
889
+ else
890
+ {
891
+ await API . HttpDownloadAsync ( downloadUrl , filePath , reportProgress , cts . Token ) . ConfigureAwait ( false ) ;
892
+ }
893
+ } , cts . Cancel ) ;
894
+
895
+ // if exception happened while downloading and user does not cancel downloading,
896
+ // we need to redownload the plugin
897
+ if ( exceptionHappened && ( ! cts . IsCancellationRequested ) )
898
+ await API . HttpDownloadAsync ( downloadUrl , filePath , token : cts . Token ) . ConfigureAwait ( false ) ;
899
+ }
900
+ else
901
+ {
902
+ await API . HttpDownloadAsync ( downloadUrl , filePath , token : cts . Token ) . ConfigureAwait ( false ) ;
903
+ }
904
+ }
905
+
697
906
#endregion
698
907
}
699
908
}
0 commit comments