diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index c78ea962ff..36837044a1 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -6,8 +6,10 @@ using System.IO; using System.Linq; using osu.Framework; using osu.Framework.Desktop; +using osu.Framework.Desktop.Platform; using osu.Framework.Platform; using osu.Game; +using osu.Game.IPC; namespace osu.Desktop { @@ -16,19 +18,24 @@ namespace osu.Desktop [STAThread] public static int Main(string[] args) { - BasicGameHost host = Host.GetSuitableHost(@"osu"); - BaseGame osuGame = new OsuGame(); - if (args.Length != 0 && args.All(File.Exists)) + DesktopGameHost host = Host.GetSuitableHost(@"osu", true); + + if (!host.IsPrimaryInstance) { - host.Load(osuGame); - var beatmapIPC = new IpcChannel(host); + var importer = new BeatmapImporter(host); + foreach (var file in args) - beatmapIPC.SendMessage(new OsuGame.ImportBeatmap { Path = file }).Wait(); - Console.WriteLine(@"Sent file to running instance"); - return 0; + if (importer.Import(file).Wait(1000)) + throw new TimeoutException(@"IPC took too long to send"); + Console.WriteLine(@"Sent import requests to running instance"); } - host.Add(osuGame); - host.Run(); + else + { + BaseGame osu = new OsuGame(args); + host.Add(osu); + host.Run(); + } + return 0; } } diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs new file mode 100644 index 0000000000..e57fc71666 --- /dev/null +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -0,0 +1,109 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using NUnit.Framework; +using osu.Framework.Desktop.Platform; +using osu.Framework.Platform; +using osu.Game.Database; +using osu.Game.IPC; + +namespace osu.Game.Tests.Beatmaps.IO +{ + [TestFixture] + public class ImportBeatmapTest + { + const string osz_path = @"../../../osu-resources/osu.Game.Resources/Beatmaps/241526 Soleily - Renatus.osz"; + + [TestFixtureSetUp] + public void SetUp() + { + } + + [Test] + public void TestImportWhenClosed() + { + //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. + HeadlessGameHost host = new HeadlessGameHost(); + + var osu = loadOsu(host); + osu.Beatmaps.Import(osz_path); + ensureLoaded(osu); + } + + [Test] + public void TestImportOverIPC() + { + HeadlessGameHost host = new HeadlessGameHost(true); + HeadlessGameHost client = new HeadlessGameHost(true); + + Assert.IsTrue(host.IsPrimaryInstance); + Assert.IsTrue(!client.IsPrimaryInstance); + + var osu = loadOsu(host); + + var importer = new BeatmapImporter(client); + if (!importer.Import(osz_path).Wait(1000)) + Assert.Fail(@"IPC took too long to send"); + + ensureLoaded(osu, 10000); + } + + private OsuGameBase loadOsu(BasicGameHost host) + { + var osu = new OsuGameBase(); + host.Add(osu); + + //reset beatmap database (sqlite and storage backing) + osu.Beatmaps.Reset(); + + return osu; + } + + private void ensureLoaded(OsuGameBase osu, int timeout = 100) + { + IEnumerable resultSets = null; + + Action waitAction = () => + { + while ((resultSets = osu.Beatmaps.Query().Where(s => s.BeatmapSetID == 241526)).Count() != 1) + Thread.Sleep(1); + }; + + Assert.IsTrue(waitAction.BeginInvoke(null, null).AsyncWaitHandle.WaitOne(timeout), + @"BeatmapSet did not import to the database"); + + //ensure we were stored to beatmap database backing... + + Assert.IsTrue(resultSets.Count() == 1); + + IEnumerable resultBeatmaps = null; + + //if we don't re-check here, the set will be inserted but the beatmaps won't be present yet. + waitAction = () => + { + while ((resultBeatmaps = osu.Beatmaps.Query().Where(s => s.BeatmapSetID == 241526 && s.BaseDifficultyID > 0)).Count() != 12) + Thread.Sleep(1); + }; + + Assert.IsTrue(waitAction.BeginInvoke(null, null).AsyncWaitHandle.WaitOne(timeout), + @"Beatmaps did not import to the database"); + + //fetch children and check we can load from the post-storage path... + var set = osu.Beatmaps.GetChildren(resultSets.First()); + + Assert.IsTrue(set.Beatmaps.Count == resultBeatmaps.Count()); + + foreach (BeatmapInfo b in resultBeatmaps) + Assert.IsTrue(set.Beatmaps.Any(c => c.BeatmapID == b.BeatmapID)); + + Assert.IsTrue(set.Beatmaps.Count > 0); + + var beatmap = osu.Beatmaps.GetBeatmap(set.Beatmaps[0]); + + Assert.IsTrue(beatmap.HitObjects.Count > 0); + } + } +} + diff --git a/osu.Game.Tests/app.config b/osu.Game.Tests/app.config new file mode 100644 index 0000000000..44ccc4b77a --- /dev/null +++ b/osu.Game.Tests/app.config @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 264eabd85c..0718c61942 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -1,4 +1,4 @@ - + Debug @@ -30,6 +30,21 @@ ..\packages\NUnit.3.5.0\lib\net45\nunit.framework.dll True + + ..\packages\SQLite.Net-PCL.3.0.5\lib\net40\SQLite.Net.dll + True + + + ..\packages\SQLite.Net-PCL.3.0.5\lib\net40\SQLite.Net.Platform.Generic.dll + True + + + ..\packages\SQLite.Net-PCL.3.0.5\lib\net4\SQLite.Net.Platform.Win32.dll + True + + + + $(SolutionDir)\packages\NUnit.2.6.4\lib\nunit.framework.dll ..\packages\ppy.OpenTK.2.0.50727.1337\lib\net20\OpenTK.dll @@ -40,10 +55,19 @@ + + + {65DC628F-A640-4111-AB35-3A5652BC1E17} + osu.Framework.Desktop + + + {c76bf5b3-985e-4d39-95fe-97c9c879b83a} + osu.Framework + {0D3FBF8A-7464-4CF7-8C90-3E7886DF2D4D} osu.Game @@ -53,14 +77,10 @@ osu.Game.Resources - - - - - - + + diff --git a/osu.Game.Tests/packages.config b/osu.Game.Tests/packages.config index 00f92c3cb8..ca50867221 100644 --- a/osu.Game.Tests/packages.config +++ b/osu.Game.Tests/packages.config @@ -2,4 +2,5 @@ - \ No newline at end of file + + diff --git a/osu.Game/Database/BeatmapDatabase.cs b/osu.Game/Database/BeatmapDatabase.cs index 5e5db103ac..a39caac2fe 100644 --- a/osu.Game/Database/BeatmapDatabase.cs +++ b/osu.Game/Database/BeatmapDatabase.cs @@ -9,6 +9,7 @@ using osu.Framework.Platform; using osu.Game.Beatmaps; using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.IO; +using osu.Game.IPC; using SQLite.Net; using SQLiteNetExtensions.Extensions; @@ -19,10 +20,15 @@ namespace osu.Game.Database private static SQLiteConnection connection { get; set; } private BasicStorage storage; public event Action BeatmapSetAdded; - - public BeatmapDatabase(BasicStorage storage) + + private BeatmapImporter ipc; + + public BeatmapDatabase(BasicGameHost host) { - this.storage = storage; + this.storage = host.Storage; + + ipc = new BeatmapImporter(host, this); + if (connection == null) { connection = storage.GetDatabase(@"beatmaps"); @@ -44,58 +50,63 @@ namespace osu.Game.Database connection.DeleteAll(); } - public void Import(string path) + public void Import(params string[] paths) { - string hash = null; - BeatmapMetadata metadata; - - using (var reader = ArchiveReader.GetReader(storage, path)) - metadata = reader.ReadMetadata(); - - if (connection.Table().Count(b => b.BeatmapSetID == metadata.BeatmapSetID) != 0) - return; // TODO: Update this beatmap instead - - if (File.Exists(path)) // Not always the case, i.e. for LegacyFilesystemReader + foreach (string p in paths) { - using (var md5 = MD5.Create()) - using (var input = storage.GetStream(path)) + var path = p; + string hash = null; + + BeatmapMetadata metadata; + + using (var reader = ArchiveReader.GetReader(storage, path)) + metadata = reader.ReadMetadata(); + + if (connection.Table().Count(b => b.BeatmapSetID == metadata.BeatmapSetID) != 0) + return; // TODO: Update this beatmap instead + + if (File.Exists(path)) // Not always the case, i.e. for LegacyFilesystemReader { - hash = BitConverter.ToString(md5.ComputeHash(input)).Replace("-", "").ToLowerInvariant(); - input.Seek(0, SeekOrigin.Begin); - path = Path.Combine(@"beatmaps", hash.Remove(1), hash.Remove(2), hash); - using (var output = storage.GetStream(path, FileAccess.Write)) - input.CopyTo(output); - } - } - var beatmapSet = new BeatmapSetInfo - { - BeatmapSetID = metadata.BeatmapSetID, - Beatmaps = new List(), - Path = path, - Hash = hash, - Metadata = metadata - }; - - using (var reader = ArchiveReader.GetReader(storage, path)) - { - string[] mapNames = reader.ReadBeatmaps(); - foreach (var name in mapNames) - { - using (var stream = new StreamReader(reader.ReadFile(name))) + using (var md5 = MD5.Create()) + using (var input = storage.GetStream(path)) { - var decoder = BeatmapDecoder.GetDecoder(stream); - Beatmap beatmap = decoder.Decode(stream); - beatmap.BeatmapInfo.Path = name; - - // TODO: Diff beatmap metadata with set metadata and leave it here if necessary - beatmap.BeatmapInfo.Metadata = null; - - beatmapSet.Beatmaps.Add(beatmap.BeatmapInfo); + hash = BitConverter.ToString(md5.ComputeHash(input)).Replace("-", "").ToLowerInvariant(); + input.Seek(0, SeekOrigin.Begin); + path = Path.Combine(@"beatmaps", hash.Remove(1), hash.Remove(2), hash); + using (var output = storage.GetStream(path, FileAccess.Write)) + input.CopyTo(output); } } + var beatmapSet = new BeatmapSetInfo + { + BeatmapSetID = metadata.BeatmapSetID, + Beatmaps = new List(), + Path = path, + Hash = hash, + Metadata = metadata + }; + + using (var reader = ArchiveReader.GetReader(storage, path)) + { + string[] mapNames = reader.ReadBeatmaps(); + foreach (var name in mapNames) + { + using (var stream = new StreamReader(reader.ReadFile(name))) + { + var decoder = BeatmapDecoder.GetDecoder(stream); + Beatmap beatmap = decoder.Decode(stream); + beatmap.BeatmapInfo.Path = name; + + // TODO: Diff beatmap metadata with set metadata and leave it here if necessary + beatmap.BeatmapInfo.Metadata = null; + + beatmapSet.Beatmaps.Add(beatmap.BeatmapInfo); + } + } + } + connection.InsertWithChildren(beatmapSet, true); + BeatmapSetAdded?.Invoke(beatmapSet); } - connection.InsertWithChildren(beatmapSet, true); - BeatmapSetAdded?.Invoke(beatmapSet); } public ArchiveReader GetReader(BeatmapSetInfo beatmapSet) @@ -154,7 +165,7 @@ namespace osu.Game.Database typeof(BeatmapMetadata), typeof(BaseDifficulty), }; - + public void Update(T record, bool cascade = true) where T : class { if (!validTypes.Any(t => t == typeof(T))) diff --git a/osu.Game/IPC/BeatmapImporter.cs b/osu.Game/IPC/BeatmapImporter.cs new file mode 100644 index 0000000000..1c011ebf5b --- /dev/null +++ b/osu.Game/IPC/BeatmapImporter.cs @@ -0,0 +1,46 @@ +//Copyright (c) 2007-2016 ppy Pty Ltd . +//Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Diagnostics; +using System.Threading.Tasks; +using osu.Framework.Platform; +using osu.Game.Database; + +namespace osu.Game.IPC +{ + public class BeatmapImporter + { + private IpcChannel channel; + private BeatmapDatabase beatmaps; + + public BeatmapImporter(BasicGameHost host, BeatmapDatabase beatmaps = null) + { + this.beatmaps = beatmaps; + + channel = new IpcChannel(host); + channel.MessageReceived += messageReceived; + } + + public async Task Import(string path) + { + if (beatmaps != null) + beatmaps.Import(path); + else + { + await channel.SendMessage(new BeatmapImportMessage { Path = path }); + } + } + + private void messageReceived(BeatmapImportMessage msg) + { + Debug.Assert(beatmaps != null); + + Import(msg.Path); + } + } + + public class BeatmapImportMessage + { + public string Path; + } +} diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 11fc3fd33c..db74e2f020 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -9,38 +9,33 @@ using osu.Game.GameModes.Menu; using OpenTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Sprites; using osu.Framework.Platform; -using osu.Game.GameModes; -using osu.Game.Graphics.Background; using osu.Game.GameModes.Play; -using osu.Game.Graphics.Containers; using osu.Game.Overlays; using osu.Framework; using osu.Framework.Input; using osu.Game.Input; using OpenTK.Input; -using System.IO; -using osu.Game.Beatmaps.IO; using osu.Framework.Logging; namespace osu.Game { public class OsuGame : OsuGameBase { - public class ImportBeatmap - { - public string Path; - } - public Toolbar Toolbar; public ChatConsole Chat; public MainMenu MainMenu => intro?.ChildGameMode as MainMenu; private Intro intro; - private IpcChannel BeatmapIPC; public Bindable PlayMode; + string[] args; + + public OsuGame(string[] args = null) + { + this.args = args; + } + public override void SetHost(BasicGameHost host) { base.SetHost(host); @@ -50,30 +45,17 @@ namespace osu.Game public override void Load(BaseGame game) { - BeatmapIPC = new IpcChannel(Host); - if (!Host.IsPrimaryInstance) { Logger.Log(@"osu! does not support multiple running instances.", LoggingTarget.Runtime, LogLevel.Error); Environment.Exit(0); } - BeatmapIPC.MessageReceived += message => - { - try - { - Beatmaps.ImportBeatmap(message.Path); - // TODO: Switch to beatmap list and select the new song - } - catch (Exception ex) - { - // TODO: Show the user some info? - Logger.Log($@"Failed to import beatmap: {ex}", LoggingTarget.Runtime, LogLevel.Error); - } - }; - base.Load(game); + if (args?.Length > 0) + Schedule(delegate { Beatmaps.Import(args); }); + //attach our bindables to the audio subsystem. Audio.Volume.Weld(Config.GetBindable(OsuConfig.VolumeGlobal)); Audio.VolumeSample.Weld(Config.GetBindable(OsuConfig.VolumeEffect)); diff --git a/osu.Game/OsuGameBase.cs b/osu.Game/OsuGameBase.cs index ba6dd19506..96b56f1d1b 100644 --- a/osu.Game/OsuGameBase.cs +++ b/osu.Game/OsuGameBase.cs @@ -10,6 +10,7 @@ using osu.Game.Configuration; using osu.Game.Database; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.Processing; +using osu.Game.IPC; using osu.Game.Online.API; using osu.Game.Overlays; @@ -18,7 +19,7 @@ namespace osu.Game public class OsuGameBase : BaseGame { internal OsuConfigManager Config = new OsuConfigManager(); - internal BeatmapDatabase Beatmaps { get; private set; } + public BeatmapDatabase Beatmaps { get; private set; } protected override string MainResourceFile => @"osu.Game.Resources.dll"; @@ -47,7 +48,7 @@ namespace osu.Game base.Load(game); OszArchiveReader.Register(); - Beatmaps = new BeatmapDatabase(Host.Storage); + Beatmaps = new BeatmapDatabase(Host); //this completely overrides the framework default. will need to change once we make a proper FontStore. Fonts = new TextureStore() { ScaleAdjust = 0.01f }; diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 0ee86c5237..4041045343 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -156,6 +156,7 @@ +