// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. #nullable disable using System; using System.Collections.Generic; using System.IO; using System.Linq; using osu.Framework.Platform; using osu.Game.Extensions; using osu.Game.Utils; using SharpCompress.Archives.Zip; namespace osu.Game.Database { /// /// A class which handles exporting legacy user data of a single type from osu-stable. /// public abstract class LegacyExporter where TModel : class, IHasNamedFiles { /// /// Max length of filename (including extension). /// /// /// /// The filename limit for most OSs is 255. This actual usable length is smaller because adds an additional "_" to the end of the path. /// /// /// For more information see: ///
/// File specification syntax ///
///
/// File systems limitations ///
///
///
public const int MAX_FILENAME_LENGTH = 255 - (32 + 4 + 2); //max path - (Guid + Guid "D" format chars + Storage.CreateFileSafely chars) /// /// The file extension for exports (including the leading '.'). /// protected abstract string FileExtension { get; } protected readonly Storage UserFileStorage; private readonly Storage exportStorage; protected LegacyExporter(Storage storage) { exportStorage = storage.GetStorageForDirectory(@"exports"); UserFileStorage = storage.GetStorageForDirectory(@"files"); } protected virtual string GetFilename(TModel item) => item.GetDisplayString(); /// /// Exports an item to a legacy (.zip based) package. /// /// The item to export. public void Export(TModel item) { string itemFilename = GetFilename(item).GetValidFilename(); IEnumerable existingExports = exportStorage .GetFiles(string.Empty, $"{itemFilename}*{FileExtension}") .Concat(exportStorage.GetDirectories(string.Empty)); string filename = NamingUtils.GetNextBestFilename(existingExports, $"{itemFilename}{FileExtension}"); if (filename.Length > MAX_FILENAME_LENGTH) { string filenameWithoutExtension = Path.GetFileNameWithoutExtension(filename); filenameWithoutExtension = filenameWithoutExtension.Remove(MAX_FILENAME_LENGTH - FileExtension.Length); filename = $"{filenameWithoutExtension}{FileExtension}"; } using (var stream = exportStorage.CreateFileSafely(filename)) ExportModelTo(item, stream); exportStorage.PresentFileExternally(filename); } /// /// Exports an item to the given output stream. /// /// The item to export. /// The output stream to export to. 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); } } } }