Skip to content

Commit 7b79e6c

Browse files
committed
feat: Add remote game scan
1 parent 759d7c8 commit 7b79e6c

9 files changed

Lines changed: 153 additions & 23 deletions

File tree

VNGod/Network/WebDAVClient.cs

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Linq;
55
using System.Net;
66
using System.Threading.Tasks;
7+
using System.Windows.Navigation;
78
using VNGod.Properties;
89
using WebDav;
910

@@ -57,7 +58,18 @@ public async static Task<bool> InitializeClient()
5758
client = null;
5859
return false;
5960
}
60-
61+
private async static Task<string?> GetBaseUriAsync(string requestUri)
62+
{
63+
var response = await client!.Propfind(requestUri, new() { Headers = new Dictionary<string, string> { { "Depth", "0" } } });
64+
if (response.IsSuccessful)
65+
{
66+
return response.Resources.First().Uri!.ToString();
67+
}
68+
else
69+
{
70+
return null;
71+
}
72+
}
6173
/// <summary>
6274
/// Test WebDAV connection.
6375
/// </summary>
@@ -70,7 +82,7 @@ public static async Task<bool> TestConnectionAsync(WebDavClient testClient)
7082
var response = await testClient.Propfind("");
7183
if (response.IsSuccessful)
7284
{
73-
Logger.Info("WebDAV connection successful.");
85+
Logger.Debug("WebDAV connection successful.");
7486
return true;
7587
}
7688
else
@@ -85,7 +97,40 @@ public static async Task<bool> TestConnectionAsync(WebDavClient testClient)
8597
return false;
8698
}
8799
}
88-
100+
/// <summary>
101+
/// List remote directory contents.
102+
/// </summary>
103+
/// <param name="requestUri">The request uri to search sub dir</param>
104+
/// <param name="remoteFileList">The list to add result in</param>
105+
/// <returns>If the operation is successful</returns>
106+
public static async Task<bool> ListRemoteAsync(string requestUri, List<string> remoteFileList)
107+
{
108+
try
109+
{
110+
string? baseUri = await GetBaseUriAsync(requestUri);
111+
if (baseUri == null) return false;
112+
var response = await client!.Propfind(requestUri);
113+
if (response.IsSuccessful)
114+
{
115+
foreach (var resource in response.Resources)
116+
{
117+
if (resource.Uri!.ToString() == baseUri) continue; // Skip root dir itself
118+
remoteFileList.Add(resource.Uri!.ToString());
119+
}
120+
Logger.Debug($"Successfully listed files in remote");
121+
return true;
122+
}
123+
else
124+
{
125+
throw new Exception($"Failed to list files in remote. Status code: {response.StatusCode}");
126+
}
127+
}
128+
catch (Exception ex)
129+
{
130+
Logger.Error($"Exception during listing files: {ex.Message}", ex);
131+
return false;
132+
}
133+
}
89134
/// <summary>
90135
/// Delete a file or directory from the WebDAV server.
91136
/// </summary>
@@ -124,7 +169,7 @@ public static async Task<bool> UploadFileAsync(string localFilePath, string remo
124169
var response = await client!.PutFile(remoteFilePath, fileStream);
125170
if (response.IsSuccessful)
126171
{
127-
Logger.Info($"Successfully uploaded {localFilePath} to {remoteFilePath}");
172+
Logger.Debug($"Successfully uploaded {localFilePath} to {remoteFilePath}");
128173
return true;
129174
}
130175
else
@@ -159,7 +204,7 @@ public static async Task<bool> DownloadFileAsync(string remoteFilePath, string l
159204
await response.Stream.CopyToAsync(fileStream);
160205
}
161206
File.SetLastAccessTime(localFilePath, time ?? throw new NullReferenceException("Null Remote Time."));//Keep the access time consistent
162-
Logger.Info($"Successfully downloaded {remoteFilePath} to {localFilePath}");
207+
Logger.Debug($"Successfully downloaded {remoteFilePath} to {localFilePath}");
163208
return true;
164209
}
165210
else
@@ -170,7 +215,7 @@ public static async Task<bool> DownloadFileAsync(string remoteFilePath, string l
170215
}
171216
catch (Exception ex)
172217
{
173-
Logger.Error($"Exception during file download: {ex.Message}", ex);
218+
Logger.Error($"Exception during file download {remoteFilePath} to {localFilePath}: {ex.Message}", ex);
174219
return false;
175220
}
176221
}

