2021-11-25 15:36:30 +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-06-17 15:37:17 +08:00
#nullable disable
2023-02-16 03:15:44 +08:00
using System ;
2022-11-30 22:36:08 +08:00
using System.Collections.Generic ;
2021-11-25 15:36:30 +08:00
using System.IO ;
2023-02-15 15:11:16 +08:00
using System.Linq ;
2021-11-25 15:36:30 +08:00
using osu.Framework.Platform ;
using osu.Game.Extensions ;
2022-11-30 18:06:44 +08:00
using osu.Game.Utils ;
2021-11-25 15:36:30 +08:00
using SharpCompress.Archives.Zip ;
namespace osu.Game.Database
{
/// <summary>
/// A class which handles exporting legacy user data of a single type from osu-stable.
/// </summary>
public abstract class LegacyExporter < TModel >
where TModel : class , IHasNamedFiles
{
2023-02-16 03:15:44 +08:00
/// <summary>
2023-02-17 12:33:22 +08:00
/// Max length of filename (including extension).
2023-02-16 03:15:44 +08:00
/// </summary>
/// <remarks>
2023-02-17 20:23:43 +08:00
/// <para>
2023-02-17 20:24:27 +08:00
/// The filename limit for most OSs is 255. This actual usable length is smaller because <see cref="Storage.CreateFileSafely(string)"/> adds an additional "_<see cref="Guid"/>" to the end of the path.
2023-02-17 20:23:43 +08:00
/// </para>
/// <para>
2023-02-17 20:32:36 +08:00
/// For more information see <see href="https://www.ibm.com/docs/en/spectrum-protect/8.1.9?topic=parameters-file-specification-syntax">file specification syntax</see>, <seealso href="https://en.wikipedia.org/wiki/Comparison_of_file_systems#Limits">file systems limitations</seealso>
2023-02-17 20:23:43 +08:00
/// </para>
2023-02-16 03:15:44 +08:00
/// </remarks>
2023-02-17 12:34:19 +08:00
public const int MAX_FILENAME_LENGTH = 255 - ( 32 + 4 + 2 ) ; //max path - (Guid + Guid "D" format chars + Storage.CreateFileSafely chars)
2023-02-16 03:15:44 +08:00
2021-11-25 15:36:30 +08:00
/// <summary>
/// The file extension for exports (including the leading '.').
/// </summary>
protected abstract string FileExtension { get ; }
protected readonly Storage UserFileStorage ;
2022-11-30 18:06:44 +08:00
private readonly Storage exportStorage ;
2021-11-25 15:36:30 +08:00
2021-11-25 17:36:01 +08:00
protected LegacyExporter ( Storage storage )
2021-11-25 15:36:30 +08:00
{
2022-11-30 18:06:44 +08:00
exportStorage = storage . GetStorageForDirectory ( @"exports" ) ;
2021-11-25 15:36:30 +08:00
UserFileStorage = storage . GetStorageForDirectory ( @"files" ) ;
}
2023-02-16 03:43:43 +08:00
protected virtual string GetFilename ( TModel item ) = > item . GetDisplayString ( ) ;
2022-12-25 06:18:42 +08:00
2021-11-25 15:36:30 +08:00
/// <summary>
/// Exports an item to a legacy (.zip based) package.
/// </summary>
/// <param name="item">The item to export.</param>
2022-12-25 06:18:42 +08:00
public void Export ( TModel item )
2021-11-25 15:36:30 +08:00
{
2022-12-26 05:50:56 +08:00
string itemFilename = GetFilename ( item ) . GetValidFilename ( ) ;
2022-11-30 18:06:44 +08:00
2023-02-15 15:11:16 +08:00
IEnumerable < string > existingExports =
exportStorage
. GetFiles ( string . Empty , $"{itemFilename}*{FileExtension}" )
. Concat ( exportStorage . GetDirectories ( string . Empty ) ) ;
2022-12-26 05:50:56 +08:00
string filename = NamingUtils . GetNextBestFilename ( existingExports , $"{itemFilename}{FileExtension}" ) ;
2023-02-16 03:43:43 +08:00
2023-02-17 12:33:22 +08:00
if ( filename . Length > MAX_FILENAME_LENGTH )
2023-02-16 03:43:43 +08:00
{
string filenameWithoutExtension = Path . GetFileNameWithoutExtension ( filename ) ;
2023-02-17 18:46:06 +08:00
filenameWithoutExtension = filenameWithoutExtension . Remove ( MAX_FILENAME_LENGTH - FileExtension . Length ) ;
2023-02-16 03:43:43 +08:00
filename = $"{filenameWithoutExtension}{FileExtension}" ;
}
2022-11-30 18:06:44 +08:00
using ( var stream = exportStorage . CreateFileSafely ( filename ) )
2021-11-25 15:36:30 +08:00
ExportModelTo ( item , stream ) ;
2022-11-30 18:06:44 +08:00
exportStorage . PresentFileExternally ( filename ) ;
2021-11-25 15:36:30 +08:00
}
/// <summary>
/// Exports an item to the given output stream.
/// </summary>
/// <param name="model">The item to export.</param>
/// <param name="outputStream">The output stream to export to.</param>
public virtual void ExportModelTo ( TModel model , Stream outputStream )
{
using ( var archive = ZipArchive . Create ( ) )
{
foreach ( var file in model . Files )
archive . AddEntry ( file . Filename , UserFileStorage . GetStream ( file . File . GetStoragePath ( ) ) ) ;
archive . SaveTo ( outputStream ) ;
}
}
}
}