2022-05-16 18:21:26 +08:00
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
2022-05-17 15:33:02 +08:00
using System ;
2022-05-16 19:53:04 +08:00
using System.Collections.Generic ;
2022-05-16 20:07:42 +08:00
using System.IO ;
2022-05-16 19:37:38 +08:00
using System.Linq ;
using System.Threading ;
2022-05-17 15:33:02 +08:00
using System.Threading.Tasks ;
2022-12-07 16:01:28 +08:00
using osu.Framework ;
2022-05-16 18:21:26 +08:00
using osu.Framework.Allocation ;
2022-05-17 15:33:02 +08:00
using osu.Framework.Bindables ;
2022-05-16 19:13:34 +08:00
using osu.Framework.Extensions ;
2022-05-16 18:21:26 +08:00
using osu.Framework.Graphics ;
2022-05-16 20:07:42 +08:00
using osu.Framework.Graphics.Containers ;
2022-05-17 15:33:02 +08:00
using osu.Framework.Graphics.UserInterface ;
2022-05-16 18:21:26 +08:00
using osu.Framework.Localisation ;
2022-10-13 17:39:52 +08:00
using osu.Framework.Logging ;
2022-12-11 19:17:04 +08:00
using osu.Framework.Screens ;
2022-05-16 18:21:26 +08:00
using osu.Game.Database ;
using osu.Game.Graphics ;
using osu.Game.Graphics.Containers ;
2022-05-16 19:37:38 +08:00
using osu.Game.Graphics.UserInterfaceV2 ;
2022-05-16 18:21:26 +08:00
using osu.Game.Localisation ;
2022-12-27 17:50:30 +08:00
using osu.Game.Online.Chat ;
2022-05-16 18:21:26 +08:00
using osu.Game.Overlays.Settings ;
2022-12-11 19:17:04 +08:00
using osu.Game.Overlays.Settings.Sections.Maintenance ;
2022-05-17 15:33:02 +08:00
using osu.Game.Screens.Edit.Setup ;
2022-05-16 18:21:26 +08:00
using osuTK ;
namespace osu.Game.Overlays.FirstRunSetup
{
2022-05-17 16:58:24 +08:00
[LocalisableDescription(typeof(FirstRunOverlayImportFromStableScreenStrings), nameof(FirstRunOverlayImportFromStableScreenStrings.Header))]
2022-05-16 18:21:26 +08:00
public partial class ScreenImportFromStable : FirstRunSetupScreen
{
2022-05-16 20:33:15 +08:00
private static readonly Vector2 button_size = new Vector2 ( 400 , 50 ) ;
2022-05-16 18:21:26 +08:00
private ProgressRoundedButton importButton = null ! ;
2022-05-17 16:26:44 +08:00
private OsuTextFlowContainer progressText = null ! ;
2022-05-16 19:13:34 +08:00
[Resolved]
private LegacyImportManager legacyImportManager { get ; set ; } = null ! ;
2022-05-16 18:21:26 +08:00
2022-05-17 15:33:02 +08:00
private StableLocatorLabelledTextBox stableLocatorTextBox = null ! ;
2022-12-11 19:17:04 +08:00
private LinkFlowContainer copyInformation = null ! ;
2022-12-07 16:01:28 +08:00
2022-05-16 19:53:04 +08:00
private IEnumerable < ImportCheckbox > contentCheckboxes = > Content . Children . OfType < ImportCheckbox > ( ) ;
2022-05-16 18:21:26 +08:00
[BackgroundDependencyLoader(permitNulls: true)]
private void load ( )
{
Content . Children = new Drawable [ ]
{
2022-12-11 19:17:04 +08:00
new LinkFlowContainer ( cp = > cp . Font = OsuFont . Default . With ( size : CONTENT_FONT_SIZE ) )
2022-05-16 18:21:26 +08:00
{
Colour = OverlayColourProvider . Content1 ,
2022-05-17 16:58:24 +08:00
Text = FirstRunOverlayImportFromStableScreenStrings . Description ,
2022-05-16 18:21:26 +08:00
RelativeSizeAxes = Axes . X ,
AutoSizeAxes = Axes . Y
} ,
2022-05-17 15:33:02 +08:00
stableLocatorTextBox = new StableLocatorLabelledTextBox
2022-05-16 19:37:38 +08:00
{
2022-05-17 16:58:24 +08:00
Label = FirstRunOverlayImportFromStableScreenStrings . LocateDirectoryLabel ,
PlaceholderText = FirstRunOverlayImportFromStableScreenStrings . LocateDirectoryPlaceholder
2022-05-16 19:37:38 +08:00
} ,
2022-05-18 20:32:32 +08:00
new ImportCheckbox ( CommonStrings . Beatmaps , StableContent . Beatmaps ) ,
new ImportCheckbox ( CommonStrings . Scores , StableContent . Scores ) ,
new ImportCheckbox ( CommonStrings . Skins , StableContent . Skins ) ,
new ImportCheckbox ( CommonStrings . Collections , StableContent . Collections ) ,
2022-12-11 19:17:04 +08:00
copyInformation = new LinkFlowContainer ( cp = > cp . Font = OsuFont . Default . With ( size : CONTENT_FONT_SIZE ) )
2022-12-07 16:01:28 +08:00
{
Colour = OverlayColourProvider . Content1 ,
RelativeSizeAxes = Axes . X ,
AutoSizeAxes = Axes . Y
} ,
2022-05-16 18:21:26 +08:00
importButton = new ProgressRoundedButton
{
2022-05-16 20:33:15 +08:00
Size = button_size ,
2022-05-16 18:21:26 +08:00
Anchor = Anchor . TopCentre ,
Origin = Anchor . TopCentre ,
2022-05-17 16:58:24 +08:00
Text = FirstRunOverlayImportFromStableScreenStrings . ImportButton ,
2022-05-16 18:21:26 +08:00
Action = runImport
} ,
2022-05-17 16:26:44 +08:00
progressText = new OsuTextFlowContainer ( cp = > cp . Font = OsuFont . Default . With ( size : CONTENT_FONT_SIZE ) )
{
Colour = OverlayColourProvider . Content1 ,
2022-05-17 16:58:24 +08:00
Text = FirstRunOverlayImportFromStableScreenStrings . ImportInProgress ,
2022-05-17 16:26:44 +08:00
RelativeSizeAxes = Axes . X ,
AutoSizeAxes = Axes . Y ,
Alpha = 0 ,
} ,
2022-05-16 18:21:26 +08:00
} ;
2022-05-16 19:13:34 +08:00
2022-05-17 15:33:02 +08:00
stableLocatorTextBox . Current . BindValueChanged ( _ = > updateStablePath ( ) , true ) ;
2022-05-16 19:37:38 +08:00
}
2022-12-11 19:17:04 +08:00
[Resolved(canBeNull: true)]
private OsuGame ? game { get ; set ; }
2022-05-16 19:37:38 +08:00
private void updateStablePath ( )
{
var storage = legacyImportManager . GetCurrentStableStorage ( ) ;
if ( storage = = null )
{
2022-05-18 20:22:17 +08:00
toggleInteraction ( false ) ;
2022-05-16 20:33:15 +08:00
2022-05-17 16:26:44 +08:00
stableLocatorTextBox . Current . Disabled = false ;
2022-05-17 15:33:02 +08:00
stableLocatorTextBox . Current . Value = string . Empty ;
2022-05-16 19:37:38 +08:00
return ;
}
2022-05-16 19:53:04 +08:00
foreach ( var c in contentCheckboxes )
{
2022-05-16 19:37:38 +08:00
c . Current . Disabled = false ;
2022-05-16 19:53:04 +08:00
c . UpdateCount ( ) ;
}
2022-05-16 19:37:38 +08:00
2022-05-18 20:22:17 +08:00
toggleInteraction ( true ) ;
2022-05-17 15:33:02 +08:00
stableLocatorTextBox . Current . Value = storage . GetFullPath ( string . Empty ) ;
2022-05-16 20:33:15 +08:00
importButton . Enabled . Value = true ;
2022-10-13 17:39:52 +08:00
bool available = legacyImportManager . CheckHardLinkAvailability ( ) ;
Logger . Log ( $"Hard link support is {available}" ) ;
2022-12-07 16:01:28 +08:00
if ( available )
{
2022-12-20 05:33:35 +08:00
copyInformation . Text =
2022-12-27 17:50:30 +08:00
"Data migration will use \"hard links\". No extra disk space will be used, and you can delete either data folder at any point without affecting the other installation. " ;
copyInformation . AddLink ( "Learn more about how \"hard links\" work" , LinkAction . OpenWiki , @"Client/Release_stream/Lazer/File_storage#via-hard-links" ) ;
2022-12-07 16:01:28 +08:00
}
2022-12-28 20:23:07 +08:00
else if ( RuntimeInfo . OS ! = RuntimeInfo . Platform . Windows | | RuntimeInfo . OS ! = RuntimeInfo . Platform . Linux )
2022-12-14 10:30:01 +08:00
copyInformation . Text = "Lightweight linking of files is not supported on your operating system yet, so a copy of all files will be made during import." ;
2022-12-07 16:01:28 +08:00
else
{
2022-12-28 22:02:44 +08:00
string mentionNtfs = RuntimeInfo . OS = = RuntimeInfo . Platform . Windows ? " (and the file system is NTFS)." : "." ;
2022-12-28 21:33:38 +08:00
2022-12-07 16:01:28 +08:00
copyInformation . Text =
2022-12-28 22:02:44 +08:00
$"A second copy of all files will be made during import. To avoid this, please make sure the lazer data folder is on the same drive as your previous osu! install{mentionNtfs}" ;
2022-12-11 19:17:04 +08:00
copyInformation . AddLink ( GeneralSettingsStrings . ChangeFolderLocation , ( ) = >
{
game ? . PerformFromScreen ( menu = > menu . Push ( new MigrationSelectScreen ( ) ) ) ;
} ) ;
2022-12-07 16:01:28 +08:00
}
2022-05-16 19:53:04 +08:00
}
2022-05-16 19:37:38 +08:00
2022-05-16 19:53:04 +08:00
private void runImport ( )
{
2022-05-18 20:22:17 +08:00
toggleInteraction ( false ) ;
2022-05-17 16:26:44 +08:00
progressText . FadeIn ( 1000 , Easing . OutQuint ) ;
2022-05-16 19:37:38 +08:00
2022-05-16 19:53:04 +08:00
StableContent importableContent = 0 ;
2022-05-16 19:37:38 +08:00
2022-05-16 19:53:04 +08:00
foreach ( var c in contentCheckboxes . Where ( c = > c . Current . Value ) )
importableContent | = c . StableContent ;
2022-05-16 19:37:38 +08:00
2022-05-16 19:53:04 +08:00
legacyImportManager . ImportFromStableAsync ( importableContent , false ) . ContinueWith ( t = > Schedule ( ( ) = >
2022-05-16 19:37:38 +08:00
{
2022-05-17 16:26:44 +08:00
progressText . FadeOut ( 500 , Easing . OutQuint ) ;
2022-05-16 19:53:04 +08:00
if ( t . IsCompletedSuccessfully )
importButton . Complete ( ) ;
else
{
2022-05-18 20:22:17 +08:00
toggleInteraction ( true ) ;
2022-05-16 19:53:04 +08:00
importButton . Abort ( ) ;
}
2022-05-16 19:37:38 +08:00
} ) ) ;
2022-05-16 18:21:26 +08:00
}
2022-05-18 20:22:17 +08:00
private void toggleInteraction ( bool allow )
2022-05-17 16:26:44 +08:00
{
importButton . Enabled . Value = allow ;
stableLocatorTextBox . Current . Disabled = ! allow ;
foreach ( var c in contentCheckboxes )
c . Current . Disabled = ! allow ;
}
2022-12-20 05:33:35 +08:00
public override void OnSuspending ( ScreenTransitionEvent e )
{
stableLocatorTextBox . HidePopover ( ) ;
base . OnSuspending ( e ) ;
}
public override bool OnExiting ( ScreenExitEvent e )
{
stableLocatorTextBox . HidePopover ( ) ;
return base . OnExiting ( e ) ;
}
2022-05-16 19:53:04 +08:00
private partial class ImportCheckbox : SettingsCheckbox
2022-05-16 18:21:26 +08:00
{
2022-05-16 19:53:04 +08:00
public readonly StableContent StableContent ;
2022-05-16 18:21:26 +08:00
2022-05-16 19:53:04 +08:00
private readonly LocalisableString title ;
[Resolved]
private LegacyImportManager legacyImportManager { get ; set ; } = null ! ;
private CancellationTokenSource ? countUpdateCancellation ;
public ImportCheckbox ( LocalisableString title , StableContent stableContent )
{
this . title = title ;
2022-05-16 18:21:26 +08:00
2022-05-16 19:53:04 +08:00
StableContent = stableContent ;
2022-05-17 16:12:10 +08:00
Current . Default = true ;
2022-05-16 19:53:04 +08:00
Current . Value = true ;
LabelText = title ;
}
public void UpdateCount ( )
{
2022-05-17 16:58:24 +08:00
LabelText = LocalisableString . Interpolate ( $"{title} ({FirstRunOverlayImportFromStableScreenStrings.Calculating})" ) ;
2022-05-16 19:53:04 +08:00
countUpdateCancellation ? . Cancel ( ) ;
countUpdateCancellation = new CancellationTokenSource ( ) ;
legacyImportManager . GetImportCount ( StableContent , countUpdateCancellation . Token ) . ContinueWith ( task = > Schedule ( ( ) = >
{
if ( task . IsCanceled )
return ;
2022-05-17 16:58:24 +08:00
int count = task . GetResultSafely ( ) ;
LabelText = LocalisableString . Interpolate ( $"{title} ({FirstRunOverlayImportFromStableScreenStrings.Items(count)})" ) ;
2022-05-16 19:53:04 +08:00
} ) ) ;
}
2022-05-16 18:21:26 +08:00
}
2022-05-16 20:07:42 +08:00
2022-05-17 15:33:02 +08:00
internal partial class StableLocatorLabelledTextBox : LabelledTextBoxWithPopover , ICanAcceptFiles
2022-05-16 20:07:42 +08:00
{
2022-05-17 15:33:02 +08:00
[Resolved]
private LegacyImportManager legacyImportManager { get ; set ; } = null ! ;
2022-05-16 20:07:42 +08:00
2022-05-17 15:33:02 +08:00
public IEnumerable < string > HandledExtensions { get ; } = new [ ] { string . Empty } ;
2022-05-16 20:07:42 +08:00
2022-11-16 17:47:58 +08:00
private readonly Bindable < DirectoryInfo ? > currentDirectory = new Bindable < DirectoryInfo ? > ( ) ;
2022-05-16 20:07:42 +08:00
2022-05-19 12:49:52 +08:00
[Resolved(canBeNull: true)] // Can't really be null but required to handle potential of disposal before DI completes.
private OsuGameBase ? game { get ; set ; }
2022-05-16 20:07:42 +08:00
2022-05-17 15:33:02 +08:00
protected override void LoadComplete ( )
2022-05-16 20:07:42 +08:00
{
2022-05-17 15:33:02 +08:00
base . LoadComplete ( ) ;
2022-05-16 20:07:42 +08:00
2022-05-19 12:49:52 +08:00
game ? . RegisterImportHandler ( this ) ;
2022-05-16 20:07:42 +08:00
2022-05-17 15:33:02 +08:00
currentDirectory . BindValueChanged ( onDirectorySelected ) ;
string? fullPath = legacyImportManager . GetCurrentStableStorage ( ) ? . GetFullPath ( string . Empty ) ;
if ( fullPath ! = null )
currentDirectory . Value = new DirectoryInfo ( fullPath ) ;
}
2022-11-16 17:47:58 +08:00
private void onDirectorySelected ( ValueChangedEvent < DirectoryInfo ? > directory )
2022-05-16 20:07:42 +08:00
{
2022-05-17 16:51:28 +08:00
if ( directory . NewValue = = null )
2022-05-17 15:33:02 +08:00
{
Current . Value = string . Empty ;
return ;
}
2022-05-16 20:07:42 +08:00
2022-05-17 15:33:02 +08:00
// DirectorySelectors can trigger a noop value changed, but `DirectoryInfo` equality doesn't catch this.
2022-05-17 16:51:28 +08:00
if ( directory . OldValue ? . FullName = = directory . NewValue . FullName )
2022-05-17 15:33:02 +08:00
return ;
if ( directory . NewValue ? . GetFiles ( @"osu!.*.cfg" ) . Any ( ) ? ? false )
{
this . HidePopover ( ) ;
string path = directory . NewValue . FullName ;
legacyImportManager . UpdateStorage ( path ) ;
Current . Value = path ;
}
2022-05-16 20:07:42 +08:00
}
2022-05-17 15:33:02 +08:00
Task ICanAcceptFiles . Import ( params string [ ] paths )
2022-05-16 20:07:42 +08:00
{
2022-05-17 15:33:02 +08:00
Schedule ( ( ) = > currentDirectory . Value = new DirectoryInfo ( paths . First ( ) ) ) ;
return Task . CompletedTask ;
}
2022-12-13 20:03:25 +08:00
Task ICanAcceptFiles . Import ( ImportTask [ ] tasks , ImportParameters parameters ) = > throw new NotImplementedException ( ) ;
2022-05-17 15:33:02 +08:00
protected override void Dispose ( bool isDisposing )
{
base . Dispose ( isDisposing ) ;
2022-05-19 12:49:52 +08:00
game ? . UnregisterImportHandler ( this ) ;
2022-05-17 15:33:02 +08:00
}
public override Popover GetPopover ( ) = > new DirectoryChooserPopover ( currentDirectory ) ;
2022-05-16 20:07:42 +08:00
2022-05-17 15:33:02 +08:00
private partial class DirectoryChooserPopover : OsuPopover
{
2022-11-16 17:47:58 +08:00
public DirectoryChooserPopover ( Bindable < DirectoryInfo ? > currentDirectory )
2022-05-17 15:33:02 +08:00
{
Child = new Container
{
Size = new Vector2 ( 600 , 400 ) ,
Child = new OsuDirectorySelector ( currentDirectory . Value ? . FullName )
{
RelativeSizeAxes = Axes . Both ,
CurrentPath = { BindTarget = currentDirectory }
} ,
} ;
}
2022-05-16 20:07:42 +08:00
}
}
2022-05-16 18:21:26 +08:00
}
}