A game framework written with osu! in mind.
1// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
2// See the LICENCE file in the repository root for full licence text.
3
4using System;
5using System.Collections.Generic;
6using System.IO;
7using System.Linq;
8using JetBrains.Annotations;
9
10namespace osu.Framework.Platform
11{
12 public class NativeStorage : Storage
13 {
14 private readonly GameHost host;
15
16 public NativeStorage(string path, GameHost host = null)
17 : base(path)
18 {
19 this.host = host;
20 }
21
22 public override bool Exists(string path) => File.Exists(GetFullPath(path));
23
24 public override bool ExistsDirectory(string path) => Directory.Exists(GetFullPath(path));
25
26 public override void DeleteDirectory(string path)
27 {
28 path = GetFullPath(path);
29
30 // handles the case where the directory doesn't exist, which will throw a DirectoryNotFoundException.
31 if (Directory.Exists(path))
32 Directory.Delete(path, true);
33 }
34
35 public override void Delete(string path)
36 {
37 path = GetFullPath(path);
38
39 if (File.Exists(path))
40 File.Delete(path);
41 }
42
43 public override IEnumerable<string> GetDirectories(string path) => getRelativePaths(Directory.GetDirectories(GetFullPath(path)));
44
45 public override IEnumerable<string> GetFiles(string path, string pattern = "*") => getRelativePaths(Directory.GetFiles(GetFullPath(path), pattern));
46
47 private IEnumerable<string> getRelativePaths(IEnumerable<string> paths)
48 {
49 string basePath = Path.GetFullPath(GetFullPath(string.Empty));
50 return paths.Select(Path.GetFullPath).Select(path =>
51 {
52 if (!path.StartsWith(basePath, StringComparison.Ordinal)) throw new ArgumentException($"\"{path}\" does not start with \"{basePath}\" and is probably malformed");
53
54 return path.AsSpan(basePath.Length).TrimStart(Path.DirectorySeparatorChar).ToString();
55 });
56 }
57
58 public override string GetFullPath(string path, bool createIfNotExisting = false)
59 {
60 path = path.Replace(Path.AltDirectorySeparatorChar, Path.DirectorySeparatorChar);
61
62 var basePath = Path.GetFullPath(BasePath).TrimEnd(Path.DirectorySeparatorChar);
63 var resolvedPath = Path.GetFullPath(Path.Combine(basePath, path));
64
65 if (!resolvedPath.StartsWith(basePath, StringComparison.Ordinal)) throw new ArgumentException($"\"{resolvedPath}\" traverses outside of \"{basePath}\" and is probably malformed");
66
67 if (createIfNotExisting) Directory.CreateDirectory(Path.GetDirectoryName(resolvedPath));
68 return resolvedPath;
69 }
70
71 public override void OpenPathInNativeExplorer(string path) =>
72 host?.OpenFileExternally(GetFullPath(path));
73
74 public override Stream GetStream(string path, FileAccess access = FileAccess.Read, FileMode mode = FileMode.OpenOrCreate)
75 {
76 path = GetFullPath(path, access != FileAccess.Read);
77
78 if (string.IsNullOrEmpty(path))
79 throw new ArgumentNullException(nameof(path));
80
81 switch (access)
82 {
83 case FileAccess.Read:
84 if (!File.Exists(path)) return null;
85
86 return File.Open(path, FileMode.Open, access, FileShare.Read);
87
88 default:
89 return File.Open(path, mode, access);
90 }
91 }
92
93 public override string GetDatabaseConnectionString(string name) => string.Concat("Data Source=", GetFullPath($@"{name}.db", true));
94
95 public override void DeleteDatabase(string name) => Delete($@"{name}.db");
96
97 public override Storage GetStorageForDirectory([NotNull] string path)
98 {
99 if (path == null) throw new ArgumentNullException(nameof(path));
100
101 if (path.Length > 0 && !path.EndsWith(Path.DirectorySeparatorChar))
102 path += Path.DirectorySeparatorChar;
103
104 // create non-existing path.
105 var fullPath = GetFullPath(path, true);
106
107 return (Storage)Activator.CreateInstance(GetType(), fullPath, host);
108 }
109 }
110}