VNGod/Resource/Strings/Strings.Designer.cs

Lines changed: 18 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

VNGod/Resource/Strings/Strings.resx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -295,4 +295,10 @@
295295
<data name="About" xml:space="preserve">
296296
<value>About</value>
297297
</data>
298+
<data name="LocalGames" xml:space="preserve">
299+
<value>Local Games</value>
300+
</data>
301+
<data name="RemoteGames" xml:space="preserve">
302+
<value>Remote Games (Re-open the window to refresh)</value>
303+
</data>
298304
</root>

VNGod/Resource/Strings/Strings.zh-CN.resx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,4 +277,10 @@
277277
<data name="About" xml:space="preserve">
278278
<value>关于</value>
279279
</data>
280+
<data name="LocalGames" xml:space="preserve">
281+
<value>本地游戏</value>
282+
</data>
283+
<data name="RemoteGames" xml:space="preserve">
284+
<value>远程游戏(关闭后重新打开此窗口刷新)</value>
285+
</data>
280286
</root>

VNGod/Utils/FileHelper.cs

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ namespace VNGod.Utils
1515
static class FileHelper
1616
{
1717
private static readonly ILog logger = LogManager.GetLogger(typeof(FileHelper));
18+
public static readonly string tmpPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Temp");
1819
/// <summary>
1920
/// Initializes a repo at the given path by reading its metadata and scanning for games.
2021
/// </summary>
@@ -43,7 +44,7 @@ public static void ScanGames(Repo repo)
4344
// Check if .vngodignore file exists
4445
if (File.Exists(Path.Combine(dir, ".vngodignore")))
4546
{
46-
logger.Info($"Directory {dir} is ignored.");
47+
logger.Debug($"Directory {dir} is ignored.");
4748
continue;
4849
}
4950
// Check if .vngod file exists
@@ -114,9 +115,9 @@ public static void AddGameIgnore(Repo repo,Game game)
114115
/// Load metadata for all games in the repo from their .vngod files.
115116
/// </summary>
116117
/// <param name="repo"></param>
117-
public static void ReadMetadata(Repo repo)
118+
public static void ReadRepoMetadata(Repo repo)
118119
{
119-
logger.Info("Reading metadata from " + repo.LocalPath);
120+
logger.Debug("Reading metadata from " + repo.LocalPath);
120121
for (int i = 0; i < repo.Count; i++)
121122
{
122123
Game game = repo[i];
@@ -139,6 +140,22 @@ public static void ReadMetadata(Repo repo)
139140
}
140141

141142
}
143+
public static Game ReadGameMetadata(string metaDataPath)
144+
{
145+
try
146+
{
147+
using StreamReader reader = new(metaDataPath);
148+
XmlSerializer serializer = new(typeof(Game));
149+
Game game = (Game?)serializer.Deserialize(reader) ?? throw new Exception("Null game");
150+
return game;
151+
}
152+
catch (Exception ex)
153+
{
154+
Exception readEx = new($"Failed to read .vngod file in {metaDataPath}. Not a valid game directory.\n{ex.Message}");
155+
logger.Error(readEx.Message, readEx);
156+
throw readEx;
157+
}
158+
}
142159
/// <summary>
143160
/// Read repo metadata from disk.
144161
/// </summary>

VNGod/Utils/WebDAVHelper.cs

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
using log4net;
1+
using HandyControl.Controls;
2+
using log4net;
23
using System.IO;
34
using VNGod.Data;
45
using VNGod.Models;
@@ -36,12 +37,12 @@ public static async Task<bool> SyncMetadataAsync(Repo repo)
3637
var localMetaPath = Path.Combine(repo.LocalPath, game.DirectoryName, ".vngod");
3738
var remoteMetaPath = $"{game.DirectoryName}/.vngod";
3839
var timeComparison = WebDAVClient.CompareFileDate(remoteMetaPath, localMetaPath);
39-
Logger.Info($"Comparing metadata for game {game.DirectoryName}: Time comparison result = {timeComparison}");
40+
Logger.Debug($"Comparing metadata for game {game.DirectoryName}: Time comparison result = {timeComparison}");
4041
if (timeComparison == -1 || timeComparison == 404)
4142
{
4243
if (await WebDAVClient.UploadFileAsync(localMetaPath, remoteMetaPath))
4344
{
44-
Logger.Info($"Uploaded metadata for game {game.DirectoryName}.");
45+
Logger.Debug($"Uploaded metadata for game {game.DirectoryName}.");
4546
}
4647
else
4748
throw new Exception("Upload metadata failed.");
@@ -50,14 +51,14 @@ public static async Task<bool> SyncMetadataAsync(Repo repo)
5051
{
5152
if (await WebDAVClient.DownloadFileAsync(remoteMetaPath, localMetaPath))
5253
{
53-
Logger.Info($"Downloaded metadata for game {game.DirectoryName}.");
54+
Logger.Debug($"Downloaded metadata for game {game.DirectoryName}.");
5455
}
5556
else
5657
throw new Exception("Download metadata failed.");
5758
}
5859
else
5960
{
60-
Logger.Info($"Metadata for game {game.DirectoryName} is up to date.");
61+
Logger.Debug($"Metadata for game {game.DirectoryName} is up to date.");
6162
}
6263
}
6364
catch (Exception ex)
@@ -92,7 +93,7 @@ public static async Task<bool> SyncGameAsync(Game game)
9293
}
9394
if (timeComparison == -1 || timeComparison == 404)
9495
{
95-
Logger.Info("Local save is newer or remote not found. Uploading...");
96+
Logger.Debug("Local save is newer or remote not found. Uploading...");
9697
// Upload the zip file to WebDAV server
9798
var uploadSuccess = await WebDAVClient.UploadFileAsync(tempZipPath, remoteSavePath);
9899
if (!uploadSuccess)
@@ -103,7 +104,7 @@ public static async Task<bool> SyncGameAsync(Game game)
103104
}
104105
else
105106
{
106-
Logger.Info("Remote save is newer or same. Downloading...");
107+
Logger.Debug("Remote save is newer or same. Downloading...");
107108
var downloadSuccess = await WebDAVClient.DownloadFileAsync(remoteSavePath, tempZipPath);
108109
if (!downloadSuccess)
109110
{
@@ -133,7 +134,7 @@ public static async Task<bool> UploadGameAsync(Repo repo, Game game, IProgress<S
133134
{
134135
try
135136
{
136-
string tmpPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "Temp", game.DirectoryName);
137+
string tmpPath = Path.Combine(FileHelper.tmpPath, game.DirectoryName);
137138
if (Directory.Exists(tmpPath))
138139
Directory.Delete(tmpPath, true);
139140
if (!await WebDAVClient.DeleteRemoteAsync($"{game.DirectoryName}/game"))
@@ -158,5 +159,31 @@ public static async Task<bool> UploadGameAsync(Repo repo, Game game, IProgress<S
158159
return false;
159160
}
160161
}
162+
public static async Task<Repo> GetRemoteGamesAsync()
163+
{
164+
Repo games = new() { LocalPath = "WebDAV" };
165+
List<string> remotePaths = new();
166+
// Get remote directories under root dir, containg games
167+
await WebDAVClient.ListRemoteAsync("", remotePaths);
168+
string tmpPath = Path.Combine(FileHelper.tmpPath, ".vngodtmp");
169+
// Get remote games
170+
foreach (var remotePath in remotePaths)
171+
{
172+
// Skip if no remote game folder
173+
if (await WebDAVClient.ListRemoteAsync($"{remotePath}/game", new()))
174+
{
175+
if (await WebDAVClient.DownloadFileAsync($"{remotePath}/.vngod", tmpPath))
176+
{
177+
Game game = FileHelper.ReadGameMetadata(tmpPath);
178+
games.Add(game);
179+
}
180+
else
181+
{
182+
Growl.Warning("Error getting remote games");
183+
}
184+
}
185+
}
186+
return games;
187+
}
161188
}
162189
}

VNGod/View/GameStorageWindow.xaml

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,14 @@
88
xmlns:strings="clr-namespace:VNGod.Resource.Strings"
99
xmlns:model="clr-namespace:VNGod.Models"
1010
mc:Ignorable="d"
11-
Closing="Window_Closing"
11+
Closing="Window_Closing" IsVisibleChanged="Window_IsVisibleChanged"
1212
Title="GameStorage" Height="500" Width="800">
1313
<Window.Resources>
1414
<model:WindowStatus x:Key="windowStatus"/>
1515
</Window.Resources>
1616
<Grid>
1717
<Grid.RowDefinitions>
18+
<RowDefinition Height="30"/>
1819
<RowDefinition/>
1920
<RowDefinition Height="30"/>
2021
</Grid.RowDefinitions>
@@ -23,7 +24,9 @@
2324
<ColumnDefinition/>
2425
<ColumnDefinition Width="2*"/>
2526
</Grid.ColumnDefinitions>
26-
<ListBox x:Name="localGameList">
27+
<TextBlock Grid.Row="0" Grid.Column="0" HorizontalAlignment="Center" VerticalAlignment="Center" FontWeight="Bold" Text="{x:Static strings:Strings.LocalGames}"/>
28+
<TextBlock Grid.Row="0" Grid.Column="2" HorizontalAlignment="Center" VerticalAlignment="Center" FontWeight="Bold" Text="{x:Static strings:Strings.RemoteGames}"/>
29+
<ListBox x:Name="localGameList" Grid.Row="1">
2730
<ListBox.ItemTemplate>
2831
<DataTemplate>
2932
<Grid Margin="5">
@@ -37,14 +40,14 @@
3740
</DataTemplate>
3841
</ListBox.ItemTemplate>
3942
</ListBox>
40-
<StackPanel Grid.Column="1" VerticalAlignment="Center">
43+
<StackPanel Grid.Column="1" Grid.Row="1" VerticalAlignment="Center">
4144
<Button x:Name="uploadButton" Margin="20" Click="UploadButton_Click" IsEnabled="{DynamicResource WindowStatus.IsBusy}">Upload</Button>
4245
<Button x:Name="downloadButton" Margin="20" IsEnabled="{DynamicResource WindowStatus.IsBusy}">Download</Button>
4346
<Button x:Name="deleteLocalButton" Margin="20" IsEnabled="{DynamicResource WindowStatus.IsBusy}">Delete Local</Button>
4447
<Button x:Name="deleteRemoteButton" Margin="20" IsEnabled="{DynamicResource WindowStatus.IsBusy}">Delete Remote</Button>
4548
<TextBlock x:Name="statusText" HorizontalAlignment="Center" Margin="20" Text="Status"/>
4649
</StackPanel>
47-
<ListBox x:Name="remoteGameList" Grid.Column="2"></ListBox>
48-
<ProgressBar x:Name="workProgress" Grid.Row="1" Grid.ColumnSpan="3" Margin="20,0"/>
50+
<ListBox x:Name="remoteGameList" Grid.Column="2" Grid.Row="1"></ListBox>
51+
<ProgressBar x:Name="workProgress" Grid.Row="2" Grid.ColumnSpan="3" Margin="20,0"/>
4952
</Grid>
5053
</hc:Window>

VNGod/View/GameStorageWindow.xaml.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,5 +65,13 @@ private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs
6565
}
6666
Hide();
6767
}
68+
69+
private async void Window_IsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
70+
{
71+
if(e.NewValue is true)
72+
{
73+
remoteGameList.ItemsSource = await WebDAVHelper.GetRemoteGamesAsync();
74+
}
75+
}
6876
}
6977
}

VNGod/View/MainWindow.xaml.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ private async Task SaveAndSync(bool overwrite, bool missingLocal = false)
116116
Growl.Success(Strings.WebDAVSyncSuccess);
117117
else Growl.Warning(Strings.WebDAVSyncFailed);
118118
int index = gameList.SelectedIndex;
119-
FileHelper.ReadMetadata(repo);
119+
FileHelper.ReadRepoMetadata(repo);
120120
if (index >= 0) gameList.SelectedIndex = index;
121121
}
122122
}

0 commit comments

Comments
 (0)