From c22febc73c70009145c01c3f6204516089995249 Mon Sep 17 00:00:00 2001 From: YaR Date: Thu, 20 Jul 2023 21:53:41 +0300 Subject: [PATCH 01/77] Update readme.md --- readme.md | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/readme.md b/readme.md index 588e3032..6dff43d7 100644 --- a/readme.md +++ b/readme.md @@ -1,14 +1,10 @@ ## **WebDAV emulator for Cloud.mail.ru / Yandex.Disk**
- - - - - - + + ---- -ZZConsulting: +@ZZZConsulting:
Дополнительно сделан вход на Яндекс.Диск с помощью браузера.
Поддерживается всё разнообразие вариантов аутентификации, включая СМС-коды и QR-коды. From 939981e9fc95135ef03a010f5a93755bf7aa1c83 Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Fri, 13 Oct 2023 17:40:45 +0300 Subject: [PATCH 02/77] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20.gitattributes=20=D1=87=D1=82=D0=BE=D0=B1=D1=8B?= =?UTF-8?q?=20LF=20=D0=BD=D0=B5=20=D0=B7=D0=B0=D0=BC=D0=B5=D0=BD=D1=8F?= =?UTF-8?q?=D0=BB=D1=81=D1=8F=20=D0=BD=D0=B0=20CR-LF=20=D0=B8=20=D0=BE?= =?UTF-8?q?=D0=B1=D1=80=D0=B0=D1=82=D0=BD=D0=BE.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitattributes | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..805922a6 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,18 @@ +* text=auto + +*.txt text eol=lf +*.xml text eol=lf +*.json text eol=lf +*.properties text eol=lf +*.config text eol=lf +*.conf text eol=lf +*.md text eol=lf +*.sln text eol=lf +*.csproj text eol=lf +*.cs text eol=lf +*.yml text eol=lf +*.resx text eol=lf + +*.png binary +*.jpg binary +*.ico binary From d5cd9d62a473901cb085c1e42637d386983805e6 Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Fri, 13 Oct 2023 18:40:56 +0300 Subject: [PATCH 03/77] Code cleanup --- .../Repos/YandexDisk/YadWebV2/YadWebAuth.cs | 26 ++++++++--------- WDMRC.Console/Config.cs | 28 +++++++++---------- .../CustomHandlers/GetAndHeadHandler.cs | 4 +-- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/YadWebAuth.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/YadWebAuth.cs index 832f986f..8c2bc603 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/YadWebAuth.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/YadWebAuth.cs @@ -18,7 +18,7 @@ class YadWebAuth : IAuth public YadWebAuth(HttpCommonSettings settings, IBasicCredentials creds) { - + _settings = settings; _creds = creds; Cookies = new CookieContainer(); @@ -90,7 +90,7 @@ public YadWebAuth(HttpCommonSettings settings, IBasicCredentials creds, string p DiskSk = /*YadAuth.DiskSk*/ response.Sk; Uuid = /*YadAuth.Uuid*/response.Uuid; //yandexuid - foreach(var item in response.Cookies) + foreach (var item in response.Cookies) { var cookie = new Cookie(item.Name, item.Value, item.Path, item.Domain); Cookies.Add(cookie); @@ -104,7 +104,7 @@ public async Task MakeLogin() { (BrowserAppResponse response, string responseHtml) = await ConnectToBrowserApp(); - if(response!=null && + if (response != null && !string.IsNullOrEmpty(response.Sk) && !string.IsNullOrEmpty(response.Uuid) && !string.IsNullOrEmpty(response.Login) && @@ -116,14 +116,14 @@ public async Task MakeLogin() DiskSk = /*YadAuth.DiskSk*/ response.Sk; Uuid = /*YadAuth.Uuid*/response.Uuid; //yandexuid - foreach(var item in response.Cookies) + foreach (var item in response.Cookies) { var cookie = new Cookie(item.Name, item.Value, item.Path, item.Domain); Cookies.Add(cookie); } // Если аутентификация прошла успешно, сохраняем результат в кеш в файл - if(!string.IsNullOrEmpty(_settings.CloudSettings.BrowserAuthenticatorCacheDir)) + if (!string.IsNullOrEmpty(_settings.CloudSettings.BrowserAuthenticatorCacheDir)) { string path = Path.Combine( _settings.CloudSettings.BrowserAuthenticatorCacheDir, @@ -132,10 +132,10 @@ public async Task MakeLogin() try { string dir = Path.GetDirectoryName(path); - if(!Directory.Exists(dir)) + if (!Directory.Exists(dir)) Directory.CreateDirectory(dir); } - catch(Exception) + catch (Exception) { throw new AuthenticationException("Directory for cache can not be created, " + "remove attribute CacheDir in BrowserAuthenticator tag in configuration file!"); @@ -153,9 +153,9 @@ public async Task MakeLogin() } else { - if(string.IsNullOrEmpty(response?.ErrorMessage)) + if (string.IsNullOrEmpty(response?.ErrorMessage)) throw new AuthenticationException("OAuth: Authentication using YandexAuthBrowser is failed!"); - + throw new AuthenticationException( string.Concat( "OAuth: Authentication using YandexAuthBrowser is failed! ", @@ -211,12 +211,12 @@ public class BrowserAppCookieResponse private static string GetNameOnly(string value) { - if(string.IsNullOrEmpty(value)) + if (string.IsNullOrEmpty(value)) return value; int pos = value.IndexOf('@'); - if(pos==0) + if (pos == 0) return ""; - if(pos>0) + if (pos > 0) return value.Substring(0, pos); return value; } @@ -231,7 +231,7 @@ private static string GetNameOnly(string value) ? _settings.CloudSettings.BrowserAuthenticatorPassword : Password; - if(string.IsNullOrEmpty(url)) + if (string.IsNullOrEmpty(url)) { throw new Exception("Ошибка! " + "Для работы с Яндекс.Диском запустите сервер аутентификации и задайте в параметре YandexAuthenticationUrl его настройки!"); diff --git a/WDMRC.Console/Config.cs b/WDMRC.Console/Config.cs index cece2a5e..8ce81167 100644 --- a/WDMRC.Console/Config.cs +++ b/WDMRC.Console/Config.cs @@ -15,7 +15,7 @@ internal static class Config static Config() { Document = new XmlDocument(); - string location = Path.GetDirectoryName(typeof(Config).Assembly.Location) + string location = Path.GetDirectoryName(typeof(Config).Assembly.Location) ?? throw new DirectoryNotFoundException("Cannot locate assembly directory"); var configpath = Path.Combine(location, "wdmrc.config"); Document.Load(File.OpenRead(configpath)); @@ -28,9 +28,9 @@ public static XmlElement Log4Net { get { - var e = (XmlElement) Document.SelectSingleNode("/config/log4net"); + var e = (XmlElement)Document.SelectSingleNode("/config/log4net"); var nz = e?.SelectNodes("appender/file"); - if (nz == null) + if (nz == null) return e; foreach (XmlNode eChildNode in nz) @@ -54,7 +54,7 @@ public static string DefaultUserAgent var res = Document.SelectSingleNode("/config/DefaultUserAgent")?.InnerText; return res; } - catch(Exception) + catch (Exception) { return null; } @@ -71,7 +71,7 @@ public static string DefaultSecChUa var res = Document.SelectSingleNode("/config/DefaultSecChUa")?.InnerText; return res; } - catch(Exception) + catch (Exception) { return null; } @@ -113,16 +113,16 @@ public static BrowserAuthenticatorInfo BrowserAuthenticator string password = null; string dir = null; var node = Document.SelectSingleNode("/config/BrowserAuthenticator"); - foreach(XmlAttribute attr in node.Attributes) + foreach (XmlAttribute attr in node.Attributes) { - if(attr.LocalName.Equals("Url", StringComparison.OrdinalIgnoreCase)) + if (attr.LocalName.Equals("Url", StringComparison.OrdinalIgnoreCase)) url = attr.Value; - if(attr.LocalName.Equals("password", StringComparison.OrdinalIgnoreCase)) + if (attr.LocalName.Equals("password", StringComparison.OrdinalIgnoreCase)) password = attr.Value; - if(attr.LocalName.Equals("CacheDir", StringComparison.OrdinalIgnoreCase)) + if (attr.LocalName.Equals("CacheDir", StringComparison.OrdinalIgnoreCase)) dir = attr.Value; } - if(url!=null || dir!=null) + if (url != null || dir != null) { YaR.Clouds.WebDavStore.BrowserAuthenticator.Instance = new BrowserAuthenticatorInfo( url, @@ -131,7 +131,7 @@ public static BrowserAuthenticatorInfo BrowserAuthenticator ); } } - catch(Exception) + catch (Exception) { // ignored } @@ -180,7 +180,7 @@ public static Dictionary WebDAVProps { get { - if (null != _webDAVProps) + if (null != _webDAVProps) return _webDAVProps; try @@ -209,7 +209,7 @@ public static DeduplicateRulesBag DeduplicateRules { get { - if (null != _deduplicateRulesBag) + if (null != _deduplicateRulesBag) return _deduplicateRulesBag; try @@ -284,4 +284,4 @@ private static bool VerifyRegex(string testPattern) return isValid; } } -} \ No newline at end of file +} diff --git a/WebDavMailRuCloudStore/CustomHandlers/GetAndHeadHandler.cs b/WebDavMailRuCloudStore/CustomHandlers/GetAndHeadHandler.cs index 191bc427..3f97baec 100644 --- a/WebDavMailRuCloudStore/CustomHandlers/GetAndHeadHandler.cs +++ b/WebDavMailRuCloudStore/CustomHandlers/GetAndHeadHandler.cs @@ -148,14 +148,14 @@ public async Task HandleRequestAsync(IHttpContext httpContext, IStore stor } // HEAD method doesn't require the actual item data - if (head) + if (head) return true; try { await CopyToAsync(stream, response.Stream, 0, stream.Length - 1).ConfigureAwait(false); } - catch(System.Net.HttpListenerException) + catch (System.Net.HttpListenerException) { // Client is disconnected, we can not write to the stream any more response.Abort(); From 3a49221929d16368a9360737d3e34551dfb1949e Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Fri, 13 Oct 2023 18:56:37 +0300 Subject: [PATCH 04/77] All AssemblyVersion, FileVersion, Version tags in *.csproj set to $(ReleaseVersion) defined in Common.targets --- Common.targets | 4 +--- MailRuCloud/MailRuCloudApi/YaR.Clouds.csproj | 6 +++--- WDMRC.Console/WDMRC.Console.csproj | 6 +++--- WinServiceInstaller/WinServiceInstaller.csproj | 4 ++-- YandexAuthBrowser/YandexAuthBrowser.csproj | 7 +++---- 5 files changed, 12 insertions(+), 15 deletions(-) diff --git a/Common.targets b/Common.targets index 6b63cdb3..e60e1b20 100644 --- a/Common.targets +++ b/Common.targets @@ -1,9 +1,7 @@ - - net7.0-windows;net48;netcoreapp3.1;net5.0;net6.0;net7.0 - latest + 1.14.2.0 \ No newline at end of file diff --git a/MailRuCloud/MailRuCloudApi/YaR.Clouds.csproj b/MailRuCloud/MailRuCloudApi/YaR.Clouds.csproj index 1fdfd29d..0f17ffa1 100644 --- a/MailRuCloud/MailRuCloudApi/YaR.Clouds.csproj +++ b/MailRuCloud/MailRuCloudApi/YaR.Clouds.csproj @@ -8,9 +8,9 @@ false YaR.Clouds YaR.Clouds - 1.14.1.0 - 1.14.1.0 - $(AssemblyVersion) + $(ReleaseVersion) + $(ReleaseVersion) + $(ReleaseVersion) $(CommonLangVersion) False diff --git a/WDMRC.Console/WDMRC.Console.csproj b/WDMRC.Console/WDMRC.Console.csproj index a977473a..7be6201d 100644 --- a/WDMRC.Console/WDMRC.Console.csproj +++ b/WDMRC.Console/WDMRC.Console.csproj @@ -16,9 +16,9 @@ yar229@yandex.ru;ZZZConsulting@internet.ru WebDAV emulator for Cloud.mail.ru WebDAVCloudMailRu - 1.14.1.0 - 1.14.1.0 - $(AssemblyVersion) + $(ReleaseVersion) + $(ReleaseVersion) + $(ReleaseVersion) wdmrc YaR.Clouds.Console diff --git a/WinServiceInstaller/WinServiceInstaller.csproj b/WinServiceInstaller/WinServiceInstaller.csproj index 599adaa7..ae915e3a 100644 --- a/WinServiceInstaller/WinServiceInstaller.csproj +++ b/WinServiceInstaller/WinServiceInstaller.csproj @@ -8,8 +8,8 @@ $(CommonTargetFrameworks) false - 1.10.0.5 - 1.10.0.5 + $(ReleaseVersion) + $(ReleaseVersion) WinServiceInstaller WinServiceInstaller diff --git a/YandexAuthBrowser/YandexAuthBrowser.csproj b/YandexAuthBrowser/YandexAuthBrowser.csproj index 53acb9ba..aa424d5e 100644 --- a/YandexAuthBrowser/YandexAuthBrowser.csproj +++ b/YandexAuthBrowser/YandexAuthBrowser.csproj @@ -2,16 +2,15 @@ WinExe - latest $(CommonLangVersion) net7.0-windows enable true enable cloud.ico - 1.13.6.5 - 1.13.6.5 - $(AssemblyVersion) + $(ReleaseVersion) + $(ReleaseVersion) + $(ReleaseVersion) ZZZConsulting https://github.com/ZZZConsulting/WebDavMailRuCloud readme.md From 1d0e4c2a6805fa642e696ae5b2fb7b3bd969fe33 Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Sat, 14 Oct 2023 20:53:46 +0300 Subject: [PATCH 05/77] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D1=8B=20command=20line=20=D0=BF=D0=B0=D1=80=D0=B0?= =?UTF-8?q?=D0=BC=D0=B5=D1=82=D1=80=D1=8B=20=D1=82=D0=B0=D0=B9=D0=BC=D0=B0?= =?UTF-8?q?=D1=83=D1=82=D0=BE=D0=B2=20=D0=BF=D0=BE=D0=B4=D0=BA=D0=BB=D1=8E?= =?UTF-8?q?=D1=87=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BA=20=D0=BE=D0=B1=D0=BB?= =?UTF-8?q?=D0=B0=D0=BA=D1=83=20=D0=B8=20=D0=BF=D0=B5=D1=80=D0=B5=D0=B4?= =?UTF-8?q?=D0=B0=D1=87=D0=B8=20=D0=B4=D0=B0=D0=BD=D0=BD=D1=8B=D1=85.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs | 9 ++++++--- MailRuCloud/MailRuCloudApi/CloudSettings.cs | 4 ++++ WDMRC.Console/CommandLineOptions.cs | 9 +++++++++ WDMRC.Console/Payload.cs | 4 ++++ 4 files changed, 23 insertions(+), 3 deletions(-) diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs index eae7bd08..7aae12a9 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs @@ -41,13 +41,16 @@ protected virtual HttpWebRequest CreateRequest(string baseDomain = null) request.ContentType = ConstSettings.DefaultRequestType; request.Accept = "application/json"; request.UserAgent = Settings.UserAgent; + request.ContinueTimeout = Settings.CloudSettings.Wait100ContinueTimeoutMs; + request.Timeout = Settings.CloudSettings.WaitResponseTimeoutMs; + request.ReadWriteTimeout = Settings.CloudSettings.ReadWriteTimeoutMs; - #if NET48 +#if NET48 request.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip; - #else +#else request.AutomaticDecompression = DecompressionMethods.All; - #endif +#endif //request.AllowReadStreamBuffering = true; diff --git a/MailRuCloud/MailRuCloudApi/CloudSettings.cs b/MailRuCloud/MailRuCloudApi/CloudSettings.cs index 69d09e38..b463bc53 100644 --- a/MailRuCloud/MailRuCloudApi/CloudSettings.cs +++ b/MailRuCloud/MailRuCloudApi/CloudSettings.cs @@ -36,6 +36,10 @@ public int ListDepth public bool DisableLinkManager { get; set; } + public int Wait100ContinueTimeoutMs { get; set; } + public int WaitResponseTimeoutMs { get; set; } + public int ReadWriteTimeoutMs { get; set; } + #region BrowserAuthenticator public string BrowserAuthenticatorUrl { get; set; } diff --git a/WDMRC.Console/CommandLineOptions.cs b/WDMRC.Console/CommandLineOptions.cs index 2cf17f12..5b56e422 100644 --- a/WDMRC.Console/CommandLineOptions.cs +++ b/WDMRC.Console/CommandLineOptions.cs @@ -71,5 +71,14 @@ class CommandLineOptions [Option("disable-links", Required = false, Default = false, HelpText = "Disable support for shared folder and stop using /item.links.wdmrc")] public bool DisableLinkManager { get; set; } + + [Option("100-continue-timeout-ms", Required = false, Default = 350, HelpText = "Timeout in milliseconds, to wait until the 100-Continue is received")] + public int Wait100ContinueTimeoutMs { get; set; } + + [Option("response-timeout-ms", Required = false, Default = 100_000, HelpText = "Timeout in milliseconds, to wait until 1-st byte from server is received")] + public int WaitResponseTimeoutMs { get; set; } + + [Option("read-write-timeout-ms", Required = false, Default = 300_000, HelpText = "Timeout in milliseconds, the maximum duration of read or write operation")] + public int ReadWriteTimeoutMs { get; set; } } } diff --git a/WDMRC.Console/Payload.cs b/WDMRC.Console/Payload.cs index badeb41c..9ec55259 100644 --- a/WDMRC.Console/Payload.cs +++ b/WDMRC.Console/Payload.cs @@ -61,6 +61,10 @@ public static void Run(CommandLineOptions options) DisableLinkManager = options.DisableLinkManager, + Wait100ContinueTimeoutMs = options.Wait100ContinueTimeoutMs, + WaitResponseTimeoutMs = options.WaitResponseTimeoutMs, + ReadWriteTimeoutMs = options.ReadWriteTimeoutMs, + BrowserAuthenticatorUrl = Config.BrowserAuthenticator?.Url, BrowserAuthenticatorPassword = Config.BrowserAuthenticator?.Password, BrowserAuthenticatorCacheDir = Config.BrowserAuthenticator?.CacheDir, From 3b4dfe815c7671903e4a1b761b526df5ced328c4 Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Sat, 14 Oct 2023 20:55:44 +0300 Subject: [PATCH 06/77] Code cleanup --- .../MailRuCloudApi/Base/Requests/BaseRequest.cs | 10 +++++----- MailRuCloud/MailRuCloudApi/Base/WebDavPath.cs | 11 ++++++----- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs index 7aae12a9..9f68193e 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs @@ -68,13 +68,13 @@ public virtual async Task MakeRequestAsync() Stopwatch watch = new Stopwatch(); watch.Start(); - var httprequest = CreateRequest(); + var httpRequest = CreateRequest(); var content = CreateHttpContent(); if (content != null) { - httprequest.Method = "POST"; - var stream = httprequest.GetRequestStream(); + httpRequest.Method = "POST"; + var stream = httpRequest.GetRequestStream(); /* * For debug add to Watch: * System.Text.Encoding.UTF8.GetString(content) @@ -83,7 +83,7 @@ public virtual async Task MakeRequestAsync() } try { - using var response = (HttpWebResponse)await httprequest.GetResponseAsync(); + using var response = (HttpWebResponse)await httpRequest.GetResponseAsync(); if ((int) response.StatusCode >= 500) throw new RequestException("Server fault") @@ -128,7 +128,7 @@ public virtual async Task MakeRequestAsync() finally { watch.Stop(); - Logger.Debug($"HTTP:{httprequest.Method}:{httprequest.RequestUri.AbsoluteUri} ({watch.Elapsed.Milliseconds} ms)"); + Logger.Debug($"HTTP:{httpRequest.Method}:{httpRequest.RequestUri.AbsoluteUri} ({watch.Elapsed.Milliseconds} ms)"); } diff --git a/MailRuCloud/MailRuCloudApi/Base/WebDavPath.cs b/MailRuCloud/MailRuCloudApi/Base/WebDavPath.cs index 6876b69a..ccfca216 100644 --- a/MailRuCloud/MailRuCloudApi/Base/WebDavPath.cs +++ b/MailRuCloud/MailRuCloudApi/Base/WebDavPath.cs @@ -23,15 +23,16 @@ public static string Combine(string a, string b) } - public static string Clean(string path, bool doAddFinalseparator = false) + public static string Clean(string path, bool doAddFinalSeparator = false) { try { - string res = path.Replace("\\", "/"); - res = res.Replace("//", "/"); - if (res.Length > 1 && !doAddFinalseparator) + string res = path?.Replace("\\", "/").Replace("//", "/") + ?? throw new ArgumentNullException(nameof(path)); + if (res.Length > 1 && !doAddFinalSeparator) return res.TrimEnd('/'); - if (doAddFinalseparator && !res.EndsWith("/")) res += Separator; + if (doAddFinalSeparator && !res.EndsWith("/")) + res += Separator; return res; } catch (Exception e) From 83ba40cbe7e0fc7ceddf2a16be3ae4cf78443a55 Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Sat, 14 Oct 2023 20:57:41 +0300 Subject: [PATCH 07/77] =?UTF-8?q?=D0=9D=D0=B5=D0=B1=D0=BE=D0=BB=D1=8C?= =?UTF-8?q?=D1=88=D0=BE=D0=B5=20=D1=83=D1=81=D0=BA=D0=BE=D1=80=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=BA=D0=BE=D0=B4=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MailRuCloud/MailRuCloudApi/Base/File.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/MailRuCloud/MailRuCloudApi/Base/File.cs b/MailRuCloud/MailRuCloudApi/Base/File.cs index e55a95df..641eceda 100644 --- a/MailRuCloud/MailRuCloudApi/Base/File.cs +++ b/MailRuCloud/MailRuCloudApi/Base/File.cs @@ -156,12 +156,14 @@ public void SetName(string destinationName) FullPath = WebDavPath.Combine(Path, destinationName); if (ServiceInfo != null) ServiceInfo.CleanName = Name; - if (Files.Count <= 1) + if (Files.Count <= 1) return; string path = Path; foreach (var fiFile in Parts) - fiFile.FullPath = WebDavPath.Combine(path, destinationName + fiFile.ServiceInfo.ToString(false)); //TODO: refact + fiFile.FullPath = WebDavPath.Combine( + path, + string.Concat(destinationName, fiFile.ServiceInfo.ToString(false))); //TODO: refact } //TODO : refact, bad design From aee494d6183ff31135d281381a28b7a974bb38fc Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Sat, 14 Oct 2023 20:59:38 +0300 Subject: [PATCH 08/77] =?UTF-8?q?81920=20->=2016384=20=D1=81=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D1=80=D0=B0=D0=B7=D0=BC=D0=B5=D1=80=D0=B0=20?= =?UTF-8?q?=D0=B1=D1=83=D1=84=D0=B5=D1=80=D0=B0=20=D0=BF=D0=BE=D0=B4=20?= =?UTF-8?q?=D1=80=D0=B0=D0=B7=D0=BC=D0=B5=D1=80=20=D0=BE=D0=BA=D0=BD=D0=B0?= =?UTF-8?q?=20TCP=20(=D0=BF=D0=BE=D0=BF=D1=8B=D1=82=D0=BA=D0=B0=20=D1=83?= =?UTF-8?q?=D1=81=D0=BA=D0=BE=D1=80=D0=B8=D1=82=D1=8C=20=D0=BA=D0=BE=D0=B4?= =?UTF-8?q?).=20=D0=98=20=D1=83=D1=81=D1=82=D1=80=D0=B0=D0=BD=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BE=D0=BA,=20=D0=BA?= =?UTF-8?q?=D0=BE=D0=B3=D0=B4=D0=B0=20=D0=BA=D0=BB=D0=B8=D0=B5=D0=BD=D1=82?= =?UTF-8?q?=20=D0=BE=D1=82=D0=BA=D0=BB=D1=8E=D1=87=D0=B8=D0=BB=D1=81=D1=8F?= =?UTF-8?q?=20=D0=B4=D0=BE=D1=81=D1=80=D0=BE=D1=87=D0=BD=D0=BE.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Base/Streams/DownloadStream.cs | 5 ++++- .../HttpBaseContext.cs | 21 +++++++++++++------ 2 files changed, 19 insertions(+), 7 deletions(-) diff --git a/MailRuCloud/MailRuCloudApi/Base/Streams/DownloadStream.cs b/MailRuCloud/MailRuCloudApi/Base/Streams/DownloadStream.cs index d9e5fcb9..fe20f427 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Streams/DownloadStream.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Streams/DownloadStream.cs @@ -96,7 +96,10 @@ private async Task Download(long start, long end, File file, CancellationToken c { using var httpweb = _responseGenerator(start, end, file); using var responseStream = httpweb.Value.GetResponseStream(); - await responseStream.CopyToAsync(_innerStream, 81920, cancellationToken: cancellationToken).ConfigureAwait(false); + await responseStream.CopyToAsync(_innerStream, + /* 81920 is default buffer size in .NET */ + 16384 /* lets try to align to TCP window */, + cancellationToken: cancellationToken).ConfigureAwait(false); } protected override void Dispose(bool disposing) diff --git a/NWebDav/NWebDav.Server.HttpListener/HttpBaseContext.cs b/NWebDav/NWebDav.Server.HttpListener/HttpBaseContext.cs index e6fd6dd8..98a42238 100644 --- a/NWebDav/NWebDav.Server.HttpListener/HttpBaseContext.cs +++ b/NWebDav/NWebDav.Server.HttpListener/HttpBaseContext.cs @@ -25,14 +25,23 @@ protected HttpBaseContext(HttpListenerRequest request, HttpListenerResponse resp public Task CloseAsync() { - try + if (_response != null) { - // Prevent error of closing stream before all bytes are rent - _response?.OutputStream?.Flush(); - } catch { } + // Prevent any exceptions + try + { + // At first send remaining buffered byte to client + _response.OutputStream?.Flush(); + } + catch { } - // Close the response - _response?.Close(); + try + { + // Then close the response + _response.Close(); + } + catch { } + } // Command completed synchronous return Task.FromResult(true); From a82422b108c1040241ea60cbcd0664d3a6d5045f Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Sat, 14 Oct 2023 21:22:19 +0300 Subject: [PATCH 09/77] =?UTF-8?q?=D0=9E=D0=BF=D0=B5=D1=87=D0=B0=D1=82?= =?UTF-8?q?=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MailRuCloud/MailRuCloudApi/Base/File.cs | 2 +- MailRuCloud/MailRuCloudApi/Base/Folder.cs | 4 ++-- .../MailRuCloudApi/Base/Repos/DtoImportExtensions.cs | 2 +- .../MailRuCloudApi/Base/Repos/MailRuCloud/DtoImport.cs | 2 +- .../Repos/MailRuCloud/Mobile/Requests/ListRequest.cs | 2 +- .../MailRuCloud/Mobile/Requests/Types/FsFolder.cs | 2 +- .../Base/Repos/YandexDisk/YadWeb/DtoImportYadWeb.cs | 4 ++-- .../Base/Repos/YandexDisk/YadWebV2/DtoImportYadWeb.cs | 4 ++-- MailRuCloud/MailRuCloudApi/Base/SplittedFile.cs | 2 +- MailRuCloud/MailRuCloudApi/Cloud.cs | 10 +++++----- MailRuCloud/MailRuCloudApi/Links/LinkManager.cs | 4 ++-- .../SpecialCommands/Commands/ListCommand.cs | 2 +- NWebDav/NWebDav.Server/Handlers/CopyHandler.cs | 2 +- 13 files changed, 21 insertions(+), 21 deletions(-) diff --git a/MailRuCloud/MailRuCloudApi/Base/File.cs b/MailRuCloud/MailRuCloudApi/Base/File.cs index 641eceda..a1a4121b 100644 --- a/MailRuCloud/MailRuCloudApi/Base/File.cs +++ b/MailRuCloud/MailRuCloudApi/Base/File.cs @@ -135,7 +135,7 @@ public IEnumerable GetPublicLinks(Cloud cloud) } /// - /// List of phisical files contains data + /// List of physical files contains data /// public virtual List Parts => new() {this}; public virtual IList Files => new List { this }; diff --git a/MailRuCloud/MailRuCloudApi/Base/Folder.cs b/MailRuCloud/MailRuCloudApi/Base/Folder.cs index 89914a16..0dfc0846 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Folder.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Folder.cs @@ -111,7 +111,7 @@ public IEnumerable GetPublicLinks(Cloud cloud) public bool IsFile => false; - public bool IsChildsLoaded { get; internal set; } + public bool IsChildrenLoaded { get; internal set; } public int? ServerFoldersCount { get; set; } @@ -126,7 +126,7 @@ public PublishInfo ToPublishInfo() } - //public List> GetLinearChilds() + //public List> GetLinearChildren() //{ //} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/DtoImportExtensions.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/DtoImportExtensions.cs index 2e0a5db4..5d731d45 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/DtoImportExtensions.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/DtoImportExtensions.cs @@ -63,7 +63,7 @@ internal static Folder ToFolder(this ListRequest.Result data) internal static Folder ToFolder(this FsFolder data) { - var res = new Folder((long)data.Size, data.FullPath) { IsChildsLoaded = data.IsChildsLoaded }; + var res = new Folder((long)data.Size, data.FullPath) { IsChildrenLoaded = data.IsChildrenLoaded }; res.Files.AddRange(data.Items .OfType() diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/DtoImport.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/DtoImport.cs index 5b83fcc1..b017e34c 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/DtoImport.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/DtoImport.cs @@ -236,7 +236,7 @@ public static Folder ToFolder(this FolderInfoResult data, string publicBaseUrl, .Select(item => item.ToFile(publicBaseUrl, "")) .ToGroupedFiles() .ToList(), - IsChildsLoaded = true + IsChildrenLoaded = true }; return folder; diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/ListRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/ListRequest.cs index 4c4b79b1..2f5c9d2f 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/ListRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/ListRequest.cs @@ -164,7 +164,7 @@ private FsItem Deserialize(ResponseBodyStream data, string fullPath) if (item is FsFolder fsFolder) { lastFolder = fsFolder; - fsFolder.IsChildsLoaded = lvl < Depth; + fsFolder.IsChildrenLoaded = lvl < Depth; } parseOp = (ParseOp)data.ReadShort(); diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/Types/FsFolder.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/Types/FsFolder.cs index b89c9b29..7459fb31 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/Types/FsFolder.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/Types/FsFolder.cs @@ -19,6 +19,6 @@ public FsFolder(string fullPath, TreeId treeId, CloudFolderType cloudFolderType, Size = size; } - public bool IsChildsLoaded { get; set; } + public bool IsChildrenLoaded { get; set; } } } \ No newline at end of file diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/DtoImportYadWeb.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/DtoImportYadWeb.cs index ba1776da..bec5a17d 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/DtoImportYadWeb.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/DtoImportYadWeb.cs @@ -33,7 +33,7 @@ public static IEntry ToFolder(this YadFolderInfoRequestData data, YadItemInfoReq { var fi = data.Resources; - var res = new Folder(resStats?.Size ?? itemInfo?.Meta?.Size ?? 0, path) { IsChildsLoaded = true }; + var res = new Folder(resStats?.Size ?? itemInfo?.Meta?.Size ?? 0, path) { IsChildrenLoaded = true }; if (!string.IsNullOrEmpty(itemInfo?.Meta?.UrlShort)) res.PublicLinks.Add(new PublicLinkInfo("short", itemInfo.Meta.UrlShort)); @@ -89,7 +89,7 @@ public static Folder ToFolder(this FolderInfoDataResource resource) { var path = resource.Path.Remove(0, "/disk".Length); - var res = new Folder(path) { IsChildsLoaded = false }; + var res = new Folder(path) { IsChildrenLoaded = false }; return res; } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/DtoImportYadWeb.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/DtoImportYadWeb.cs index 429dcf01..8df7aa3d 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/DtoImportYadWeb.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/DtoImportYadWeb.cs @@ -37,7 +37,7 @@ public static IEntry ToFolder(this YadFolderInfoRequestData data, { var fi = data.Resources; - var res = new Folder(resStats?.Size ?? itemInfo?.Meta?.Size ?? 0, path) { IsChildsLoaded = true }; + var res = new Folder(resStats?.Size ?? itemInfo?.Meta?.Size ?? 0, path) { IsChildrenLoaded = true }; if (!string.IsNullOrEmpty(itemInfo?.Meta?.UrlShort)) res.PublicLinks.Add(new PublicLinkInfo("short", itemInfo.Meta.UrlShort)); @@ -93,7 +93,7 @@ public static Folder ToFolder(this FolderInfoDataResource resource) { var path = resource.Path.Remove(0, "/disk".Length); - var res = new Folder(path) { IsChildsLoaded = false }; + var res = new Folder(path) { IsChildrenLoaded = false }; return res; } diff --git a/MailRuCloud/MailRuCloudApi/Base/SplittedFile.cs b/MailRuCloud/MailRuCloudApi/Base/SplittedFile.cs index 602f9024..ec46f1e6 100644 --- a/MailRuCloud/MailRuCloudApi/Base/SplittedFile.cs +++ b/MailRuCloud/MailRuCloudApi/Base/SplittedFile.cs @@ -45,7 +45,7 @@ public SplittedFile(IList files) private readonly File _fileHeader; /// - /// List of phisical files contains data + /// List of physical files contains data /// public override List Parts { get; } diff --git a/MailRuCloud/MailRuCloudApi/Cloud.cs b/MailRuCloud/MailRuCloudApi/Cloud.cs index 8a7c9dc2..ea048841 100644 --- a/MailRuCloud/MailRuCloudApi/Cloud.cs +++ b/MailRuCloud/MailRuCloudApi/Cloud.cs @@ -151,7 +151,7 @@ public virtual async Task GetItemAsync(string path, ItemType itemType = private void FillWithULinks(Folder folder) { - if (!folder.IsChildsLoaded) return; + if (!folder.IsChildrenLoaded) return; if (LinkManager != null) { @@ -190,7 +190,7 @@ private void CacheAddEntry(IEntry entry) case File cfile: _itemCache.Add(cfile.FullPath, cfile); break; - case Folder { IsChildsLoaded: true } cfolder: + case Folder { IsChildrenLoaded: true } cfolder: { _itemCache.Add(cfolder.FullPath, cfolder); _itemCache.Add(cfolder.Files.Select(f => new KeyValuePair(f.FullPath, f))); @@ -382,7 +382,7 @@ public async Task Copy(Folder folder, string destinationPath) //clone all inner links if (LinkManager != null) { - var links = LinkManager.GetChilds(folder.FullPath); + var links = LinkManager.GetChildren(folder.FullPath); if (links != null) { foreach (var linka in links) @@ -644,7 +644,7 @@ public async Task MoveAsync(Folder folder, string destinationPath) //clone all inner links if (LinkManager != null) { - var links = LinkManager.GetChilds(folder.FullPath).ToList(); + var links = LinkManager.GetChildren(folder.FullPath).ToList(); foreach (var linka in links) { // некоторые клиенты сначала делают структуру каталогов, а потом по одному переносят файлы @@ -814,7 +814,7 @@ private async Task Remove(string fullPath) //remove inner links if (LinkManager != null) { - var innerLinks = LinkManager.GetChilds(fullPath); + var innerLinks = LinkManager.GetChildren(fullPath); LinkManager.RemoveLinks(innerLinks); } diff --git a/MailRuCloud/MailRuCloudApi/Links/LinkManager.cs b/MailRuCloud/MailRuCloudApi/Links/LinkManager.cs index d053d24b..bdb83e11 100644 --- a/MailRuCloud/MailRuCloudApi/Links/LinkManager.cs +++ b/MailRuCloud/MailRuCloudApi/Links/LinkManager.cs @@ -289,7 +289,7 @@ private async Task ResolveLink(Link link) } } - //public IEnumerable GetChilds(string folderFullPath, bool doResolveType) + //public IEnumerable GetChildren(string folderFullPath, bool doResolveType) //{ // var lst = _itemList.Items // .Where(it => @@ -298,7 +298,7 @@ private async Task ResolveLink(Link link) // return lst; //} - public IEnumerable GetChilds(string folderFullPath) + public IEnumerable GetChildren(string folderFullPath) { var lst = _itemList.Items .Where(it => WebDavPath.IsParentOrSame(folderFullPath, it.MapTo)) diff --git a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/ListCommand.cs b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/ListCommand.cs index e39c64bf..a94be2af 100644 --- a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/ListCommand.cs +++ b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/ListCommand.cs @@ -57,7 +57,7 @@ private IEnumerable Flat(IEntry entry, LinkManager lm) .Select(it => it switch { File => it, - Folder ifolder => ifolder.IsChildsLoaded + Folder ifolder => ifolder.IsChildrenLoaded ? ifolder : Cloud.Account.RequestRepo.FolderInfo(RemotePath.Get(it.FullPath, lm).Result, depth: 3).Result, _ => throw new NotImplementedException("Unknown item type") diff --git a/NWebDav/NWebDav.Server/Handlers/CopyHandler.cs b/NWebDav/NWebDav.Server/Handlers/CopyHandler.cs index 719191b7..5dc8cdcb 100644 --- a/NWebDav/NWebDav.Server/Handlers/CopyHandler.cs +++ b/NWebDav/NWebDav.Server/Handlers/CopyHandler.cs @@ -125,7 +125,7 @@ private static async Task CopyAsync(IStoreItem source, IStoreCollection destinat // The result should also contain a collection var newCollection = (IStoreCollection)copyResult.Item; - // Copy all childs of the source collection + // Copy all children of the source collection foreach (var entry in await sourceCollection.GetItemsAsync(httpContext)) await CopyAsync(entry, newCollection, entry.Name, overwrite, depth - 1, httpContext, newBaseUri, errors).ConfigureAwait(false); } From 26e5a7566bc1fdaf2f72f04fc932f38dfaf1ed81 Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Sat, 14 Oct 2023 21:25:50 +0300 Subject: [PATCH 10/77] Yandex API version --- .../Repos/YandexDisk/YadWebV2/Requests/YaDCommonRequest.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YaDCommonRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YaDCommonRequest.cs index 01137d30..07a894e5 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YaDCommonRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YaDCommonRequest.cs @@ -72,10 +72,13 @@ protected override RequestResponse DeserializeMessage( { Logger.Debug(text); } - if (msg.Result.Version != "315.2.0") + if (_postData.Models != null && + _postData.Models.Count > 0 && + _postData.Models[0].Name == "space") { - Logger.Warn($"Yandex has new API version {msg.Result.Version}"); + Logger.Warn($"Yandex has API version {msg.Result.Version}"); } + return msg; } } From fa6515027a6f2c52d4ad7c5e187cfd8d13089827 Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Sat, 14 Oct 2023 21:26:49 +0300 Subject: [PATCH 11/77] =?UTF-8?q?=D0=9E=D0=BF=D0=B5=D1=87=D0=B0=D1=82?= =?UTF-8?q?=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- NWebDav/NWebDav.Server/WebDavUri.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NWebDav/NWebDav.Server/WebDavUri.cs b/NWebDav/NWebDav.Server/WebDavUri.cs index 48f15c03..7c424166 100644 --- a/NWebDav/NWebDav.Server/WebDavUri.cs +++ b/NWebDav/NWebDav.Server/WebDavUri.cs @@ -47,7 +47,7 @@ public string Scheme } //_fakeurl.Scheme; /// - /// Decoded path (standart decode, may fail) + /// Decoded path (standard decode, may fail) /// public string LocalPath => Path; //_fakeurl.LocalPath; From 285a84f3e858a6e7a1bcd8fa42052feccc850e4c Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Sat, 14 Oct 2023 21:35:22 +0300 Subject: [PATCH 12/77] =?UTF-8?q?=D0=A3=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD=20?= =?UTF-8?q?=D0=BB=D0=B8=D1=88=D0=BD=D0=B8=D0=B9=20=D0=BE=D1=82=D0=BB=D0=B0?= =?UTF-8?q?=D0=B4=D0=BE=D1=87=D0=BD=D1=8B=D0=B9=20=D0=BA=D0=BE=D0=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MailRuCloud/MailRuCloudApi/Cloud.cs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/MailRuCloud/MailRuCloudApi/Cloud.cs b/MailRuCloud/MailRuCloudApi/Cloud.cs index ea048841..19526462 100644 --- a/MailRuCloud/MailRuCloudApi/Cloud.cs +++ b/MailRuCloud/MailRuCloudApi/Cloud.cs @@ -216,13 +216,7 @@ public virtual IEntry GetItem(string path, ItemType itemType = ItemType.Unknown, public IEnumerable IsFileExists(string filename, IList folderPaths) { if (folderPaths == null) - { -#if DEBUG - // This case should not be happened, let's find out why we are here. - System.Diagnostics.Debugger.Break(); -#endif return Enumerable.Empty(); - } var folder = folderPaths .AsParallel() @@ -230,13 +224,7 @@ public IEnumerable IsFileExists(string filename, IList folderPaths .Select(async path => (Folder)await GetItemAsync(path, ItemType.Folder, false)); if (folder == null) - { -#if DEBUG - // This case should not be happened, let's find out why we are here. - System.Diagnostics.Debugger.Break(); -#endif return Enumerable.Empty(); - } var files = folder .SelectMany(fld => (fld.Result?.Files ?? new List()).Where(file => WebDavPath.PathEquals(file.Name, filename))); From a72beab33eefbdc8cc830aa0db527d020c3c1177 Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Sat, 14 Oct 2023 22:48:09 +0300 Subject: [PATCH 13/77] =?UTF-8?q?=D0=9F=D0=BE=D0=BB=D0=B8=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=D0=BA=D0=B0=20=D0=BA=D0=BE=D0=B4=D0=B0=20request-respons?= =?UTF-8?q?e=20=D0=BE=D0=B1=D1=80=D0=B0=D1=89=D0=B5=D0=BD=D0=B8=D1=8F=20?= =?UTF-8?q?=D0=BA=20=D1=81=D0=B5=D1=80=D0=B2=D0=B5=D1=80=D1=83?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Base/Requests/BaseRequest.cs | 39 ++++++++++--------- 1 file changed, 21 insertions(+), 18 deletions(-) diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs index 9f68193e..914555a7 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs @@ -28,8 +28,8 @@ protected virtual HttpWebRequest CreateRequest(string baseDomain = null) { string domain = string.IsNullOrEmpty(baseDomain) ? ConstSettings.CloudDomain : baseDomain; var uriz = new Uri(new Uri(domain), RelationalUri); - - // supressing escaping is obsolete and breaks, for example, chinese names + + // suppressing escaping is obsolete and breaks, for example, Chinese names // url generated for %E2%80%8E and %E2%80%8F seems ok, but mail.ru replies error // https://stackoverflow.com/questions/20211496/uri-ignore-special-characters //var udriz = new Uri(new Uri(domain), RelationalUri, true); @@ -53,7 +53,7 @@ protected virtual HttpWebRequest CreateRequest(string baseDomain = null) #endif //request.AllowReadStreamBuffering = true; - + return request; } @@ -74,38 +74,43 @@ public virtual async Task MakeRequestAsync() if (content != null) { httpRequest.Method = "POST"; - var stream = httpRequest.GetRequestStream(); + httpRequest.AllowWriteStreamBuffering = false; + using Stream requestStream = await httpRequest.GetRequestStreamAsync().ConfigureAwait(false); /* - * For debug add to Watch: + * The debug add the following to a watch list: * System.Text.Encoding.UTF8.GetString(content) */ - await stream.WriteAsync(content, 0, content.Length); +#if NET48 + await requestStream.WriteAsync(content, 0, content.Length).ConfigureAwait(false); +#else + await requestStream.WriteAsync(content).ConfigureAwait(false); +#endif + await requestStream.FlushAsync().ConfigureAwait(false); + requestStream.Close(); } try { - using var response = (HttpWebResponse)await httpRequest.GetResponseAsync(); + using var response = (HttpWebResponse)await httpRequest.GetResponseAsync().ConfigureAwait(false); - if ((int) response.StatusCode >= 500) + if ((int)response.StatusCode >= 500) + { throw new RequestException("Server fault") { StatusCode = response.StatusCode }; + } RequestResponse result; -#if NET48 using (var responseStream = response.GetResponseStream()) -#else - await using (var responseStream = response.GetResponseStream()) -#endif - { result = DeserializeMessage(response.Headers, Transport(responseStream)); + responseStream.Close(); } if (!result.Ok || response.StatusCode != HttpStatusCode.OK) { var exceptionMessage = - $"Request failed (status code {(int) response.StatusCode}): {result.Description}"; + $"Request failed (status code {(int)response.StatusCode}): {result.Description}"; throw new RequestException(exceptionMessage) { StatusCode = response.StatusCode, @@ -119,9 +124,9 @@ public virtual async Task MakeRequestAsync() return retVal; } // ReSharper disable once RedundantCatchClause - #pragma warning disable 168 +#pragma warning disable 168 catch (Exception ex) - #pragma warning restore 168 +#pragma warning restore 168 { throw; } @@ -130,8 +135,6 @@ public virtual async Task MakeRequestAsync() watch.Stop(); Logger.Debug($"HTTP:{httpRequest.Method}:{httpRequest.RequestUri.AbsoluteUri} ({watch.Elapsed.Milliseconds} ms)"); } - - } protected abstract TConvert Transport(Stream stream); From 9cd8d4e9886005fb28f11a0f3c3970bb6055d56e Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Sat, 14 Oct 2023 23:10:35 +0300 Subject: [PATCH 14/77] =?UTF-8?q?=D0=9D=D0=B5=D0=B7=D0=BD=D0=B0=D1=87?= =?UTF-8?q?=D0=B8=D1=82=D0=B5=D0=BB=D1=8C=D0=BD=D1=8B=D0=B5=20=D0=BF=D1=80?= =?UTF-8?q?=D0=B0=D0=B2=D0=BA=D0=B8=20=D0=B2=20=D0=B0=D0=BB=D0=B3=D0=BE?= =?UTF-8?q?=D1=80=D0=B8=D1=82=D0=BC=D0=B5=20=D0=BE=D1=87=D0=B8=D1=81=D1=82?= =?UTF-8?q?=D0=BA=D0=B8=20=D1=83=D1=81=D1=82=D0=B0=D1=80=D0=B5=D0=B2=D1=88?= =?UTF-8?q?=D0=B8=D1=85=20=D0=B7=D0=B0=D0=BF=D0=B8=D1=81=D0=B5=D0=B9=20?= =?UTF-8?q?=D0=BA=D0=B5=D1=88=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MailRuCloudApi/Common/ItemCache.cs | 48 +++++++++++-------- 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/MailRuCloud/MailRuCloudApi/Common/ItemCache.cs b/MailRuCloud/MailRuCloudApi/Common/ItemCache.cs index 1e4dc088..a8dfaa59 100644 --- a/MailRuCloud/MailRuCloudApi/Common/ItemCache.cs +++ b/MailRuCloud/MailRuCloudApi/Common/ItemCache.cs @@ -10,6 +10,20 @@ public class ItemCache { private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(ItemCache)); + private static readonly TimeSpan _minCleanUpInterval = new TimeSpan(0, 0, 20 /* секунды */ ); + private static readonly TimeSpan _maxCleanUpInterval = new TimeSpan(0, 10 /* минуты */, 0); + + // По умолчанию очистка кеша от устаревших записей производится каждые 5 минут + private TimeSpan _cleanUpPeriod = TimeSpan.FromMinutes(5); + + private readonly TimeSpan _expirePeriod; + + private readonly bool _noCache; + + private readonly Timer _cleanTimer; + private readonly ConcurrentDictionary> _items = new(); + //private readonly object _locker = new object(); + public ItemCache(TimeSpan expirePeriod) { _expirePeriod = expirePeriod; @@ -18,22 +32,21 @@ public ItemCache(TimeSpan expirePeriod) long cleanPeriod = (long)CleanUpPeriod.TotalMilliseconds; // if there is no cache, we don't need clean up timer - _cleanTimer = _noCache? null : new Timer(_ => RemoveExpired(), null, cleanPeriod, cleanPeriod); + _cleanTimer = _noCache ? null : new Timer(_ => RemoveExpired(), null, cleanPeriod, cleanPeriod); } - private readonly bool _noCache; - - private readonly Timer _cleanTimer; - private readonly ConcurrentDictionary> _items = new(); - //private readonly object _locker = new object(); - public TimeSpan CleanUpPeriod { get => _cleanUpPeriod; set { - // Clean up period should not be shorter then cache expiration period - _cleanUpPeriod = value < _expirePeriod ? value : _expirePeriod; + // Очистку кеша от устаревших записей не следует проводить часто чтобы не нагружать машину, + // и не следует проводить редко, редко, чтобы не натыкаться постоянно на устаревшие записи. + _cleanUpPeriod = value < _minCleanUpInterval + ? _minCleanUpInterval + : value > _maxCleanUpInterval + ? _maxCleanUpInterval + : value; if (!_noCache) { @@ -47,10 +60,11 @@ public int RemoveExpired() { if (!_items.Any()) return 0; + DateTime threshold = DateTime.Now - _expirePeriod; int removedCount = 0; foreach (var item in _items) - if (DateTime.Now - item.Value.Created > TimeSpan.FromMinutes(5)) - if (_items.TryRemove(item.Key, out _)) + if (item.Value.Created <= threshold) + if (_items.TryRemove(item.Key, out _)) removedCount++; if (removedCount > 0) @@ -64,7 +78,7 @@ public TValue Get(TKey key) if (_noCache) return default; - if (!_items.TryGetValue(key, out var item)) + if (!_items.TryGetValue(key, out var item)) return default; if (IsExpired(item)) @@ -128,23 +142,19 @@ public void Forget(TKey whoKey, TKey whomKey) private bool IsExpired(TimedItem item) { - return DateTime.Now - item.Created > _expirePeriod; + DateTime threshold = DateTime.Now - _expirePeriod; + return item.Created <= threshold; } - private readonly TimeSpan _expirePeriod; - private TimeSpan _cleanUpPeriod = TimeSpan.FromMinutes(5); - private class TimedItem { public DateTime Created { get; set; } public T Item { get; set; } } - - } public interface ICanForget { void Forget(object whomKey); } -} \ No newline at end of file +} From 96c9afa7d4bf26d54eb4eb483d803ce5cb20cf9a Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Sat, 14 Oct 2023 23:14:16 +0300 Subject: [PATCH 15/77] =?UTF-8?q?=D0=92=20=D0=BD=D0=B5=D0=BA=D0=BE=D1=82?= =?UTF-8?q?=D0=BE=D1=80=D1=8B=D1=85=20=D1=81=D0=BB=D1=83=D1=87=D0=B0=D1=8F?= =?UTF-8?q?=D1=85=20=D0=BF=D1=80=D0=B8=20=D0=BE=D1=82=D0=BB=D0=B0=D0=B4?= =?UTF-8?q?=D0=BA=D0=B5=20=D0=BF=D0=BE=D0=BB=D0=B5=D0=B7=D0=BD=D0=BE=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=BB=D1=83=D1=87=D0=B0=D1=82=D1=8C=20=D0=B8=D0=BD?= =?UTF-8?q?=D1=84=D0=BE=D1=80=D0=BC=D0=B0=D1=86=D0=B8=D1=8E=20=D0=BE=20?= =?UTF-8?q?=D1=82=D0=BE=D0=BC,=20=D1=87=D1=82=D0=BE=20=D0=B1=D1=8B=D0=BB?= =?UTF-8?q?=D0=B0=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- NWebDav/NWebDav.Server/WebDavDispatcher.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NWebDav/NWebDav.Server/WebDavDispatcher.cs b/NWebDav/NWebDav.Server/WebDavDispatcher.cs index a9c6099c..7d7d5196 100644 --- a/NWebDav/NWebDav.Server/WebDavDispatcher.cs +++ b/NWebDav/NWebDav.Server/WebDavDispatcher.cs @@ -187,7 +187,7 @@ aex.InnerExceptions[2] is System.Net.WebException ) { // If client didn't wait for operation completion, just do nothing - //s_log.Log(LogLevel.Error, $"Client disconnected. Error while handling request (method={request.HttpMethod}, url={request.Url} {httpContext.Response.StatusDescription}"); + s_log.Log(LogLevel.Debug, $"Client disconnected. Error while handling request (method={request.HttpMethod}, url={request.Url} {httpContext.Response.StatusDescription}"); } catch (Exception exc) From 30c4c4b6e9ec254cccf01f2e6b7d8a67fa37a3b6 Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Sat, 14 Oct 2023 23:28:40 +0300 Subject: [PATCH 16/77] =?UTF-8?q?=D0=9C=D0=B5=D0=BB=D0=BA=D0=B8=D0=B5,=20?= =?UTF-8?q?=D0=BD=D0=B5=D0=B7=D0=BD=D0=B0=D1=87=D0=B8=D1=82=D0=B5=D0=BB?= =?UTF-8?q?=D1=8C=D0=BD=D1=8B=D0=B5=20=D0=BF=D1=80=D0=B0=D0=B2=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MailRuCloud/MailRuCloudApi/Cloud.cs | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/MailRuCloud/MailRuCloudApi/Cloud.cs b/MailRuCloud/MailRuCloudApi/Cloud.cs index 19526462..f9ecc9d8 100644 --- a/MailRuCloud/MailRuCloudApi/Cloud.cs +++ b/MailRuCloud/MailRuCloudApi/Cloud.cs @@ -59,11 +59,15 @@ public Cloud(CloudSettings settings, Credentials credentials) Account = new Account(settings, credentials); if (!Account.Login()) { - throw new AuthenticationException("Auth token has't been retrieved."); + throw new AuthenticationException("Auth token hasn't been retrieved."); } //TODO: wow very dummy linking, refact cache realization globally! - _itemCache = new ItemCache(TimeSpan.FromSeconds(settings.CacheListingSec)) { CleanUpPeriod = TimeSpan.FromMinutes(5) }; + _itemCache = new ItemCache(TimeSpan.FromSeconds(settings.CacheListingSec)); + //{ + // Полагаемся на стандартно заданное время очистки + // CleanUpPeriod = TimeSpan.FromMinutes(5) + //}; LinkManager = settings.DisableLinkManager ? null : new LinkManager(this); } @@ -202,16 +206,10 @@ private void CacheAddEntry(IEntry entry) } } - private IEntry CacheGetEntry(string path) - { - var cached = _itemCache.Get(path); - return cached; - } + private IEntry CacheGetEntry(string path) => _itemCache.Get(path); public virtual IEntry GetItem(string path, ItemType itemType = ItemType.Unknown, bool resolveLinks = true) - { - return GetItemAsync(path, itemType, resolveLinks).Result; - } + => GetItemAsync(path, itemType, resolveLinks).Result; public IEnumerable IsFileExists(string filename, IList folderPaths) { @@ -243,7 +241,7 @@ private async Task Unpublish(Uri publicLink, string fullPath) return res.IsSuccess; } - public async Task Unpublish(File file) + public async Task Unpublish(File file) { foreach (var innerFile in file.Files) { @@ -958,9 +956,12 @@ public async Task DownloadFileAsString(string path) } catch (Exception e) when ( // let's check if there really no file or just other network error - (e is AggregateException && e.InnerException is WebException we && (we.Response as HttpWebResponse)?.StatusCode == HttpStatusCode.NotFound) + (e is AggregateException && + e.InnerException is WebException we && + we.Response is HttpWebResponse { StatusCode: HttpStatusCode.NotFound }) || - (e is WebException wee && (wee.Response as HttpWebResponse)?.StatusCode == HttpStatusCode.NotFound) + (e is WebException wee && + wee.Response is HttpWebResponse { StatusCode: HttpStatusCode.NotFound }) ) { return null; From 3e532b885d2a1db1c89afd30e834e1ced212cb11 Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Sun, 15 Oct 2023 15:57:49 +0300 Subject: [PATCH 17/77] =?UTF-8?q?=D0=9F=D0=BE=D0=BF=D1=8B=D1=82=D0=BA?= =?UTF-8?q?=D0=B0=20=D1=81=D0=B4=D0=B5=D0=BB=D0=B0=D1=82=D1=8C=20File,=20F?= =?UTF-8?q?older=20=D0=B8=20PublicLinks=20thread=20safe.=20=D0=9D=D0=B0?= =?UTF-8?q?=D1=81=D0=BA=D0=BE=D0=BB=D1=8C=D0=BA=D0=BE=20=D0=BF=D0=BE=D0=BF?= =?UTF-8?q?=D1=8B=D1=82=D0=BA=D0=B0=20=D0=B1=D1=8B=D0=BB=D0=B0=20=D1=83?= =?UTF-8?q?=D0=B4=D0=B0=D1=87=D0=BD=D0=BE=D0=B9,=20=D0=B1=D1=83=D0=B4?= =?UTF-8?q?=D0=B5=D1=82=20=D1=8F=D1=81=D0=BD=D0=BE=20=D0=BF=D0=BE=D0=B7?= =?UTF-8?q?=D0=B6=D0=B5.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MailRuCloud/MailRuCloudApi/Base/File.cs | 28 ++++--- MailRuCloud/MailRuCloudApi/Base/Folder.cs | 76 +++++++++---------- MailRuCloud/MailRuCloudApi/Base/IEntry.cs | 6 +- .../Base/Repos/DtoImportExtensions.cs | 34 +++++---- .../Base/Repos/MailRuCloud/DtoImport.cs | 76 +++++++++++-------- .../MailRuCloud/Mobile/MobileRequestRepo.cs | 4 +- .../WebBin/Requests/DownloadRequest.cs | 16 ++-- .../MailRuCloud/WebBin/WebBinRequestRepo.cs | 12 ++- .../MailRuCloud/WebM1/WebM1RequestRepo.cs | 6 +- .../WebV2/Requests/DownloadRequest.cs | 4 +- .../MailRuCloud/WebV2/WebV2RequestRepo.cs | 2 +- .../YandexDisk/YadWeb/DtoImportYadWeb.cs | 47 ++++++++---- .../YandexDisk/YadWeb/YadWebRequestRepo.cs | 48 ++++++++---- .../YandexDisk/YadWebV2/DtoImportYadWeb.cs | 43 +++++++---- .../YandexDisk/YadWebV2/YadWebRequestRepo.cs | 45 +++++++---- MailRuCloud/MailRuCloudApi/Base/WebDavPath.cs | 14 ++-- MailRuCloud/MailRuCloudApi/Cloud.cs | 48 +++++++----- .../MailRuCloudApi/Common/ItemCache.cs | 2 +- .../MailRuCloudApi/Extensions/Extensions.cs | 6 +- MailRuCloud/MailRuCloudApi/Links/Link.cs | 15 +++- .../MailRuCloudApi/Links/LinkManager.cs | 6 +- .../SpecialCommands/Commands/ListCommand.cs | 2 +- .../Streams/UploadStreamFabric.cs | 7 +- .../StoreBase/LocalStoreCollectionProps.cs | 30 ++++---- .../StoreBase/LocalStoreItemProps.cs | 4 +- 25 files changed, 355 insertions(+), 226 deletions(-) diff --git a/MailRuCloud/MailRuCloudApi/Base/File.cs b/MailRuCloud/MailRuCloudApi/Base/File.cs index a1a4121b..16c7a348 100644 --- a/MailRuCloud/MailRuCloudApi/Base/File.cs +++ b/MailRuCloud/MailRuCloudApi/Base/File.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.IO; @@ -29,7 +30,8 @@ public File(string fullPath, long size, IFileHash hash = null) public File(string fullPath, long size, params PublicLinkInfo[] links) : this(fullPath, size) { - PublicLinks.AddRange(links); + foreach (var link in links) + PublicLinks.AddOrUpdate(link.Uri.AbsolutePath, link, (_, _) => link); } private IFileHash _hash; @@ -37,17 +39,18 @@ public File(string fullPath, long size, params PublicLinkInfo[] links) /// /// makes copy of this file with new path /// - /// + /// /// - public virtual File New(string newfullPath) + public virtual File New(string newFullPath) { - var file = new File(newfullPath, Size, Hash) + var file = new File(newFullPath, Size, Hash) { CreationTimeUtc = CreationTimeUtc, LastAccessTimeUtc = LastAccessTimeUtc, LastWriteTimeUtc = LastWriteTimeUtc }; - file.PublicLinks.AddRange(PublicLinks); + foreach (var linkPair in PublicLinks) + file.PublicLinks.AddOrUpdate(linkPair.Key, linkPair.Value, (_, _) => linkPair.Value); return file; } @@ -123,15 +126,16 @@ protected set /// Gets public file link. /// /// Public link. - public List PublicLinks => _publicLinks ??= new List(); + public ConcurrentDictionary PublicLinks + => _publicLinks ??= new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); - private List _publicLinks; + private ConcurrentDictionary _publicLinks; public IEnumerable GetPublicLinks(Cloud cloud) { - return !PublicLinks.Any() + return PublicLinks.IsEmpty ? cloud.GetSharedLinks(FullPath) - : PublicLinks; + : PublicLinks.Values; } /// @@ -198,14 +202,14 @@ public PublishInfo ToPublishInfo(Cloud cloud, bool generateDirectVideoLink, Shar int cnt = 0; foreach (var innerFile in Files) { - if (innerFile.PublicLinks.Any()) + if (!innerFile.PublicLinks.IsEmpty) info.Items.Add(new PublishInfoItem { Path = innerFile.FullPath, - Urls = innerFile.PublicLinks.Select(pli => pli.Uri).ToList(), + Urls = innerFile.PublicLinks.Select(pli => pli.Value.Uri).ToList(), PlaylistUrl = !isSplitted || cnt > 0 ? generateDirectVideoLink - ? ConvertToVideoLink(cloud, innerFile.PublicLinks.First().Uri, videoResolution) + ? ConvertToVideoLink(cloud, innerFile.PublicLinks.Values.FirstOrDefault()?.Uri, videoResolution) : null : null }); diff --git a/MailRuCloud/MailRuCloudApi/Base/Folder.cs b/MailRuCloud/MailRuCloudApi/Base/Folder.cs index 0dfc0846..07fdaacd 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Folder.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Folder.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.IO; @@ -27,40 +28,34 @@ public Folder(string fullPath) /// Folder size. /// Full folder path. /// Public folder link. - public Folder(FileSize size, string fullPath, IEnumerable publicLinks = null):this(fullPath) + public Folder(FileSize size, string fullPath, IEnumerable publicLinks = null) + : this(fullPath) { Size = size; - if (null != publicLinks) - PublicLinks.AddRange(publicLinks); + if (publicLinks != null) + { + foreach (var link in publicLinks) + PublicLinks.TryAdd(link.Uri.AbsolutePath, link); + } } public IEnumerable Entries { get { - foreach (var file in Files) + foreach (var file in Files.Values) yield return file; - foreach (var folder in Folders) + foreach (var folder in Folders.Values) yield return folder; } } - public List Files { get; set; } = new(); - - public List Folders { get; set; } = new(); + public ConcurrentDictionary Files { get; set; } + = new(StringComparer.InvariantCultureIgnoreCase); + public ConcurrentDictionary Folders { get; set; } + = new(StringComparer.InvariantCultureIgnoreCase); - /// - /// Gets number of folders in folder. - /// - /// Number of folders. - public int NumberOfFolders => Files.Count; - - /// - /// Gets number of files in folder. - /// - /// Number of files. - public int NumberOfFiles => Folders.Count; /// /// Gets folder name. @@ -88,14 +83,16 @@ public string FullPath /// Gets public file link. /// /// Public link. - public List PublicLinks => _publicLinks ??= new List(); - private List _publicLinks; + public ConcurrentDictionary PublicLinks + => _publicLinks ??= new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); + + private ConcurrentDictionary _publicLinks; public IEnumerable GetPublicLinks(Cloud cloud) { - return !PublicLinks.Any() + return PublicLinks.IsEmpty ? cloud.GetSharedLinks(FullPath) - : PublicLinks; + : PublicLinks.Values; } @@ -111,7 +108,7 @@ public IEnumerable GetPublicLinks(Cloud cloud) public bool IsFile => false; - public bool IsChildrenLoaded { get; internal set; } + public bool IsChildrenLoaded { get; internal set; } public int? ServerFoldersCount { get; set; } @@ -120,29 +117,32 @@ public IEnumerable GetPublicLinks(Cloud cloud) public PublishInfo ToPublishInfo() { var info = new PublishInfo(); - if (PublicLinks.Any()) - info.Items.Add(new PublishInfoItem { Path = FullPath, Urls = PublicLinks.Select(pli => pli.Uri).ToList() }); + if (!PublicLinks.IsEmpty) + info.Items.Add(new PublishInfoItem { Path = FullPath, Urls = PublicLinks.Select(pli => pli.Value.Uri).ToList() }); return info; } - //public List> GetLinearChildren() - //{ - - //} + //public List> GetLinearChildren() + //{ + + //} public void Forget(object whomKey) { - string key = whomKey.ToString(); + string key = whomKey?.ToString(); + if (string.IsNullOrEmpty(key)) return; - var file = Files.FirstOrDefault(f => f.FullPath == key); - if (null != file) - Files.Remove(file); - else + + // Удалять начинаем с директорий, т.к. их обычно меньше, + // а значит поиск должен завершиться в среднем быстрее. + + if (!Folders.TryRemove(key, out _)) { - var folder = Folders.FirstOrDefault(f => f.FullPath == key); - if (null != folder) - Folders.Remove(folder); + // Если по ключу в виде полного пути не удалось удалить директорию, + // пытаемся по этому же ключу удалить файл, если он есть. + // Если ничего не удалилось, значит и удалять нечего. + _ = Files.TryRemove( key, out _); } } } diff --git a/MailRuCloud/MailRuCloudApi/Base/IEntry.cs b/MailRuCloud/MailRuCloudApi/Base/IEntry.cs index be955ee3..a3b68959 100644 --- a/MailRuCloud/MailRuCloudApi/Base/IEntry.cs +++ b/MailRuCloud/MailRuCloudApi/Base/IEntry.cs @@ -1,5 +1,5 @@ using System; -using System.Collections.Generic; +using System.Collections.Concurrent; namespace YaR.Clouds.Base { @@ -10,6 +10,6 @@ public interface IEntry string Name { get; } string FullPath { get; } DateTime CreationTimeUtc { get; } - List PublicLinks { get; } + ConcurrentDictionary PublicLinks { get; } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/DtoImportExtensions.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/DtoImportExtensions.cs index 5d731d45..cf061c50 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/DtoImportExtensions.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/DtoImportExtensions.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Security.Authentication; @@ -62,21 +63,26 @@ internal static Folder ToFolder(this ListRequest.Result data) } internal static Folder ToFolder(this FsFolder data) - { - var res = new Folder((long)data.Size, data.FullPath) { IsChildrenLoaded = data.IsChildrenLoaded }; - - res.Files.AddRange(data.Items - .OfType() - .Select(f => f.ToFile()) - .ToGroupedFiles()); - - foreach (var it in data.Items.OfType()) - { - res.Folders.Add(it.ToFolder()); - } + { + var res = new Folder((long)data.Size, data.FullPath) { IsChildrenLoaded = data.IsChildrenLoaded }; + + res.Files = new ConcurrentDictionary( + data.Items + .OfType() + .Select(f => f.ToFile()) + .ToGroupedFiles() + .Select(item => new KeyValuePair(item.FullPath, item)), + StringComparer.InvariantCultureIgnoreCase); + + res.Folders = new ConcurrentDictionary( + data.Items + .OfType() + .Select(f => f.ToFolder()) + .Select(item => new KeyValuePair(item.FullPath, item)), + StringComparer.InvariantCultureIgnoreCase); - return res; - } + return res; + } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/DtoImport.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/DtoImport.cs index b017e34c..27474314 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/DtoImport.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/DtoImport.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Net.Http; @@ -202,15 +203,8 @@ public static IEntry ToEntry(this FolderInfoResult data, string publicBaseUrl) var folder = new Folder(data.Body.Size, WebDavPath.Combine(data.Body.Home ?? WebDavPath.Root, data.Body.Name)) { - Folders = data.Body.List? - .Where(it => FolderKinds.Contains(it.Kind)) - .Select(item => item.ToFolder(publicBaseUrl)) - .ToList(), - Files = data.Body.List? - .Where(it => it.Kind == "file") - .Select(item => item.ToFile(publicBaseUrl, "")) - .ToGroupedFiles() - .ToList() + Folders = data.Body.List.ToFolderDictionary(publicBaseUrl), + Files = data.Body.List.ToFileDictionary(publicBaseUrl), }; @@ -227,21 +221,41 @@ public static Folder ToFolder(this FolderInfoResult data, string publicBaseUrl, ServerFoldersCount = data.Body.Count?.Folders, ServerFilesCount = data.Body.Count?.Files, - Folders = data.Body.List? - .Where(it => FolderKinds.Contains(it.Kind)) - .Select(item => item.ToFolder(publicBaseUrl)) - .ToList(), - Files = data.Body.List? - .Where(it => it.Kind == "file") - .Select(item => item.ToFile(publicBaseUrl, "")) - .ToGroupedFiles() - .ToList(), + Folders = data.Body.List.ToFolderDictionary(publicBaseUrl), + Files = data.Body.List.ToFileDictionary(publicBaseUrl), IsChildrenLoaded = true }; return folder; } + public static ConcurrentDictionary ToFolderDictionary( + this List data, string publicBaseUrl) + { + if (data == null) + return new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); + + return new ConcurrentDictionary( + data.Where(it => FolderKinds.Contains(it.Kind)) + .Select(item => item.ToFolder(publicBaseUrl)) + .Select(item => new KeyValuePair(item.FullPath, item)), + StringComparer.InvariantCultureIgnoreCase); + } + + public static ConcurrentDictionary ToFileDictionary( + this List data, string publicBaseUrl) + { + if (data == null) + return new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); + + return new ConcurrentDictionary( + data.Where(it => it.Kind == "file") + .Select(item => item.ToFile(publicBaseUrl, "")) + .ToGroupedFiles() + .Select(item => new KeyValuePair(item.FullPath, item)), + StringComparer.InvariantCultureIgnoreCase); + } + /// /// When it's a linked item, need to shift paths /// @@ -263,10 +277,11 @@ private static void PatchEntryPath(FolderInfoResult data, string home, Link link } //TODO: subject to heavily refact - public static File ToFile(this FolderInfoResult data, string publicBaseUrl, string home = null, Link ulink = null, string filename = null, string nameReplacement = null) + public static File ToFile(this FolderInfoResult data, string publicBaseUrl, + string home = null, Link ulink = null, string fileName = null, string nameReplacement = null) { if (ulink == null || ulink.IsLinkedToFileSystem) - if (string.IsNullOrEmpty(filename)) + if (string.IsNullOrEmpty(fileName)) { return new File(WebDavPath.Combine(data.Body.Home ?? "", data.Body.Name), data.Body.Size); } @@ -275,39 +290,39 @@ public static File ToFile(this FolderInfoResult data, string publicBaseUrl, stri var z = data.Body.List? .Where(it => it.Kind == "file") - .Select(it => filename != null && it.Name == filename + .Select(it => fileName != null && it.Name == fileName ? it.ToFile(publicBaseUrl, nameReplacement) : it.ToFile(publicBaseUrl, "")) .ToList(); - var cmpname = string.IsNullOrEmpty(nameReplacement) - ? filename + var cmpName = string.IsNullOrEmpty(nameReplacement) + ? fileName : nameReplacement; - if (string.IsNullOrEmpty(cmpname) && data.Body.Weblink != "/" && ulink is { IsLinkedToFileSystem: false }) + if (string.IsNullOrEmpty(cmpName) && data.Body.Weblink != "/" && ulink is { IsLinkedToFileSystem: false }) { - cmpname = WebDavPath.Name(ulink.PublicLinks.First().Uri.OriginalString); + cmpName = WebDavPath.Name(ulink.PublicLinks.Values.FirstOrDefault()?.Uri.OriginalString ?? string.Empty); } var groupedFile = z?.ToGroupedFiles(); - var res = groupedFile?.First(it => string.IsNullOrEmpty(cmpname) || it.Name == cmpname); + var res = groupedFile?.First(it => string.IsNullOrEmpty(cmpName) || it.Name == cmpName); return res; } - private static File ToFile(this FolderInfoResult.FolderInfoBody.FolderInfoProps item, string publicBaseUrl, string nameReplacement) + private static File ToFile(this FolderInfoResult.FolderInfoBody.FolderInfoProps item, + string publicBaseUrl, string nameReplacement) { try { - var path = string.IsNullOrEmpty(nameReplacement) ? item.Home : WebDavPath.Combine(WebDavPath.Parent(item.Home), nameReplacement); - var file = new File(path ?? item.Name, item.Size, new FileHashMrc(item.Hash)); - file.PublicLinks.AddRange(item.Weblink.ToPublicLinkInfos(publicBaseUrl)); + foreach (var link in item.Weblink.ToPublicLinkInfos(publicBaseUrl)) + file.PublicLinks.TryAdd(link.Uri.AbsolutePath, link); var dt = UnixTimeStampToDateTime(item.Mtime, file.CreationTimeUtc); file.CreationTimeUtc = @@ -321,7 +336,6 @@ private static File ToFile(this FolderInfoResult.FolderInfoBody.FolderInfoProps Console.WriteLine(e); throw; } - } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/MobileRequestRepo.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/MobileRequestRepo.cs index 9aa20881..97ff8552 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/MobileRequestRepo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/MobileRequestRepo.cs @@ -154,13 +154,13 @@ public async Task FolderInfo(RemotePath path, int offset = 0, int limit CreationTimeUtc = fsfi.ModifDate, LastWriteTimeUtc = fsfi.ModifDate }; - f.Files.Add(fi); + f.Files.TryAdd(fi.FullPath, fi); break; } case FsFolder fsfo: { var fo = new Folder(fsfo.Size == null ? 0 : (long) fsfo.Size.Value, fsfo.FullPath); - f.Folders.Add(fo); + f.Folders.TryAdd(fo.FullPath, fo); break; } default: diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebBin/Requests/DownloadRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebBin/Requests/DownloadRequest.cs index 8e4bbdfc..2c791d0b 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebBin/Requests/DownloadRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebBin/Requests/DownloadRequest.cs @@ -16,19 +16,23 @@ public DownloadRequest(HttpCommonSettings settings, IAuth authent, File file, lo public HttpWebRequest Request { get; } - private static HttpWebRequest CreateRequest(HttpCommonSettings settings, IAuth authent, File file, long instart, long inend, string downServerUrl, IEnumerable publicBaseUrls) //(IAuth authent, IWebProxy proxy, string url, long instart, long inend, string userAgent) + private static HttpWebRequest CreateRequest(HttpCommonSettings settings, + IAuth authent, File file, long instart, long inend, string downServerUrl, IEnumerable publicBaseUrls) + //(IAuth authent, IWebProxy proxy, string url, long instart, long inend, string userAgent) { - bool isLinked = file.PublicLinks.Any(); + bool isLinked = !file.PublicLinks.IsEmpty; string url; if (isLinked) { - var urii = file.PublicLinks.First().Uri; - var uriistr = urii.OriginalString; - var baseura = publicBaseUrls.First(pbu => uriistr.StartsWith(pbu, StringComparison.InvariantCulture)); + var urii = file.PublicLinks.Values.FirstOrDefault()?.Uri; + var uriistr = urii?.OriginalString; + var baseura = uriistr == null + ? null + : publicBaseUrls.First(pbu => uriistr.StartsWith(pbu, StringComparison.InvariantCulture)); if (string.IsNullOrEmpty(baseura)) - throw new ArgumentException("url does not starts with base url"); + throw new ArgumentException("URL does not starts with base URL"); url = $"{downServerUrl}{WebDavPath.EscapeDataString(uriistr.Remove(0, baseura.Length))}"; } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebBin/WebBinRequestRepo.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebBin/WebBinRequestRepo.cs index bee6cc20..d129400d 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebBin/WebBinRequestRepo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebBin/WebBinRequestRepo.cs @@ -71,7 +71,7 @@ public Stream GetDownloadStream(File afile, long? start = null, long? end = null private DownloadStream GetDownloadStreamInternal(File afile, long? start = null, long? end = null) { - bool isLinked = afile.PublicLinks.Any(); + bool isLinked = !afile.PublicLinks.IsEmpty; Cached downServer = null; var pendingServers = isLinked @@ -201,10 +201,14 @@ private async Task FolderInfo(string path, int depth = 1) var zz = datares.ToFolder(); - return zz.Files.First(f => f.Name == name); + // return zz.Files.Values.FirstOrDefault(f => f.Name == name); + // Вариант без перебора предпочтительнее + if (zz.Files.TryGetValue(path, out var file)) + return file; + return null; } } - catch (RequestException re) when (re.StatusCode == HttpStatusCode.NotFound) + catch (RequestException re) when (re.StatusCode == HttpStatusCode.NotFound) { return null; } @@ -254,7 +258,7 @@ public async Task FolderInfo(RemotePath path, int offset = 0, int limit PublicBaseUrlDefault, home: WebDavPath.Parent(path.Path ?? string.Empty), ulink: path.Link, - filename: path.Link == null ? WebDavPath.Name(path.Path) : path.Link.OriginalName, + fileName: path.Link == null ? WebDavPath.Name(path.Path) : path.Link.OriginalName, nameReplacement: path.Link?.IsLinkedToFileSystem ?? true ? WebDavPath.Name(path.Path) : path.Link.Name ) : datares.ToFolder(PublicBaseUrlDefault, path.Path, path.Link); diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/WebM1RequestRepo.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/WebM1RequestRepo.cs index 804ba9a6..7aefc734 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/WebM1RequestRepo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/WebM1RequestRepo.cs @@ -71,7 +71,7 @@ public Stream GetDownloadStream(File afile, long? start = null, long? end = null private DownloadStream GetDownloadStreamInternal(File afile, long? start = null, long? end = null) { - bool isLinked = afile.PublicLinks.Any(); + bool isLinked = !afile.PublicLinks.IsEmpty; Cached downServer = null; var pendingServers = isLinked @@ -87,7 +87,7 @@ CustomDisposable ResponseGenerator(long instart, long inend, Fi downServer = pendingServers.Next(downServer); string url =(isLinked - ? $"{downServer.Value.Url}{WebDavPath.EscapeDataString(file.PublicLinks.First().Uri.PathAndQuery)}" + ? $"{downServer.Value.Url}{WebDavPath.EscapeDataString(file.PublicLinks.Values.FirstOrDefault()?.Uri.PathAndQuery)}" : $"{downServer.Value.Url}{Uri.EscapeDataString(file.FullPath.TrimStart('/'))}") + $"?client_id={HttpSettings.ClientId}&token={Authent.AccessToken}"; var uri = new Uri(url); @@ -259,7 +259,7 @@ public async Task FolderInfo(RemotePath path, int offset = 0, int limit PublicBaseUrlDefault, home: WebDavPath.Parent(path.Path ?? string.Empty), ulink: path.Link, - filename: path.Link == null ? WebDavPath.Name(path.Path) : path.Link.OriginalName, + fileName: path.Link == null ? WebDavPath.Name(path.Path) : path.Link.OriginalName, nameReplacement: path.Link?.IsLinkedToFileSystem ?? true ? WebDavPath.Name(path.Path) : null ) : datares.ToFolder(PublicBaseUrlDefault, path.Path, path.Link); diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/DownloadRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/DownloadRequest.cs index 60268bf6..d5e9542a 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/DownloadRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/DownloadRequest.cs @@ -20,7 +20,7 @@ public DownloadRequest(File file, long instart, long inend, IAuth authent, HttpC private static HttpWebRequest CreateRequest(IAuth authent, IWebProxy proxy, File file, long instart, long inend, string userAgent, Cached> shards) { - bool isLinked = file.PublicLinks.Any(); + bool isLinked = !file.PublicLinks.IsEmpty; string downloadkey = isLinked ? authent.DownloadToken @@ -32,7 +32,7 @@ private static HttpWebRequest CreateRequest(IAuth authent, IWebProxy proxy, File string url = !isLinked ? $"{shard.Url}{Uri.EscapeDataString(file.FullPath)}" - : $"{shard.Url}{file.PublicLinks.First().Uri.PathAndQuery.Remove(0, "/public".Length)}?key={downloadkey}"; + : $"{shard.Url}{file.PublicLinks.Values.FirstOrDefault()?.Uri.PathAndQuery.Remove(0, "/public".Length) ?? string.Empty}?key={downloadkey}"; var request = (HttpWebRequest) WebRequest.Create(url); diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/WebV2RequestRepo.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/WebV2RequestRepo.cs index 520b9bbc..864fbaf1 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/WebV2RequestRepo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/WebV2RequestRepo.cs @@ -205,7 +205,7 @@ public async Task FolderInfo(RemotePath path, int offset = 0, int limit PublicBaseUrlDefault, home: WebDavPath.Parent(path.Path ?? string.Empty), ulink: path.Link, - filename: path.Link == null ? WebDavPath.Name(path.Path) : path.Link.OriginalName, + fileName: path.Link == null ? WebDavPath.Name(path.Path) : path.Link.OriginalName, nameReplacement: path.Link?.IsLinkedToFileSystem ?? true ? WebDavPath.Name(path.Path) : null) : datares.ToFolder(PublicBaseUrlDefault, path.Path, path.Link); diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/DtoImportYadWeb.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/DtoImportYadWeb.cs index bec5a17d..b533fcb4 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/DtoImportYadWeb.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/DtoImportYadWeb.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Concurrent; +using System.Collections.Generic; using System.Linq; using YaR.Clouds.Base.Repos.YandexDisk.YadWeb.Models; using YaR.Clouds.Base.Requests.Types; @@ -28,26 +30,33 @@ public static AccountInfoResult ToAccountInfo(this YadResponseModel it.Type == "file") - .Select(f => f.ToFile(publicBaseUrl)) - .ToGroupedFiles() - ); - - foreach (var it in fi.Where(it => it.Type == "dir")) { - res.Folders.Add(it.ToFolder()); + PublicLinkInfo item = new PublicLinkInfo("short", itemInfo.Meta.UrlShort); + res.PublicLinks.TryAdd(item.Uri.AbsoluteUri, item); } + res.Files = new ConcurrentDictionary( + fi + .Where(it => it.Type == "file") + .Select(f => f.ToFile(publicBaseUrl)) + .ToGroupedFiles() + .Select(item => new KeyValuePair(item.FullPath, item)), + StringComparer.InvariantCultureIgnoreCase); + + res.Folders = new ConcurrentDictionary( + fi + .Where(it => it.Type == "dir") + .Select(f => f.ToFolder()) + .Select(item => new KeyValuePair(item.FullPath, item)), + StringComparer.InvariantCultureIgnoreCase); + return res; } @@ -62,7 +71,10 @@ public static File ToFile(this FolderInfoDataResource data, string publicBaseUrl LastWriteTimeUtc = UnixTimeStampToDateTime(data.Mtime, DateTime.MinValue) }; if (!string.IsNullOrEmpty(data.Meta.UrlShort)) - res.PublicLinks.Add(new PublicLinkInfo("short", data.Meta.UrlShort)); + { + PublicLinkInfo item = new PublicLinkInfo("short", data.Meta.UrlShort); + res.PublicLinks.TryAdd(item.Uri.AbsoluteUri, item); + } return res; } @@ -80,8 +92,11 @@ public static File ToFile(this YadItemInfoRequestData data, string publicBaseUrl // : data.Meta.UrlShort }; if (!string.IsNullOrEmpty(data.Meta.UrlShort)) - res.PublicLinks.Add(new PublicLinkInfo("short", data.Meta.UrlShort)); - + { + PublicLinkInfo item = new PublicLinkInfo("short", data.Meta.UrlShort); + res.PublicLinks.TryAdd(item.Uri.AbsoluteUri, item); + } + return res; } @@ -207,7 +222,7 @@ private static DateTime UnixTimeStampToDateTime(double unixTimeStamp, DateTime d { // Unix timestamp is seconds past epoch var dtDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); - dtDateTime = dtDateTime.AddSeconds(unixTimeStamp); //.ToLocalTime(); - doesn't need, clients usially convert to localtime by itself + dtDateTime = dtDateTime.AddSeconds(unixTimeStamp); //.ToLocalTime(); - doesn't need, clients usually convert to localtime by itself return dtDateTime; } catch (Exception e) diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebRequestRepo.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebRequestRepo.cs index e47e3658..4f593d3c 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebRequestRepo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebRequestRepo.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Net; @@ -237,13 +238,20 @@ private async Task MediaFolderInfo(string path) if (WebDavPath.PathEquals(path, YadMediaPath)) return root; - string albumName = WebDavPath.Name(path); - var album = root.Folders.FirstOrDefault(f => f.Name == albumName); - if (null == album) + //string albumName = WebDavPath.Name(path); + //var album = root.Folders.Values.FirstOrDefault(f => f.Name == albumName); + //if (null == album) + // return null; + // Вариант без перебора предпочтительнее + if (!root.Folders.TryGetValue(path, out var album)) return null; - _ = new YaDCommonRequest(HttpSettings, (YadWebAuth) Authent) - .With(new YadFolderInfoPostModel(album.PublicLinks.First().Key, "/album"), + var key = album.PublicLinks.Values.FirstOrDefault()?.Key; + if (key == null) + return null; + + _ = new YaDCommonRequest(HttpSettings, (YadWebAuth)Authent) + .With(new YadFolderInfoPostModel(key, "/album"), out YadResponseModel folderInfo) .MakeRequestAsync() .Result; @@ -265,19 +273,31 @@ private async Task MediaFolderRootInfo() .MakeRequestAsync(); if (slices.Data.Albums.Camera != null) - res.Folders.Add(new Folder($"{YadMediaPath}/.{slices.Data.Albums.Camera.Id}") - { ServerFilesCount = (int)slices.Data.Albums.Camera.Count }); + { + Folder folder = new Folder($"{YadMediaPath}/.{slices.Data.Albums.Camera.Id}") + { ServerFilesCount = (int)slices.Data.Albums.Camera.Count }; + res.Folders.TryAdd(folder.FullPath, folder); + } if (slices.Data.Albums.Photounlim != null) - res.Folders.Add(new Folder($"{YadMediaPath}/.{slices.Data.Albums.Photounlim.Id}") - { ServerFilesCount = (int)slices.Data.Albums.Photounlim.Count }); + { + Folder folder = new Folder($"{YadMediaPath}/.{slices.Data.Albums.Photounlim.Id}") + { ServerFilesCount = (int)slices.Data.Albums.Photounlim.Count }; + res.Folders.TryAdd(folder.FullPath, folder); + } if (slices.Data.Albums.Videos != null) - res.Folders.Add(new Folder($"{YadMediaPath}/.{slices.Data.Albums.Videos.Id}") - { ServerFilesCount = (int)slices.Data.Albums.Videos.Count }); + { + Folder folder = new Folder($"{YadMediaPath}/.{slices.Data.Albums.Videos.Id}") + { ServerFilesCount = (int)slices.Data.Albums.Videos.Count }; + res.Folders.TryAdd(folder.FullPath, folder); + } - res.Folders.AddRange(albums.Data.Select(al => new Folder($"{YadMediaPath}/{al.Title}") + foreach (var item in albums.Data) { - PublicLinks = { new PublicLinkInfo(al.Public.PublicUrl) {Key = al.Public.PublicKey} } - })); + Folder folder = new Folder($"{YadMediaPath}/{item.Title}"); + folder.PublicLinks.TryAdd( + item.Public.PublicUrl, + new PublicLinkInfo(item.Public.PublicUrl) { Key = item.Public.PublicKey }); + } return res; } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/DtoImportYadWeb.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/DtoImportYadWeb.cs index 8df7aa3d..7c78d0d8 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/DtoImportYadWeb.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/DtoImportYadWeb.cs @@ -1,6 +1,9 @@ using System; +using System.Collections.Concurrent; +using System.Collections.Generic; using System.Linq; using System.Security.Authentication; +using YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests.Types; using YaR.Clouds.Base.Repos.YandexDisk.YadWebV2.Models; using YaR.Clouds.Base.Requests.Types; @@ -31,7 +34,6 @@ public static AccountInfoResult ToAccountInfo(this YadResponseModel it.Type == "file") - .Select(f => f.ToFile()) - .ToGroupedFiles() - ); - - foreach (var it in fi.Where(it => it.Type == "dir")) { - res.Folders.Add(it.ToFolder()); + PublicLinkInfo item = new PublicLinkInfo("short", itemInfo.Meta.UrlShort); + res.PublicLinks.TryAdd(item.Uri.AbsoluteUri, item); } + res.Files = new ConcurrentDictionary( + fi + .Where(it => it.Type == "file") + .Select(f => f.ToFile()) + .ToGroupedFiles() + .Select(item => new KeyValuePair(item.FullPath, item)), + StringComparer.InvariantCultureIgnoreCase); + + res.Folders = new ConcurrentDictionary( + fi + .Where(it => it.Type == "dir") + .Select(f => f.ToFolder()) + .Select(item => new KeyValuePair(item.FullPath, item)), + StringComparer.InvariantCultureIgnoreCase); + return res; } @@ -66,7 +75,10 @@ public static File ToFile(this FolderInfoDataResource data) LastWriteTimeUtc = UnixTimeStampToDateTime(data.Mtime, DateTime.MinValue) }; if (!string.IsNullOrEmpty(data.Meta.UrlShort)) - res.PublicLinks.Add(new PublicLinkInfo("short", data.Meta.UrlShort)); + { + PublicLinkInfo item = new PublicLinkInfo("short", data.Meta.UrlShort); + res.PublicLinks.TryAdd(item.Uri.AbsoluteUri, item); + } return res; } @@ -84,8 +96,11 @@ public static File ToFile(this YadItemInfoRequestData data) // : data.Meta.UrlShort }; if (!string.IsNullOrEmpty(data.Meta.UrlShort)) - res.PublicLinks.Add(new PublicLinkInfo("short", data.Meta.UrlShort)); - + { + PublicLinkInfo item = new PublicLinkInfo("short", data.Meta.UrlShort); + res.PublicLinks.TryAdd(item.Uri.AbsoluteUri, item); + } + return res; } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/YadWebRequestRepo.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/YadWebRequestRepo.cs index a3571717..503bd011 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/YadWebRequestRepo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/YadWebRequestRepo.cs @@ -246,13 +246,20 @@ private async Task MediaFolderInfo(string path) if (WebDavPath.PathEquals(path, YadMediaPath)) return root; - string albumName = WebDavPath.Name(path); - var album = root.Folders.FirstOrDefault(f => f.Name == albumName); - if (null == album) + //string albumName = WebDavPath.Name(path); + //var album = root.Folders.Values.FirstOrDefault(f => f.Name == albumName); + //if (null == album) + // return null; + // Вариант без перебора предпочтительнее + if (!root.Folders.TryGetValue(path, out var album)) + return null; + + var key = album.PublicLinks.Values.FirstOrDefault()?.Key; + if (key == null) return null; _ = new YaDCommonRequest(HttpSettings, (YadWebAuth) Authent) - .With(new YadFolderInfoPostModel(album.PublicLinks.First().Key, "/album"), + .With(new YadFolderInfoPostModel(key, "/album"), out YadResponseModel folderInfo) .MakeRequestAsync() .Result; @@ -274,19 +281,31 @@ private async Task MediaFolderRootInfo() .MakeRequestAsync(); if (slices.Data.Albums.Camera != null) - res.Folders.Add(new Folder($"{YadMediaPath}/.{slices.Data.Albums.Camera.Id}") - { ServerFilesCount = (int)slices.Data.Albums.Camera.Count }); + { + Folder folder = new Folder($"{YadMediaPath}/.{slices.Data.Albums.Camera.Id}") + { ServerFilesCount = (int)slices.Data.Albums.Camera.Count }; + res.Folders.TryAdd(folder.FullPath, folder); + } if (slices.Data.Albums.Photounlim != null) - res.Folders.Add(new Folder($"{YadMediaPath}/.{slices.Data.Albums.Photounlim.Id}") - { ServerFilesCount = (int)slices.Data.Albums.Photounlim.Count }); + { + Folder folder = new Folder($"{YadMediaPath}/.{slices.Data.Albums.Photounlim.Id}") + { ServerFilesCount = (int)slices.Data.Albums.Photounlim.Count }; + res.Folders.TryAdd(folder.FullPath, folder); + } if (slices.Data.Albums.Videos != null) - res.Folders.Add(new Folder($"{YadMediaPath}/.{slices.Data.Albums.Videos.Id}") - { ServerFilesCount = (int)slices.Data.Albums.Videos.Count }); + { + Folder folder = new Folder($"{YadMediaPath}/.{slices.Data.Albums.Videos.Id}") + { ServerFilesCount = (int)slices.Data.Albums.Videos.Count }; + res.Folders.TryAdd(folder.FullPath, folder); + } - res.Folders.AddRange(albums.Data.Select(al => new Folder($"{YadMediaPath}/{al.Title}") + foreach (var item in albums.Data) { - PublicLinks = { new PublicLinkInfo(al.Public.PublicUrl) {Key = al.Public.PublicKey} } - })); + Folder folder = new Folder($"{YadMediaPath}/{item.Title}"); + folder.PublicLinks.TryAdd( + item.Public.PublicUrl, + new PublicLinkInfo(item.Public.PublicUrl) { Key = item.Public.PublicKey }); + } return res; } diff --git a/MailRuCloud/MailRuCloudApi/Base/WebDavPath.cs b/MailRuCloud/MailRuCloudApi/Base/WebDavPath.cs index ccfca216..d83ff0d4 100644 --- a/MailRuCloud/MailRuCloudApi/Base/WebDavPath.cs +++ b/MailRuCloud/MailRuCloudApi/Base/WebDavPath.cs @@ -103,17 +103,21 @@ public static WebDavPathParts Parts(string path) return res; } - public static IEnumerable GetParents(string path, bool includeSelf = true) + public static List GetParents(string path, bool includeSelf = true) { + List result = new List(); + path = Clean(path); if (includeSelf) - yield return path; + result.Add(path); while (path != Root) { path = Parent(path); - yield return path; - } + result.Add(path); + } + + return result; } public static string ModifyParent(string path, string oldParent, string newParent) @@ -137,7 +141,7 @@ public static bool PathEquals(string path1, string path2) public static string EscapeDataString(string path) { return Uri - .EscapeDataString(path) + .EscapeDataString(path ?? string.Empty) .Replace("#", "%23"); } } diff --git a/MailRuCloud/MailRuCloudApi/Cloud.cs b/MailRuCloud/MailRuCloudApi/Cloud.cs index f9ecc9d8..e63c8751 100644 --- a/MailRuCloud/MailRuCloudApi/Cloud.cs +++ b/MailRuCloud/MailRuCloudApi/Cloud.cs @@ -11,6 +11,7 @@ using Newtonsoft.Json; using YaR.Clouds.Base; using YaR.Clouds.Base.Repos; +using YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests.Types; using YaR.Clouds.Base.Requests.Types; using YaR.Clouds.Common; using YaR.Clouds.Extensions; @@ -167,22 +168,25 @@ private void FillWithULinks(Folder folder) string linkpath = WebDavPath.Combine(folder.FullPath, flink.Name); if (!flink.IsFile) - folder.Folders.Add(new Folder(0, linkpath) { CreationTimeUtc = flink.CreationDate ?? DateTime.MinValue }); + { + Folder item = new Folder(0, linkpath) { CreationTimeUtc = flink.CreationDate ?? DateTime.MinValue }; + folder.Folders.AddOrUpdate(item.FullPath, item, (_, _) => item); + } else { - if (folder.Files.Any(inf => inf.FullPath == linkpath)) + if (folder.Files.ContainsKey(linkpath)) continue; - var newfile = new File(linkpath, flink.Size, new PublicLinkInfo(flink.Href)); + var newFile = new File(linkpath, flink.Size, new PublicLinkInfo(flink.Href)); if (flink.CreationDate != null) - newfile.LastWriteTimeUtc = flink.CreationDate.Value; - folder.Files.Add(newfile); + newFile.LastWriteTimeUtc = flink.CreationDate.Value; + folder.Files.AddOrUpdate(newFile.FullPath, newFile, (_, _) => newFile); } } } } - foreach (var childFolder in folder.Folders) + foreach (var childFolder in folder.Folders.Values) FillWithULinks(childFolder); } @@ -197,7 +201,7 @@ private void CacheAddEntry(IEntry entry) case Folder { IsChildrenLoaded: true } cfolder: { _itemCache.Add(cfolder.FullPath, cfolder); - _itemCache.Add(cfolder.Files.Select(f => new KeyValuePair(f.FullPath, f))); + _itemCache.Add(cfolder.Files.Select(f => new KeyValuePair(f.Value.FullPath, f.Value))); foreach (var childFolder in cfolder.Entries) CacheAddEntry(childFolder); @@ -211,23 +215,33 @@ private void CacheAddEntry(IEntry entry) public virtual IEntry GetItem(string path, ItemType itemType = ItemType.Unknown, bool resolveLinks = true) => GetItemAsync(path, itemType, resolveLinks).Result; - public IEnumerable IsFileExists(string filename, IList folderPaths) + public bool IsFileExists(string filename, List folderPaths) { if (folderPaths == null) - return Enumerable.Empty(); + return false; - var folder = folderPaths + var folders = folderPaths .AsParallel() .WithDegreeOfParallelism(Math.Min(MaxInnerParallelRequests, folderPaths.Count)) .Select(async path => (Folder)await GetItemAsync(path, ItemType.Folder, false)); - if (folder == null) - return Enumerable.Empty(); + if (folders == null) + return false; - var files = folder - .SelectMany(fld => (fld.Result?.Files ?? new List()).Where(file => WebDavPath.PathEquals(file.Name, filename))); + foreach (var item in folders) + { + if (item.Result == null) + continue; + Folder folder = item.Result; + // Вместо перебора и сравнения всех названий файлов в папке, + // формируем полный путь файла, как если бы он был в данной папке, + // а затем проверяем наличие в словаре Files по FullPath. + string fullPath = Path.Combine(folder.FullPath, filename); + if (folder.Files.TryGetValue(fullPath, out _)) + return true; + } - return files; + return false; } #region == Publish ========================================================================================================================== @@ -274,7 +288,7 @@ public async Task Publish(File file, bool makeShareFile = true, { var url = await Publish(innerFile.FullPath); innerFile.PublicLinks.Clear(); - innerFile.PublicLinks.Add(new PublicLinkInfo(url)); + innerFile.PublicLinks.TryAdd(url.AbsolutePath, new PublicLinkInfo(url)); } var info = file.ToPublishInfo(this, generateDirectVideoLink, videoResolution); @@ -309,7 +323,7 @@ public async Task Publish(Folder folder, bool makeShareFile = true) { var url = await Publish(folder.FullPath); folder.PublicLinks.Clear(); - folder.PublicLinks.Add(new PublicLinkInfo(url)); + folder.PublicLinks.TryAdd(url.AbsolutePath, new PublicLinkInfo(url)); var info = folder.ToPublishInfo(); if (!makeShareFile) diff --git a/MailRuCloud/MailRuCloudApi/Common/ItemCache.cs b/MailRuCloud/MailRuCloudApi/Common/ItemCache.cs index a8dfaa59..409cbe35 100644 --- a/MailRuCloud/MailRuCloudApi/Common/ItemCache.cs +++ b/MailRuCloud/MailRuCloudApi/Common/ItemCache.cs @@ -58,7 +58,7 @@ public TimeSpan CleanUpPeriod public int RemoveExpired() { - if (!_items.Any()) return 0; + if (_items.IsEmpty) return 0; DateTime threshold = DateTime.Now - _expirePeriod; int removedCount = 0; diff --git a/MailRuCloud/MailRuCloudApi/Extensions/Extensions.cs b/MailRuCloud/MailRuCloudApi/Extensions/Extensions.cs index 6f662555..f4affbbc 100644 --- a/MailRuCloud/MailRuCloudApi/Extensions/Extensions.cs +++ b/MailRuCloud/MailRuCloudApi/Extensions/Extensions.cs @@ -9,10 +9,10 @@ namespace YaR.Clouds.Extensions { public static class Extensions { - internal static IEnumerable ToPublicLinkInfos(this string uristring, string publicBaseUrl) + internal static IEnumerable ToPublicLinkInfos(this string uriString, string publicBaseUrl) { - if (!string.IsNullOrEmpty(uristring)) - yield return new PublicLinkInfo("", publicBaseUrl, uristring); + if (!string.IsNullOrEmpty(uriString)) + yield return new PublicLinkInfo("", publicBaseUrl, uriString); } diff --git a/MailRuCloud/MailRuCloudApi/Links/Link.cs b/MailRuCloud/MailRuCloudApi/Links/Link.cs index e3530a60..d018b69f 100644 --- a/MailRuCloud/MailRuCloudApi/Links/Link.cs +++ b/MailRuCloud/MailRuCloudApi/Links/Link.cs @@ -1,5 +1,5 @@ using System; -using System.Collections.Generic; +using System.Collections.Concurrent; using System.IO; using YaR.Clouds.Base; using YaR.Clouds.Links.Dto; @@ -11,7 +11,7 @@ public class Link : IEntry { public Link(Uri href, Cloud.ItemType itemType = Cloud.ItemType.Unknown) { - Href = href.IsAbsoluteUri ? href : throw new ArgumentException("Absolute uri required"); + Href = href.IsAbsoluteUri ? href : throw new ArgumentException("Absolute URI required"); IsLinkedToFileSystem = false; ItemType = itemType; } @@ -70,7 +70,16 @@ public IEntry ToBadEntry() public Uri Href { get; } - public List PublicLinks => new() {new PublicLinkInfo("linked", Href) }; + //public List PublicLinks => new() {new PublicLinkInfo("linked", Href) }; + public ConcurrentDictionary PublicLinks + { + get + { + ConcurrentDictionary result = new(StringComparer.InvariantCultureIgnoreCase); + result.TryAdd(Href.AbsoluteUri, new PublicLinkInfo("linked", Href)); + return result; + } + } public FileAttributes Attributes => FileAttributes.Normal; //TODO: dunno what to do diff --git a/MailRuCloud/MailRuCloudApi/Links/LinkManager.cs b/MailRuCloud/MailRuCloudApi/Links/LinkManager.cs index bdb83e11..49f4a5f7 100644 --- a/MailRuCloud/MailRuCloudApi/Links/LinkManager.cs +++ b/MailRuCloud/MailRuCloudApi/Links/LinkManager.cs @@ -173,11 +173,12 @@ public async Task RemoveDeadLinks(bool doWriteHistory) itl.IsBad || _cloud.GetItemAsync(itl.MapPath, Cloud.ItemType.Folder, false).Result == null) .ToList(); - if (removes.Count == 0) return 0; + if (removes.Count == 0) + return 0; _itemList.Items.RemoveAll(it => removes.Any(rem => WebDavPath.PathEquals(rem.MapPath, it.MapTo) && rem.Name == it.Name)); - if (!removes.Any()) + if (removes.Count == 0) return 0; if (doWriteHistory) @@ -198,7 +199,6 @@ public async Task RemoveDeadLinks(bool doWriteHistory) } Save(); return removes.Count; - } ///// diff --git a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/ListCommand.cs b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/ListCommand.cs index a94be2af..da15f26a 100644 --- a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/ListCommand.cs +++ b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/ListCommand.cs @@ -35,7 +35,7 @@ public override async Task Execute() foreach (var e in Flat(data, Cloud.LinkManager)) { string hash = (e as File)?.Hash.ToString() ?? "-"; - string link = e.PublicLinks.Any() ? e.PublicLinks.First().Uri.OriginalString : "-"; + string link = e.PublicLinks.Values.FirstOrDefault()?.Uri.OriginalString ?? "-"; sb.AppendLine( $"{e.FullPath}\t{e.Size.DefaultValue}\t{e.CreationTimeUtc:yyyy.MM.dd HH:mm:ss}\t{hash}\t{link}"); } diff --git a/MailRuCloud/MailRuCloudApi/Streams/UploadStreamFabric.cs b/MailRuCloud/MailRuCloudApi/Streams/UploadStreamFabric.cs index aecb4ad4..52d231e4 100644 --- a/MailRuCloud/MailRuCloudApi/Streams/UploadStreamFabric.cs +++ b/MailRuCloud/MailRuCloudApi/Streams/UploadStreamFabric.cs @@ -27,16 +27,17 @@ public async Task Create(File file, FileUploadedDelegate onUploaded = nu Stream stream; - bool cryptRequired = _cloud.IsFileExists(CryptFileInfo.FileName, WebDavPath.GetParents(folder.FullPath).ToList()).Any(); + bool cryptRequired = _cloud.IsFileExists(CryptFileInfo.FileName, WebDavPath.GetParents(folder.FullPath)); if (cryptRequired && !discardEncryption) { if (!_cloud.Account.Credentials.CanCrypt) throw new Exception($"Cannot upload {file.FullPath} to crypt folder without additional password!"); // #142 remove crypted file parts if size changed - var remoteFile = folder.Files.FirstOrDefault(f => f.FullPath == file.FullPath); - if (remoteFile != null) + if (folder.Files.TryGetValue(file.FullPath, out var remoteFile)) + { await _cloud.Remove(remoteFile); + } stream = GetCryptoStream(file, onUploaded); } diff --git a/WebDavMailRuCloudStore/StoreBase/LocalStoreCollectionProps.cs b/WebDavMailRuCloudStore/StoreBase/LocalStoreCollectionProps.cs index d1051e9b..81557210 100644 --- a/WebDavMailRuCloudStore/StoreBase/LocalStoreCollectionProps.cs +++ b/WebDavMailRuCloudStore/StoreBase/LocalStoreCollectionProps.cs @@ -121,10 +121,14 @@ public LocalStoreCollectionProps(Func isEnabledPropFunc) { Getter = (_, collection) => { - int files = collection.DirectoryInfo.NumberOfFiles; - int folders = collection.DirectoryInfo.NumberOfFolders; - return (folders > 0 ? folders : collection.DirectoryInfo.ServerFoldersCount) + - files > 0 ? files : collection.DirectoryInfo.ServerFilesCount ?? 0; + var info = collection.DirectoryInfo; + return (info.Folders.IsEmpty + ? (info.ServerFoldersCount ?? 0) + : info.Folders.Count) + + + (info.Files.IsEmpty + ? (info.ServerFilesCount ?? 0) + : info.Files.Count); } }, new DavExtCollectionIsFolder @@ -142,7 +146,7 @@ public LocalStoreCollectionProps(Func isEnabledPropFunc) new DavExtCollectionHasSubs //Identifies whether this collection contains any collections which are folders (see "isfolder"). { - Getter = (_, collection) => collection.DirectoryInfo.NumberOfFolders > 0 || collection.DirectoryInfo.ServerFoldersCount > 0 + Getter = (_, collection) => !collection.DirectoryInfo.Folders.IsEmpty || collection.DirectoryInfo.ServerFoldersCount > 0 }, new DavExtCollectionNoSubs //Identifies whether this collection allows child collections to be created. @@ -152,9 +156,9 @@ public LocalStoreCollectionProps(Func isEnabledPropFunc) new DavExtCollectionObjectCount //To count the number of non-folder resources in the collection. { - Getter = (_, collection) => - collection.DirectoryInfo.NumberOfFiles > 0 - ? collection.DirectoryInfo.NumberOfFiles + Getter = (_, collection) => + !collection.DirectoryInfo.Files.IsEmpty + ? collection.DirectoryInfo.Files.Count : collection.DirectoryInfo.ServerFilesCount ?? 0 }, @@ -165,9 +169,9 @@ public LocalStoreCollectionProps(Func isEnabledPropFunc) new DavExtCollectionVisibleCount //Counts the number of visible non-folder resources in the collection. { - Getter = (_, collection) => - collection.DirectoryInfo.NumberOfFiles > 0 - ? collection.DirectoryInfo.NumberOfFiles + Getter = (_, collection) => + !collection.DirectoryInfo.Files.IsEmpty + ? collection.DirectoryInfo.Files.Count : collection.DirectoryInfo.ServerFilesCount ?? 0 }, @@ -218,9 +222,7 @@ public LocalStoreCollectionProps(Func isEnabledPropFunc) }, new DavSharedLink { - Getter = (_, item) => !item.DirectoryInfo.PublicLinks.Any() - ? string.Empty - : item.DirectoryInfo.PublicLinks.First().Uri.OriginalString, + Getter = (_, item) => item.DirectoryInfo.PublicLinks.Values.FirstOrDefault()?.Uri.OriginalString ?? string.Empty, Setter = (_, _, _) => DavStatusCode.Ok } }; diff --git a/WebDavMailRuCloudStore/StoreBase/LocalStoreItemProps.cs b/WebDavMailRuCloudStore/StoreBase/LocalStoreItemProps.cs index 99a5e7ed..fef88dc3 100644 --- a/WebDavMailRuCloudStore/StoreBase/LocalStoreItemProps.cs +++ b/WebDavMailRuCloudStore/StoreBase/LocalStoreItemProps.cs @@ -130,9 +130,7 @@ public LocalStoreItemProps(Func isEnabledPropFunc) }, new DavSharedLink { - Getter = (_, item) => !item.FileInfo.PublicLinks.Any() - ? string.Empty - : item.FileInfo.PublicLinks.First().Uri.OriginalString, + Getter = (_, item) => item.FileInfo.PublicLinks.Values.FirstOrDefault()?.Uri.OriginalString ?? string.Empty, Setter = (_, _, _) => DavStatusCode.Ok } }; From 564cfc38a10c3a1b7b0a960c7b672e7b636d2e0d Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Sun, 15 Oct 2023 16:05:55 +0300 Subject: [PATCH 18/77] =?UTF-8?q?=D0=9F=D0=BE=D0=B4=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BF=D1=80=D0=B5=D0=B4=D1=83=D0=BF?= =?UTF-8?q?=D1=80=D0=B5=D0=B6=D0=B4=D0=B5=D0=BD=D0=B8=D0=B9=20'Uri.EscapeU?= =?UTF-8?q?riString(string)'=20is=20obsolete:=20'Uri.EscapeUriString=20can?= =?UTF-8?q?=20corrupt=20the=20Uri=20string=20in=20some=20cases=20=D0=B8?= =?UTF-8?q?=D0=BB=D0=B8=20WebRequest.Create=20is=20obsolete?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MailRuCloud/Mobile/Requests/OAuthSecondStepRequest.cs | 2 ++ .../Base/Repos/MailRuCloud/WebBin/Requests/DownloadRequest.cs | 4 +++- .../Base/Repos/MailRuCloud/WebM1/WebM1RequestRepo.cs | 4 +++- .../Base/Repos/MailRuCloud/WebV2/Requests/DownloadRequest.cs | 4 +++- .../Base/Repos/MailRuCloud/WebV2/Requests/LoginRequest.cs | 2 ++ .../Repos/MailRuCloud/WebV2/Requests/SecondStepAuthRequest.cs | 2 ++ .../Base/Repos/MailRuCloud/WebV2/Requests/UploadRequest.cs | 2 ++ .../YandexDisk/YadWeb/Requests/YadAuthPasswordRequest.cs | 2 ++ .../Repos/YandexDisk/YadWeb/Requests/YadDownloadRequest.cs | 4 +++- .../Base/Repos/YandexDisk/YadWeb/Requests/YadUploadRequest.cs | 2 ++ .../YandexDisk/YadWebV2/Requests/YadAuthPasswordRequest.cs | 2 ++ .../Repos/YandexDisk/YadWebV2/Requests/YadDownloadRequest.cs | 4 +++- .../Repos/YandexDisk/YadWebV2/Requests/YadUploadRequest.cs | 2 ++ MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs | 2 ++ 14 files changed, 33 insertions(+), 5 deletions(-) diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/OAuthSecondStepRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/OAuthSecondStepRequest.cs index c79afa6a..ac7682a6 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/OAuthSecondStepRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/OAuthSecondStepRequest.cs @@ -21,7 +21,9 @@ public OAuthSecondStepRequest(HttpCommonSettings settings, string login, string protected override byte[] CreateHttpContent() { +#pragma warning disable SYSLIB0013 // Type or member is obsolete var data = $"client_id={Settings.ClientId}&grant_type=password&username={Uri.EscapeUriString(_login)}&tsa_token={_tsaToken}&auth_code={_authCode}"; +#pragma warning restore SYSLIB0013 // Type or member is obsolete return Encoding.UTF8.GetBytes(data); } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebBin/Requests/DownloadRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebBin/Requests/DownloadRequest.cs index 2c791d0b..9f6531bf 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebBin/Requests/DownloadRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebBin/Requests/DownloadRequest.cs @@ -44,7 +44,9 @@ private static HttpWebRequest CreateRequest(HttpCommonSettings settings, var uri = new Uri(url); - HttpWebRequest request = (HttpWebRequest) WebRequest.Create(uri.OriginalString); +#pragma warning disable SYSLIB0014 // Type or member is obsolete + HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri.OriginalString); +#pragma warning restore SYSLIB0014 // Type or member is obsolete request.AllowAutoRedirect = true; diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/WebM1RequestRepo.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/WebM1RequestRepo.cs index 7aefc734..cf52b7a8 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/WebM1RequestRepo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/WebM1RequestRepo.cs @@ -92,7 +92,9 @@ CustomDisposable ResponseGenerator(long instart, long inend, Fi $"?client_id={HttpSettings.ClientId}&token={Authent.AccessToken}"; var uri = new Uri(url); - request = (HttpWebRequest) WebRequest.Create(uri.OriginalString); +#pragma warning disable SYSLIB0014 // Type or member is obsolete + request = (HttpWebRequest)WebRequest.Create(uri.OriginalString); +#pragma warning restore SYSLIB0014 // Type or member is obsolete request.AddRange(instart, inend); request.Proxy = HttpSettings.Proxy; diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/DownloadRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/DownloadRequest.cs index d5e9542a..70a1e899 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/DownloadRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/DownloadRequest.cs @@ -34,7 +34,9 @@ private static HttpWebRequest CreateRequest(IAuth authent, IWebProxy proxy, File ? $"{shard.Url}{Uri.EscapeDataString(file.FullPath)}" : $"{shard.Url}{file.PublicLinks.Values.FirstOrDefault()?.Uri.PathAndQuery.Remove(0, "/public".Length) ?? string.Empty}?key={downloadkey}"; - var request = (HttpWebRequest) WebRequest.Create(url); +#pragma warning disable SYSLIB0014 // Type or member is obsolete + var request = (HttpWebRequest)WebRequest.Create(url); +#pragma warning restore SYSLIB0014 // Type or member is obsolete request.Headers.Add("Accept-Ranges", "bytes"); request.AddRange(instart, inend); diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/LoginRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/LoginRequest.cs index f6eae210..4872d670 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/LoginRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/LoginRequest.cs @@ -26,7 +26,9 @@ protected override HttpWebRequest CreateRequest(string baseDomain = null) protected override byte[] CreateHttpContent() { +#pragma warning disable SYSLIB0013 // Type or member is obsolete string data = $"Login={Uri.EscapeUriString(Auth.Login)}&Domain={CommonSettings.Domain}&Password={Uri.EscapeUriString(Auth.Password)}"; +#pragma warning restore SYSLIB0013 // Type or member is obsolete return Encoding.UTF8.GetBytes(data); } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/SecondStepAuthRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/SecondStepAuthRequest.cs index 47c7b67a..f616e631 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/SecondStepAuthRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/SecondStepAuthRequest.cs @@ -19,7 +19,9 @@ public SecondStepAuthRequest(HttpCommonSettings settings, string csrf, string au protected override byte[] CreateHttpContent() { +#pragma warning disable SYSLIB0013 // Type or member is obsolete string data = $"csrf={_csrf}&Login={Uri.EscapeUriString(Auth.Login)}&AuthCode={_authCode}"; +#pragma warning restore SYSLIB0013 // Type or member is obsolete return Encoding.UTF8.GetBytes(data); } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/UploadRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/UploadRequest.cs index fce0e2bb..cd0d8562 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/UploadRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/UploadRequest.cs @@ -17,7 +17,9 @@ private static HttpWebRequest CreateRequest(string shardUrl, IAuth authent, IWeb { var url = new Uri($"{shardUrl}?cloud_domain=2&{authent.Login}"); +#pragma warning disable SYSLIB0014 // Type or member is obsolete var request = (HttpWebRequest)WebRequest.Create(url.OriginalString); +#pragma warning restore SYSLIB0014 // Type or member is obsolete request.Proxy = proxy; request.CookieContainer = authent.Cookies; request.Method = "PUT"; diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadAuthPasswordRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadAuthPasswordRequest.cs index c108aeec..fd7e2a2f 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadAuthPasswordRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadAuthPasswordRequest.cs @@ -50,6 +50,7 @@ protected override HttpWebRequest CreateRequest(string baseDomain = null) protected override byte[] CreateHttpContent() { +#pragma warning disable SYSLIB0013 // Type or member is obsolete var keyValues = new List> { new("csrf_token", Uri.EscapeUriString(_csrf)), @@ -57,6 +58,7 @@ protected override byte[] CreateHttpContent() new("password", Uri.EscapeUriString(_auth.Password)), new("retpath", Uri.EscapeUriString("https://disk.yandex.ru/client/disk")) }; +#pragma warning restore SYSLIB0013 // Type or member is obsolete var content = new FormUrlEncodedContent(keyValues); var d = content.ReadAsByteArrayAsync().Result; return d; diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadDownloadRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadDownloadRequest.cs index 7451571c..a3f3c0d0 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadDownloadRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadDownloadRequest.cs @@ -15,7 +15,9 @@ public YadDownloadRequest(HttpCommonSettings settings, IAuth authent, string url private static HttpWebRequest CreateRequest(IAuth authent, IWebProxy proxy, string url, long instart, long inend, string userAgent) { - var request = (HttpWebRequest) WebRequest.Create(url); +#pragma warning disable SYSLIB0014 // Type or member is obsolete + var request = (HttpWebRequest)WebRequest.Create(url); +#pragma warning restore SYSLIB0014 // Type or member is obsolete request.Headers.Add("Accept-Ranges", "bytes"); request.Headers.Add("Upgrade-Insecure-Requests", "1"); diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadUploadRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadUploadRequest.cs index 9954aa6b..d091f6e6 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadUploadRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadUploadRequest.cs @@ -15,7 +15,9 @@ public YadUploadRequest(HttpCommonSettings settings, YadWebAuth authent, string private HttpWebRequest CreateRequest(string url, YadWebAuth authent, IWebProxy proxy, long size, string userAgent) { +#pragma warning disable SYSLIB0014 // Type or member is obsolete var request = (HttpWebRequest)WebRequest.Create(url); +#pragma warning restore SYSLIB0014 // Type or member is obsolete request.Proxy = proxy; request.CookieContainer = authent.Cookies; request.Method = "PUT"; diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YadAuthPasswordRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YadAuthPasswordRequest.cs index 9061f924..0f0db900 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YadAuthPasswordRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YadAuthPasswordRequest.cs @@ -54,6 +54,7 @@ protected override HttpWebRequest CreateRequest(string baseDomain = null) protected override byte[] CreateHttpContent() { +#pragma warning disable SYSLIB0013 // Type or member is obsolete var keyValues = new List> { new("csrf_token", Uri.EscapeUriString(_csrf)), @@ -61,6 +62,7 @@ protected override byte[] CreateHttpContent() new("password", Uri.EscapeUriString(_auth.Password)), new("retpath", Uri.EscapeUriString("https://disk.yandex.ru/client/disk")) }; +#pragma warning restore SYSLIB0013 // Type or member is obsolete var content = new FormUrlEncodedContent(keyValues); var d = content.ReadAsByteArrayAsync().Result; return d; diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YadDownloadRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YadDownloadRequest.cs index 8a204d8e..40f6409b 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YadDownloadRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YadDownloadRequest.cs @@ -15,7 +15,9 @@ public YadDownloadRequest(HttpCommonSettings settings, IAuth authent, string url private static HttpWebRequest CreateRequest(IAuth authent, IWebProxy proxy, string url, long instart, long inend, string userAgent) { - var request = (HttpWebRequest) WebRequest.Create(url); +#pragma warning disable SYSLIB0014 // Type or member is obsolete + var request = (HttpWebRequest)WebRequest.Create(url); +#pragma warning restore SYSLIB0014 // Type or member is obsolete request.Headers.Add("Accept-Ranges", "bytes"); request.Headers.Add("Upgrade-Insecure-Requests", "1"); diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YadUploadRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YadUploadRequest.cs index 171307ce..608a6073 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YadUploadRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YadUploadRequest.cs @@ -15,7 +15,9 @@ public YadUploadRequest(HttpCommonSettings settings, YadWebAuth authent, string private HttpWebRequest CreateRequest(string url, YadWebAuth authent, IWebProxy proxy, long size, string userAgent) { +#pragma warning disable SYSLIB0014 // Type or member is obsolete var request = (HttpWebRequest)WebRequest.Create(url); +#pragma warning restore SYSLIB0014 // Type or member is obsolete request.Proxy = proxy; request.CookieContainer = authent.Cookies; request.Method = "PUT"; diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs index 914555a7..11043b11 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs @@ -34,7 +34,9 @@ protected virtual HttpWebRequest CreateRequest(string baseDomain = null) // https://stackoverflow.com/questions/20211496/uri-ignore-special-characters //var udriz = new Uri(new Uri(domain), RelationalUri, true); +#pragma warning disable SYSLIB0014 // Type or member is obsolete var request = (HttpWebRequest)WebRequest.Create(uriz); +#pragma warning restore SYSLIB0014 // Type or member is obsolete request.Proxy = Settings.Proxy; request.CookieContainer = Auth?.Cookies; request.Method = "GET"; From 9ad49f9f5323ff6ececbfaeb0bd4f42c471d4258 Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Sun, 15 Oct 2023 16:07:12 +0300 Subject: [PATCH 19/77] Code cleanup --- .../MailRuCloudApi/SpecialCommands/Commands/ListCommand.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/ListCommand.cs b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/ListCommand.cs index da15f26a..f642e6d3 100644 --- a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/ListCommand.cs +++ b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/ListCommand.cs @@ -13,7 +13,7 @@ public class ListCommand : SpecialCommand { //private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(FishCommand)); - public ListCommand(Cloud cloud, string path, IList parames) : base(cloud, path, parames) + public ListCommand(Cloud cloud, string path, IList parameters) : base(cloud, path, parameters) { } From 4a321c4ee5b65b2c33d850fc4c7ce1219e1b2dc0 Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Sun, 15 Oct 2023 17:52:03 +0300 Subject: [PATCH 20/77] =?UTF-8?q?=D0=9D=D0=B5=D0=B1=D0=BE=D0=BB=D1=8C?= =?UTF-8?q?=D1=88=D0=BE=D0=B5=20=D1=83=D1=81=D0=BA=D0=BE=D1=80=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=B7=D0=B0=20=D1=81=D1=87=D0=B5=D1=82=20"?= =?UTF-8?q?=D0=BE=D0=B4=D0=B8=D0=BD=20=D1=80=D0=B0=D0=B7=20=D0=BF=D0=BE?= =?UTF-8?q?=D1=81=D1=87=D0=B8=D1=82=D0=B0=D1=82=D1=8C,=20=D0=BC=D0=BD?= =?UTF-8?q?=D0=BE=D0=B3=D0=BE=20=D1=80=D0=B0=D0=B7=20=D0=B8=D1=81=D0=BF?= =?UTF-8?q?=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D1=82=D1=8C"=20?= =?UTF-8?q?=D0=B2=D0=BC=D0=B5=D1=81=D1=82=D0=BE=20=D0=BF=D0=BE=D1=81=D1=82?= =?UTF-8?q?=D0=BE=D1=8F=D0=BD=D0=BD=D0=BE=D0=B3=D0=BE=20=D0=BF=D0=BE=D0=B4?= =?UTF-8?q?=D1=81=D1=87=D0=B5=D1=82=D0=B0.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MailRuCloud/MailRuCloudApi/Base/File.cs | 6 ++---- MailRuCloud/MailRuCloudApi/Base/Folder.cs | 8 +++----- MailRuCloud/MailRuCloudApi/Base/SplittedFile.cs | 4 ++-- MailRuCloud/MailRuCloudApi/Cloud.cs | 2 +- MailRuCloud/MailRuCloudApi/Links/Link.cs | 3 ++- WebDavMailRuCloudStore/StoreBase/LocalStoreCollection.cs | 3 ++- 6 files changed, 12 insertions(+), 14 deletions(-) diff --git a/MailRuCloud/MailRuCloudApi/Base/File.cs b/MailRuCloud/MailRuCloudApi/Base/File.cs index 16c7a348..544641e5 100644 --- a/MailRuCloud/MailRuCloudApi/Base/File.cs +++ b/MailRuCloud/MailRuCloudApi/Base/File.cs @@ -112,6 +112,7 @@ protected set { _fullPath = WebDavPath.Clean(value); Name = WebDavPath.Name(_fullPath); + Path = WebDavPath.Parent(_fullPath); } } @@ -120,7 +121,7 @@ protected set /// /// Path to file (without filename) /// - public string Path => WebDavPath.Parent(FullPath); + public string Path { get; private set; } /// /// Gets public file link. @@ -229,8 +230,5 @@ private static string ConvertToVideoLink(Cloud cloud, Uri publicLink, SharedVide //Base64Encode(publicLink.TrimStart('/')) + //".m3u8?double_encode=1"; } - - } } - diff --git a/MailRuCloud/MailRuCloudApi/Base/Folder.cs b/MailRuCloud/MailRuCloudApi/Base/Folder.cs index 07fdaacd..407de3ef 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Folder.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Folder.cs @@ -20,6 +20,7 @@ public class Folder : IEntry, ICanForget public Folder(string fullPath) { FullPath = WebDavPath.Clean(fullPath); + Name = WebDavPath.Name(FullPath); } /// @@ -61,7 +62,7 @@ public IEnumerable Entries /// Gets folder name. /// /// Folder name. - public string Name => WebDavPath.Name(FullPath); + public string Name { get; } /// /// Gets folder size. @@ -73,10 +74,7 @@ public IEnumerable Entries /// Gets full folder path on the server. /// /// Full folder path. - public string FullPath - { - get; - } + public string FullPath { get; } /// diff --git a/MailRuCloud/MailRuCloudApi/Base/SplittedFile.cs b/MailRuCloud/MailRuCloudApi/Base/SplittedFile.cs index ec46f1e6..23a4950c 100644 --- a/MailRuCloud/MailRuCloudApi/Base/SplittedFile.cs +++ b/MailRuCloud/MailRuCloudApi/Base/SplittedFile.cs @@ -52,9 +52,9 @@ public SplittedFile(IList files) public override IList Files { get; } - public override File New(string newfullPath) + public override File New(string newFullPath) { - string path = WebDavPath.Parent(newfullPath); + string path = WebDavPath.Parent(newFullPath); var flist = Files .Select(f => f.New(WebDavPath.Combine(path, f.Name))) diff --git a/MailRuCloud/MailRuCloudApi/Cloud.cs b/MailRuCloud/MailRuCloudApi/Cloud.cs index e63c8751..66d3d0ee 100644 --- a/MailRuCloud/MailRuCloudApi/Cloud.cs +++ b/MailRuCloud/MailRuCloudApi/Cloud.cs @@ -809,7 +809,7 @@ private async Task Remove(string fullPath) var res = await Account.RequestRepo.Remove(fullPath); if (!res.IsSuccess) - return res.IsSuccess; + return false; //remove inner links if (LinkManager != null) diff --git a/MailRuCloud/MailRuCloudApi/Links/Link.cs b/MailRuCloud/MailRuCloudApi/Links/Link.cs index d018b69f..46938c51 100644 --- a/MailRuCloud/MailRuCloudApi/Links/Link.cs +++ b/MailRuCloud/MailRuCloudApi/Links/Link.cs @@ -20,6 +20,7 @@ public Link(ItemLink rootLink, string fullPath, Uri href) : this(href) { _rootLink = rootLink; FullPath = fullPath; + Name = WebDavPath.Name(fullPath); IsRoot = WebDavPath.PathEquals(WebDavPath.Parent(FullPath), _rootLink.MapTo); @@ -40,7 +41,7 @@ public Link(ItemLink rootLink, string fullPath, Uri href) : this(href) public string OriginalName { get; set; } - public string Name => WebDavPath.Name(FullPath); + public string Name { get; } public Cloud.ItemType ItemType { get; set; } diff --git a/WebDavMailRuCloudStore/StoreBase/LocalStoreCollection.cs b/WebDavMailRuCloudStore/StoreBase/LocalStoreCollection.cs index f16d5937..4a44ca13 100644 --- a/WebDavMailRuCloudStore/StoreBase/LocalStoreCollection.cs +++ b/WebDavMailRuCloudStore/StoreBase/LocalStoreCollection.cs @@ -273,7 +273,8 @@ public async Task DeleteItemAsync(string name, IHttpContext httpC { var item = FindSubItem(name); - if (null == item) return DavStatusCode.NotFound; + if (item == null) + return DavStatusCode.NotFound; var cloud = CloudManager.Instance(httpContext.Session.Principal.Identity); bool res = await cloud.Remove(item); From 3c34ef3eed1051751d5181723e35c10ea0417bc9 Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Sun, 15 Oct 2023 17:56:32 +0300 Subject: [PATCH 21/77] WebView2 is updated --- YandexAuthBrowser/YandexAuthBrowser.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/YandexAuthBrowser/YandexAuthBrowser.csproj b/YandexAuthBrowser/YandexAuthBrowser.csproj index aa424d5e..af2b9e9f 100644 --- a/YandexAuthBrowser/YandexAuthBrowser.csproj +++ b/YandexAuthBrowser/YandexAuthBrowser.csproj @@ -22,7 +22,7 @@ - + From 8c7883874873726b3cc83fac579e8787144e8b6d Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Sun, 15 Oct 2023 17:57:21 +0300 Subject: [PATCH 22/77] System.Text.Json is updated --- YandexAuthBrowser/YandexAuthBrowser.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/YandexAuthBrowser/YandexAuthBrowser.csproj b/YandexAuthBrowser/YandexAuthBrowser.csproj index af2b9e9f..1e1fa84e 100644 --- a/YandexAuthBrowser/YandexAuthBrowser.csproj +++ b/YandexAuthBrowser/YandexAuthBrowser.csproj @@ -24,7 +24,7 @@ - + \ No newline at end of file From 96c51ab147cec1b9ab80abc3d1955e1fd4dec4c5 Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Sun, 15 Oct 2023 17:58:19 +0300 Subject: [PATCH 23/77] WinServiceInstaller is updated --- WinServiceInstaller/WinServiceInstaller.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WinServiceInstaller/WinServiceInstaller.csproj b/WinServiceInstaller/WinServiceInstaller.csproj index ae915e3a..ea75c1e6 100644 --- a/WinServiceInstaller/WinServiceInstaller.csproj +++ b/WinServiceInstaller/WinServiceInstaller.csproj @@ -47,7 +47,7 @@ - + From 33f811ba744f7bafc5a9b9923e93afb3b8ed3550 Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Sun, 15 Oct 2023 18:01:29 +0300 Subject: [PATCH 24/77] IndexRange is updated --- MailRuCloud/MailRuCloudApi/YaR.Clouds.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MailRuCloud/MailRuCloudApi/YaR.Clouds.csproj b/MailRuCloud/MailRuCloudApi/YaR.Clouds.csproj index 0f17ffa1..20fc9608 100644 --- a/MailRuCloud/MailRuCloudApi/YaR.Clouds.csproj +++ b/MailRuCloud/MailRuCloudApi/YaR.Clouds.csproj @@ -29,7 +29,7 @@ - + From abd6dd2621e117f7c8a3719036fa983afb75b9f0 Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Sun, 15 Oct 2023 18:34:52 +0300 Subject: [PATCH 25/77] =?UTF-8?q?=D0=9C=D0=B5=D0=BB=D0=BA=D0=B8=D0=B5=20?= =?UTF-8?q?=D0=BF=D1=80=D0=B0=D0=B2=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- WDMRC.Console/CommandLineOptions.cs | 4 ++-- WDMRC.Console/WDMRC.Console.csproj | 1 + WebDAV.Uploader/WDMRC.Console.Client.csproj | 1 + 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/WDMRC.Console/CommandLineOptions.cs b/WDMRC.Console/CommandLineOptions.cs index 5b56e422..45b77d17 100644 --- a/WDMRC.Console/CommandLineOptions.cs +++ b/WDMRC.Console/CommandLineOptions.cs @@ -53,8 +53,8 @@ class CommandLineOptions [Option("cache-listing", Default = 30, HelpText = "Cache folders listing, sec")] public int CacheListingSec { get; set; } - [Option("cache-listing-depth", Default = 1, HelpText = "List query folder depth, always equals 1 when cache-listing>0")] - public int CacheListingDepth { get; set; } + [Option("cache-listing-depth", Default = 1, HelpText = "List query folder depth, always equals 1 when cache-listing>0")] + public int CacheListingDepth { get; set; } [Option("proxy-address", Default = "", HelpText = "Proxy address i.e. http://192.168.1.1:8080")] public string ProxyAddress { get; set; } diff --git a/WDMRC.Console/WDMRC.Console.csproj b/WDMRC.Console/WDMRC.Console.csproj index 7be6201d..bb51b97e 100644 --- a/WDMRC.Console/WDMRC.Console.csproj +++ b/WDMRC.Console/WDMRC.Console.csproj @@ -26,6 +26,7 @@ https://github.com/yar229/WebDavMailRuCloud readme.md False + WebDAV emulator for Cloud.mail.ru / Yandex.Disk diff --git a/WebDAV.Uploader/WDMRC.Console.Client.csproj b/WebDAV.Uploader/WDMRC.Console.Client.csproj index c79591b4..b9b868f5 100644 --- a/WebDAV.Uploader/WDMRC.Console.Client.csproj +++ b/WebDAV.Uploader/WDMRC.Console.Client.csproj @@ -11,6 +11,7 @@ YaR.CloudMailRu.Client.Console $(CommonLangVersion) False + $(ReleaseVersion) From d2463c015ca63464dde968a18884b60163075e50 Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Sun, 15 Oct 2023 18:36:18 +0300 Subject: [PATCH 26/77] =?UTF-8?q?=D0=9E=D0=BF=D0=B5=D1=87=D0=B0=D1=82?= =?UTF-8?q?=D0=BA=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Base/Repos/YandexDisk/YadWeb/Models/Base.cs | 2 +- .../YandexDisk/YadWeb/Requests/YaDCommonRequest.cs | 8 ++++---- .../Base/Repos/YandexDisk/YadWebV2/Models/Base.cs | 2 +- .../YandexDisk/YadWebV2/Requests/YaDCommonRequest.cs | 10 +++++----- 4 files changed, 11 insertions(+), 11 deletions(-) diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/Base.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/Base.cs index 9ac81498..ec5432d7 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/Base.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/Base.cs @@ -42,7 +42,7 @@ public virtual IEnumerable> ToKvp(int index) - public class YadResponceResult + public class YadResponseResult { [JsonProperty("uid")] public long Uid { get; set; } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YaDCommonRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YaDCommonRequest.cs index 09a1db19..f0912363 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YaDCommonRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YaDCommonRequest.cs @@ -9,7 +9,7 @@ namespace YaR.Clouds.Base.Repos.YandexDisk.YadWeb.Requests { - class YaDCommonRequest : BaseRequestJson + class YaDCommonRequest : BaseRequestJson { //private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(YaDCommonRequest)); @@ -53,17 +53,17 @@ public YaDCommonRequest With(T model, out TOut resOUt) .Select(m => m.Name) .Aggregate((current, next) => current + "," + next); - protected override RequestResponse DeserializeMessage(NameValueCollection responseHeaders, System.IO.Stream stream) + protected override RequestResponse DeserializeMessage(NameValueCollection responseHeaders, System.IO.Stream stream) { using var sr = new StreamReader(stream); string text = sr.ReadToEnd(); //Logger.Debug(text); - var msg = new RequestResponse + var msg = new RequestResponse { Ok = true, - Result = JsonConvert.DeserializeObject(text, new KnownYadModelConverter(_outData)) + Result = JsonConvert.DeserializeObject(text, new KnownYadModelConverter(_outData)) }; return msg; } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/Base.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/Base.cs index e256283f..f022b7f1 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/Base.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/Base.cs @@ -42,7 +42,7 @@ public virtual IEnumerable> ToKvp(int index) - public class YadResponceResult + public class YadResponseResult { [JsonProperty("uid")] public long Uid { get; set; } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YaDCommonRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YaDCommonRequest.cs index 07a894e5..392f4872 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YaDCommonRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YaDCommonRequest.cs @@ -9,7 +9,7 @@ namespace YaR.Clouds.Base.Repos.YandexDisk.YadWebV2.Requests { - class YaDCommonRequest : BaseRequestJson + class YaDCommonRequest : BaseRequestJson { private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(YaDCommonRequest)); @@ -53,7 +53,7 @@ public YaDCommonRequest With(T model, out TOut resOUt) .Select(m => m.Name) .Aggregate((current, next) => current + "," + next); - protected override RequestResponse DeserializeMessage( + protected override RequestResponse DeserializeMessage( NameValueCollection responseHeaders, System.IO.Stream stream) { using var sr = new StreamReader(stream); @@ -61,10 +61,10 @@ protected override RequestResponse DeserializeMessage( string text = sr.ReadToEnd(); //Logger.Debug(text); - var msg = new RequestResponse + var msg = new RequestResponse { Ok = true, - Result = JsonConvert.DeserializeObject( + Result = JsonConvert.DeserializeObject( text, new KnownYadModelConverter(_outData)) }; if (msg.Result.Models != null && @@ -82,4 +82,4 @@ protected override RequestResponse DeserializeMessage( return msg; } } -} \ No newline at end of file +} From 840b7ce0471ace5832316f62d2af5fb32f7c1257 Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Sun, 15 Oct 2023 18:41:21 +0300 Subject: [PATCH 27/77] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20sk=20=D0=BF=D0=BE=D0=BB=D1=83=D1=87?= =?UTF-8?q?=D0=B5=D0=BD=D0=BD=D1=8B=D0=BC=20=D0=B7=D0=BD=D0=B0=D1=87=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=D0=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../YandexDisk/YadWebV2/Requests/YaDCommonRequest.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YaDCommonRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YaDCommonRequest.cs index 392f4872..64a951f8 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YaDCommonRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YaDCommonRequest.cs @@ -67,6 +67,16 @@ protected override RequestResponse DeserializeMessage( Result = JsonConvert.DeserializeObject( text, new KnownYadModelConverter(_outData)) }; + + //Logger.Debug($"_postData.Sk={_postData?.Sk} | Result.sk={msg.Result?.Sk}"); + /* + * Строка sk выглядит так: "sk": "cdc3dee74a379c1adc792ef087cf8c9ba19ca9f5:1693681795" + * Правая часть содержит время после двоеточия - количество секунд, начиная с 01.01.1970. + * Обновляем sk полученным значением sk. + */ + if (!string.IsNullOrWhiteSpace(msg.Result?.Sk)) + YadAuth.DiskSk = msg.Result.Sk; + if (msg.Result.Models != null && msg.Result.Models.Any(m => m.Error != null)) { From a7565eef9f9bc54b2ae5f872ec20433e99cb5ded Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Mon, 16 Oct 2023 16:34:43 +0300 Subject: [PATCH 28/77] =?UTF-8?q?=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D1=8F=20.NET=20=D0=BF=D0=BE=D1=81=D0=BB?= =?UTF-8?q?=D0=B5=D0=B4=D0=BD=D0=B8=D1=85=20=D0=B2=D0=B5=D1=80=D1=81=D0=B8?= =?UTF-8?q?=D0=B9=20=D0=BF=D0=B5=D1=80=D0=B5=D0=B8=D1=81=D0=BF=D0=BE=D0=BB?= =?UTF-8?q?=D1=8C=D0=B7=D1=83=D0=B5=D1=82=20=D1=81=D0=BE=D0=B5=D0=B4=D0=B8?= =?UTF-8?q?=D0=BD=D0=B5=D0=BD=D0=B8=D1=8F=20=D1=81=20=D1=81=D0=B5=D1=80?= =?UTF-8?q?=D0=B2=D0=B5=D1=80=D0=B0=D0=BC=D0=B8,=20=D0=B8=D0=B7-=D0=B7?= =?UTF-8?q?=D0=B0=20=D1=87=D0=B5=D0=B3=D0=BE=20=D0=B8=D0=BD=D0=BE=D0=B3?= =?UTF-8?q?=D0=B4=D0=B0=20=D1=81=D0=BB=D1=83=D1=87=D0=B0=D1=8E=D1=82=D1=81?= =?UTF-8?q?=D1=8F=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D0=B8,=20Framework=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D1=81=D1=82=D0=BE=20=D0=BA=D0=B8=D0=B4=D0=B0?= =?UTF-8?q?=D0=B5=D1=82=20=D0=B8=D1=81=D0=BA=D0=BB=D1=8E=D1=87=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5,=20=D1=80=D0=B0=D1=81=D1=81=D1=87=D0=B8=D1=82?= =?UTF-8?q?=D1=8B=D0=B2=D0=B0=D1=8F,=20=D1=87=D1=82=D0=BE=20=D0=B7=D0=B0?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D1=81=20=D0=B1=D1=83=D0=B4=D0=B5=D1=82=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=B2=D1=82=D0=BE=D1=80=D0=B5=D0=BD.=20=D0=A1?= =?UTF-8?q?=D0=BE=D0=BE=D1=82=D0=B2=D0=B5=D1=82=D1=81=D1=82=D0=B2=D0=B5?= =?UTF-8?q?=D0=BD=D0=BD=D0=BE,=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=BE=20=D0=BF=D0=BE=D0=B2=D1=82=D0=BE=D1=80=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D0=B5=20=D0=BE=D0=B1=D1=80=D0=B0=D1=89=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=20=D0=BA=20=D1=81=D0=B5=D1=80=D0=B2=D0=B5=D1=80?= =?UTF-8?q?=D1=83=20=D0=B2=20=D1=82=D0=B0=D0=BA=D0=BE=D0=BC=20=D1=81=D0=BB?= =?UTF-8?q?=D1=83=D1=87=D0=B0=D0=B5.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Base/Requests/BaseRequest.cs | 199 ++++++++++++------ 1 file changed, 139 insertions(+), 60 deletions(-) diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs index 11043b11..f82d97cc 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs @@ -35,18 +35,23 @@ protected virtual HttpWebRequest CreateRequest(string baseDomain = null) //var udriz = new Uri(new Uri(domain), RelationalUri, true); #pragma warning disable SYSLIB0014 // Type or member is obsolete - var request = (HttpWebRequest)WebRequest.Create(uriz); + var request = WebRequest.CreateHttp(uriz); #pragma warning restore SYSLIB0014 // Type or member is obsolete + request.Host = uriz.Host; request.Proxy = Settings.Proxy; request.CookieContainer = Auth?.Cookies; request.Method = "GET"; request.ContentType = ConstSettings.DefaultRequestType; request.Accept = "application/json"; request.UserAgent = Settings.UserAgent; - request.ContinueTimeout = Settings.CloudSettings.Wait100ContinueTimeoutMs; - request.Timeout = Settings.CloudSettings.WaitResponseTimeoutMs; - request.ReadWriteTimeout = Settings.CloudSettings.ReadWriteTimeoutMs; - + request.ContinueTimeout = Settings.CloudSettings.Wait100ContinueTimeoutSec; + request.Timeout = Settings.CloudSettings.WaitResponseTimeoutSec; + request.ReadWriteTimeout = Settings.CloudSettings.ReadWriteTimeoutSec; + request.AllowWriteStreamBuffering = false; + request.AllowReadStreamBuffering = true; + request.SendChunked = false; + request.ServicePoint.Expect100Continue = false; + request.KeepAlive = true; #if NET48 request.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip; @@ -65,77 +70,151 @@ protected virtual byte[] CreateHttpContent() } + private const int MaxRetryCount = 10; + public virtual async Task MakeRequestAsync() { - Stopwatch watch = new Stopwatch(); - watch.Start(); + /* + * По всей видимости, при нескольких последовательных обращениях к серверу + * инфраструктура .NET повторно использует подключение после его закрытие по Close. + * По этой причине может получиться так, что поток, который предназначен для чтения + * данных с сервера, читает до того, как данные отправлены на сервер (как вариант), + * или (как вариант) до того, как сервер начал отправку данных клиенту. + * В таком случае поток читает 0 байт и выдает ошибку + * throw new HttpIOException(HttpRequestError.ResponseEnded, SR.net_http_invalid_response_premature_eof); + * см. код сборки .NET здесь: + * https://github.com/dotnet/runtime/blob/139f45e56b85b7e643d7e4f81cb5cdf640cd9021/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnection.cs#L625 + * Судя по сообщениям в интернете, такое поведение многим не нравится, но ситуация не исправляется. + * Учитывая, что рядом с исключением устанавливается _canRetry = true, + * можно предположить, что предполагается повторение обращения к серверу, + * что мы тут и сделаем. + */ + + Stopwatch totalWatch = Stopwatch.StartNew(); + int retry = MaxRetryCount; + while (true) + { + retry--; + bool isRetryState = false; + Stopwatch watch = Stopwatch.StartNew(); + HttpWebRequest httpRequest = null; + + try + { + httpRequest = CreateRequest(); - var httpRequest = CreateRequest(); + var requestContent = CreateHttpContent(); + if (requestContent != null) + { + httpRequest.Method = "POST"; + using Stream requestStream = await httpRequest.GetRequestStreamAsync().ConfigureAwait(false); - var content = CreateHttpContent(); - if (content != null) - { - httpRequest.Method = "POST"; - httpRequest.AllowWriteStreamBuffering = false; - using Stream requestStream = await httpRequest.GetRequestStreamAsync().ConfigureAwait(false); - /* - * The debug add the following to a watch list: - * System.Text.Encoding.UTF8.GetString(content) - */ + /* + * The debug add the following to a watch list: + * System.Text.Encoding.UTF8.GetString(content) + */ #if NET48 - await requestStream.WriteAsync(content, 0, content.Length).ConfigureAwait(false); + await requestStream.WriteAsync(requestContent, 0, requestContent.Length).ConfigureAwait(false); #else - await requestStream.WriteAsync(content).ConfigureAwait(false); + await requestStream.WriteAsync(requestContent).ConfigureAwait(false); #endif - await requestStream.FlushAsync().ConfigureAwait(false); - requestStream.Close(); - } - try - { - using var response = (HttpWebResponse)await httpRequest.GetResponseAsync().ConfigureAwait(false); + await requestStream.FlushAsync().ConfigureAwait(false); + requestStream.Close(); + } + + /* + * Здесь в методе GetResponseAsync() иногда происходит исключение + * throw new HttpIOException(HttpRequestError.ResponseEnded, SR.net_http_invalid_response_premature_eof); + * Мы его отлавливаем и повторяем обращение к серверу. + */ + using var response = (HttpWebResponse)await httpRequest.GetResponseAsync().ConfigureAwait(false); + + if ((int)response.StatusCode >= 500) + { + throw new RequestException("Server fault") + { + StatusCode = response.StatusCode + }; + } + + RequestResponse result; + using (var responseStream = response.GetResponseStream()) + { + result = DeserializeMessage(response.Headers, Transport(responseStream)); + responseStream.Close(); + } - if ((int)response.StatusCode >= 500) + if (!result.Ok || response.StatusCode != HttpStatusCode.OK) + { + var exceptionMessage = + $"Request failed (status code {(int)response.StatusCode}): {result.Description}"; + throw new RequestException(exceptionMessage) + { + StatusCode = response.StatusCode, + ResponseBody = string.Empty, + Description = result.Description, + ErrorCode = result.ErrorCode + }; + } + + response.Close(); + + return result.Result; + } + catch (WebException iex2) when (iex2?.InnerException is System.Net.Http.HttpRequestException iex1 && + iex1?.InnerException is IOException iex) { - throw new RequestException("Server fault") + /* + * Здесь мы ловим ошибку + * throw new HttpIOException(HttpRequestError.ResponseEnded, SR.net_http_invalid_response_premature_eof), + * которая здесь выглядит следующим образом: + * System.AggregateException: One or more errors occurred. (An error occurred while sending the request.) + * ---> System.Net.WebException: An error occurred while sending the request. + * ---> System.Net.Http.HttpRequestException: An error occurred while sending the request. + * ---> System.IO.IOException: The response ended prematurely. + * at System.Net.Http.HttpConnection.SendAsyncCore(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken) + * --- End of inner exception stack trace --- + */ + if (retry <= 0) { - StatusCode = response.StatusCode - }; + string msg = "The response ended prematurely, retry count completed"; +#if DEBUG + Logger.Warn(msg); +#else + Logger.Debug(msg); +#endif + throw; + } + else + { + isRetryState = true; + string msg = "The response ended prematurely, retrying..."; +#if DEBUG + Logger.Warn(msg); +#else + Logger.Debug(msg); +#endif + } } - - RequestResponse result; - using (var responseStream = response.GetResponseStream()) + // ReSharper disable once RedundantCatchClause +#pragma warning disable 168 + catch (Exception ex) +#pragma warning restore 168 { - result = DeserializeMessage(response.Headers, Transport(responseStream)); - responseStream.Close(); + throw; } - - if (!result.Ok || response.StatusCode != HttpStatusCode.OK) + finally { - var exceptionMessage = - $"Request failed (status code {(int)response.StatusCode}): {result.Description}"; - throw new RequestException(exceptionMessage) + watch.Stop(); + string totalText = null; + if (!isRetryState && retry < MaxRetryCount - 1) { - StatusCode = response.StatusCode, - ResponseBody = string.Empty, - Description = result.Description, - ErrorCode = result.ErrorCode - }; + totalWatch.Stop(); + totalText = $"({totalWatch.Elapsed.Milliseconds} ms of {MaxRetryCount - retry} retry laps)"; + } + Logger.Debug($"HTTP:{httpRequest.Method}:{httpRequest.RequestUri.AbsoluteUri} " + + $"({watch.Elapsed.Milliseconds} ms){(isRetryState ? ", retrying" : totalText)}"); } - var retVal = result.Result; - - return retVal; - } - // ReSharper disable once RedundantCatchClause -#pragma warning disable 168 - catch (Exception ex) -#pragma warning restore 168 - { - throw; - } - finally - { - watch.Stop(); - Logger.Debug($"HTTP:{httpRequest.Method}:{httpRequest.RequestUri.AbsoluteUri} ({watch.Elapsed.Milliseconds} ms)"); } } From da3de045cd06b297f3eac3bd1b4c887e2eb8ccde Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Mon, 16 Oct 2023 16:46:22 +0300 Subject: [PATCH 29/77] =?UTF-8?q?=D0=9F=D0=B0=D1=80=D0=B0=D0=BC=D0=B5?= =?UTF-8?q?=D1=82=D1=80=D1=8B=20=D1=82=D0=B0=D0=B9=D0=BC=D0=B0=D1=83=D1=82?= =?UTF-8?q?=D0=BE=D0=B2=20=D0=BF=D0=B5=D1=80=D0=B5=D0=B4=D0=B5=D0=BB=D0=B0?= =?UTF-8?q?=D0=BD=D1=8B=20=D1=81=20=D0=BC=D0=B8=D0=BB=D0=BB=D0=B8=D1=81?= =?UTF-8?q?=D0=B5=D0=BA=D1=83=D0=BD=D0=B4=20=D0=BD=D0=B0=20=D1=81=D0=B5?= =?UTF-8?q?=D0=BA=D1=83=D0=BD=D0=B4=D1=8B,=20=D0=B8=D0=BD=D0=B0=D1=87?= =?UTF-8?q?=D0=B5=20=D0=BF=D1=83=D1=82=D0=B0=D0=BD=D0=B8=D1=86=D0=B0=20?= =?UTF-8?q?=D0=B2=20=D0=B1=D0=BE=D0=BB=D1=8C=D1=88=D0=BE=D0=BC=20=D0=BA?= =?UTF-8?q?=D0=BE=D0=BB=D0=B8=D1=87=D0=B5=D1=81=D1=82=D0=B2=D0=B5=20=D0=BD?= =?UTF-8?q?=D1=83=D0=BB=D0=B5=D0=B9.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MailRuCloudApi/Base/Requests/BaseRequest.cs | 6 +++--- MailRuCloud/MailRuCloudApi/CloudSettings.cs | 6 +++--- WDMRC.Console/CommandLineOptions.cs | 12 ++++++------ WDMRC.Console/Payload.cs | 9 ++++++--- 4 files changed, 18 insertions(+), 15 deletions(-) diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs index f82d97cc..e2537885 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs @@ -44,9 +44,9 @@ protected virtual HttpWebRequest CreateRequest(string baseDomain = null) request.ContentType = ConstSettings.DefaultRequestType; request.Accept = "application/json"; request.UserAgent = Settings.UserAgent; - request.ContinueTimeout = Settings.CloudSettings.Wait100ContinueTimeoutSec; - request.Timeout = Settings.CloudSettings.WaitResponseTimeoutSec; - request.ReadWriteTimeout = Settings.CloudSettings.ReadWriteTimeoutSec; + request.ContinueTimeout = Settings.CloudSettings.Wait100ContinueTimeoutSec * 1000; + request.Timeout = Settings.CloudSettings.WaitResponseTimeoutSec * 1000; + request.ReadWriteTimeout = Settings.CloudSettings.ReadWriteTimeoutSec * 1000; request.AllowWriteStreamBuffering = false; request.AllowReadStreamBuffering = true; request.SendChunked = false; diff --git a/MailRuCloud/MailRuCloudApi/CloudSettings.cs b/MailRuCloud/MailRuCloudApi/CloudSettings.cs index b463bc53..77030594 100644 --- a/MailRuCloud/MailRuCloudApi/CloudSettings.cs +++ b/MailRuCloud/MailRuCloudApi/CloudSettings.cs @@ -36,9 +36,9 @@ public int ListDepth public bool DisableLinkManager { get; set; } - public int Wait100ContinueTimeoutMs { get; set; } - public int WaitResponseTimeoutMs { get; set; } - public int ReadWriteTimeoutMs { get; set; } + public int Wait100ContinueTimeoutSec { get; set; } + public int WaitResponseTimeoutSec { get; set; } + public int ReadWriteTimeoutSec { get; set; } #region BrowserAuthenticator diff --git a/WDMRC.Console/CommandLineOptions.cs b/WDMRC.Console/CommandLineOptions.cs index 45b77d17..ddf2f182 100644 --- a/WDMRC.Console/CommandLineOptions.cs +++ b/WDMRC.Console/CommandLineOptions.cs @@ -72,13 +72,13 @@ class CommandLineOptions [Option("disable-links", Required = false, Default = false, HelpText = "Disable support for shared folder and stop using /item.links.wdmrc")] public bool DisableLinkManager { get; set; } - [Option("100-continue-timeout-ms", Required = false, Default = 350, HelpText = "Timeout in milliseconds, to wait until the 100-Continue is received")] - public int Wait100ContinueTimeoutMs { get; set; } + [Option("100-continue-timeout-sec", Required = false, Default = 1, HelpText = "Timeout in seconds, to wait until the 100-Continue is received")] + public int Wait100ContinueTimeoutSec { get; set; } - [Option("response-timeout-ms", Required = false, Default = 100_000, HelpText = "Timeout in milliseconds, to wait until 1-st byte from server is received")] - public int WaitResponseTimeoutMs { get; set; } + [Option("response-timeout-sec", Required = false, Default = 100, HelpText = "Timeout in seconds, to wait until 1-st byte from server is received")] + public int WaitResponseTimeoutSec { get; set; } - [Option("read-write-timeout-ms", Required = false, Default = 300_000, HelpText = "Timeout in milliseconds, the maximum duration of read or write operation")] - public int ReadWriteTimeoutMs { get; set; } + [Option("read-write-timeout-sec", Required = false, Default = 300, HelpText = "Timeout in seconds, the maximum duration of read or write operation")] + public int ReadWriteTimeoutSec { get; set; } } } diff --git a/WDMRC.Console/Payload.cs b/WDMRC.Console/Payload.cs index 9ec55259..56f17e26 100644 --- a/WDMRC.Console/Payload.cs +++ b/WDMRC.Console/Payload.cs @@ -61,9 +61,9 @@ public static void Run(CommandLineOptions options) DisableLinkManager = options.DisableLinkManager, - Wait100ContinueTimeoutMs = options.Wait100ContinueTimeoutMs, - WaitResponseTimeoutMs = options.WaitResponseTimeoutMs, - ReadWriteTimeoutMs = options.ReadWriteTimeoutMs, + Wait100ContinueTimeoutSec = options.Wait100ContinueTimeoutSec, + WaitResponseTimeoutSec = options.WaitResponseTimeoutSec, + ReadWriteTimeoutSec = options.ReadWriteTimeoutSec, BrowserAuthenticatorUrl = Config.BrowserAuthenticator?.Url, BrowserAuthenticatorPassword = Config.BrowserAuthenticator?.Password, @@ -213,6 +213,9 @@ private static void ShowInfo(CommandLineOptions options) Logger.Info($"Version: {version}"); Logger.Info($"Using proxy: {options.ProxyAddress}"); Logger.Info($"Max threads count: {options.MaxThreadCount}"); + Logger.Info($"Cloud server response timeout: {options.WaitResponseTimeoutSec} sec"); + Logger.Info($"Cloud download/upload timeout: {options.ReadWriteTimeoutSec} sec"); + Logger.Info($"Wait for 100-Continue timeout: {options.Wait100ContinueTimeoutSec} sec"); Logger.Info($"Cloud protocol: {options.Protocol}"); Logger.Info($"Cache listings, sec: {options.CacheListingSec}"); Logger.Info($"List query folder depth: {options.CacheListingDepth}"); From 5c3ac799dda7ec0499c0375622e89109d0b6cdd6 Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Mon, 16 Oct 2023 16:47:32 +0300 Subject: [PATCH 30/77] =?UTF-8?q?=D0=9D=D0=B5=D1=81=D0=BA=D0=BE=D0=BB?= =?UTF-8?q?=D1=8C=D0=BA=D0=BE=20=D0=B1=D0=BE=D0=BB=D0=B5=D0=B5=20=D0=B3?= =?UTF-8?q?=D0=BE=D0=B2=D0=BE=D1=80=D1=8F=D1=89=D0=B5=D0=B5=20=D0=BE=D0=BF?= =?UTF-8?q?=D0=B8=D1=81=D0=B0=D0=BD=D0=B8=D0=B5=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D0=BF=D0=B0=D1=80=D0=B0=D0=BC=D0=B5=D1=82=D1=80=D0=B0=20--cach?= =?UTF-8?q?e-listing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MailRuCloud/MailRuCloudApi/CloudSettings.cs | 3 +++ WDMRC.Console/CommandLineOptions.cs | 2 +- WDMRC.Console/Payload.cs | 2 +- readme.md | 2 +- 4 files changed, 6 insertions(+), 3 deletions(-) diff --git a/MailRuCloud/MailRuCloudApi/CloudSettings.cs b/MailRuCloud/MailRuCloudApi/CloudSettings.cs index 77030594..19572153 100644 --- a/MailRuCloud/MailRuCloudApi/CloudSettings.cs +++ b/MailRuCloud/MailRuCloudApi/CloudSettings.cs @@ -36,10 +36,13 @@ public int ListDepth public bool DisableLinkManager { get; set; } + #region Timeouts + public int Wait100ContinueTimeoutSec { get; set; } public int WaitResponseTimeoutSec { get; set; } public int ReadWriteTimeoutSec { get; set; } + #endregion #region BrowserAuthenticator public string BrowserAuthenticatorUrl { get; set; } diff --git a/WDMRC.Console/CommandLineOptions.cs b/WDMRC.Console/CommandLineOptions.cs index ddf2f182..3eb44e6f 100644 --- a/WDMRC.Console/CommandLineOptions.cs +++ b/WDMRC.Console/CommandLineOptions.cs @@ -50,7 +50,7 @@ class CommandLineOptions [Option("protocol", Default = Protocol.WebM1Bin, HelpText = "Cloud protocol")] public Protocol Protocol { get; set; } - [Option("cache-listing", Default = 30, HelpText = "Cache folders listing, sec")] + [Option("cache-listing", Default = 30, HelpText = "Duration of in-memory cache of folder's listing, sec")] public int CacheListingSec { get; set; } [Option("cache-listing-depth", Default = 1, HelpText = "List query folder depth, always equals 1 when cache-listing>0")] diff --git a/WDMRC.Console/Payload.cs b/WDMRC.Console/Payload.cs index 56f17e26..fcf38bb8 100644 --- a/WDMRC.Console/Payload.cs +++ b/WDMRC.Console/Payload.cs @@ -217,7 +217,7 @@ private static void ShowInfo(CommandLineOptions options) Logger.Info($"Cloud download/upload timeout: {options.ReadWriteTimeoutSec} sec"); Logger.Info($"Wait for 100-Continue timeout: {options.Wait100ContinueTimeoutSec} sec"); Logger.Info($"Cloud protocol: {options.Protocol}"); - Logger.Info($"Cache listings, sec: {options.CacheListingSec}"); + Logger.Info($"Duration of in-memory cache of folder's listing: {options.CacheListingSec} sec"); Logger.Info($"List query folder depth: {options.CacheListingDepth}"); Logger.Info($"Use locks: {options.UseLocks}"); Logger.Info($"Support links in /item.links.wdmrc: {(!options.DisableLinkManager)}"); diff --git a/readme.md b/readme.md index 5e06f879..a9094b29 100644 --- a/readme.md +++ b/readme.md @@ -25,7 +25,7 @@ -h, --host (Default: "http://127.0.0.1") WebDAV server host with protocol (http://* for http://0.0.0.0) --maxthreads (Default: 5) Maximum concurrent connections to cloud.mail.ru / disk.yandex.ru --use-locks use locking feature - --cache-listing (Default: 30) Cache folders listing, sec + --cache-listing (Default: 30) Duration of in-memory cache of folder's listing, sec --cache-listing-depth (Default: 1) Cache folders listing depth. If large folder browsing is extremely slow, set to 2 From a95f5d932b3f24362d600c274e7c24269229182b Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Mon, 16 Oct 2023 19:17:57 +0300 Subject: [PATCH 31/77] =?UTF-8?q?=D0=9D=D0=B5=D0=B1=D0=BE=D0=BB=D1=8C?= =?UTF-8?q?=D1=88=D0=BE=D0=B5=20=D1=83=D1=81=D0=BA=D0=BE=D1=80=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=B7=D0=B0=20=D1=81=D1=87=D0=B5=D1=82=20=D1=81?= =?UTF-8?q?=D0=BE=D0=BA=D1=80=D0=B0=D1=89=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BE?= =?UTF-8?q?=D0=B1=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=BA=D0=B8=20=D0=B8=D1=81?= =?UTF-8?q?=D0=BA=D0=BB=D1=8E=D1=87=D0=B5=D0=BD=D0=B8=D0=B9=20=D0=B8=20?= =?UTF-8?q?=D0=BA=D1=8D=D1=88=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20URL'=D0=B0=20=D0=BD=D0=B0=20=D1=81=D0=BA=D0=B0=D1=87?= =?UTF-8?q?=D0=B8=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20=D1=84=D0=B0=D0=B9=D0=BB?= =?UTF-8?q?=D0=B0.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MailRuCloud/MailRuCloudApi/Base/File.cs | 6 ++ .../YandexDisk/YadWebV2/YadWebRequestRepo.cs | 60 +++++++++++-------- .../Base/Streams/DownloadStream.cs | 3 +- .../HttpBaseContext.cs | 8 ++- .../HttpResponse.cs | 6 ++ NWebDav/NWebDav.Server/Http/IHttpResponse.cs | 2 + 6 files changed, 58 insertions(+), 27 deletions(-) diff --git a/MailRuCloud/MailRuCloudApi/Base/File.cs b/MailRuCloud/MailRuCloudApi/Base/File.cs index 544641e5..b56f6dce 100644 --- a/MailRuCloud/MailRuCloudApi/Base/File.cs +++ b/MailRuCloud/MailRuCloudApi/Base/File.cs @@ -36,6 +36,12 @@ public File(string fullPath, long size, params PublicLinkInfo[] links) private IFileHash _hash; + /// + /// Заполняется операцией GetDownloadStream, + /// чтобы при повторном обращении на чтение файла не тратить время на получения URL'а. + /// + public string DownloadUrlCache { get; set; } = null; + /// /// makes copy of this file with new path /// diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/YadWebRequestRepo.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/YadWebRequestRepo.cs index 503bd011..8f82e95a 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/YadWebRequestRepo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/YadWebRequestRepo.cs @@ -77,34 +77,41 @@ public Cached>> CachedSharedList public HttpCommonSettings HttpSettings { get; private set; } - public Stream GetDownloadStream(File afile, long? start = null, long? end = null) + public Stream GetDownloadStream(File aFile, long? start = null, long? end = null) { CustomDisposable ResponseGenerator(long instart, long inend, File file) { //var urldata = new YadGetResourceUrlRequest(HttpSettings, (YadWebAuth)Authent, file.FullPath) // .MakeRequestAsync() // .Result; - - var _ = new YaDCommonRequest(HttpSettings, (YadWebAuth)Authent) - .With(new YadGetResourceUrlPostModel(file.FullPath), - out YadResponseModel itemInfo) - .MakeRequestAsync().Result; - - if (itemInfo == null || - itemInfo.Error != null || - itemInfo.Data == null || - itemInfo.Data.Error != null || - itemInfo?.Data?.File == null) + string url = null; + if (file.DownloadUrlCache == null) { - throw new FileNotFoundException(string.Concat( - "File reading error ", itemInfo?.Error?.Message, - " ", - itemInfo?.Data?.Error?.Message, - " ", - itemInfo?.Data?.Error?.Body?.Title)); + var _ = new YaDCommonRequest(HttpSettings, (YadWebAuth)Authent) + .With(new YadGetResourceUrlPostModel(file.FullPath), + out YadResponseModel itemInfo) + .MakeRequestAsync().Result; + + if (itemInfo == null || + itemInfo.Error != null || + itemInfo.Data == null || + itemInfo.Data.Error != null || + itemInfo?.Data?.File == null) + { + throw new FileNotFoundException(string.Concat( + "File reading error ", itemInfo?.Error?.Message, + " ", + itemInfo?.Data?.Error?.Message, + " ", + itemInfo?.Data?.Error?.Body?.Title)); + } + url = "https:" + itemInfo.Data.File; + file.DownloadUrlCache = url; + } + else + { + url = file.DownloadUrlCache; } - - var url = "https:" + itemInfo.Data.File; HttpWebRequest request = new YadDownloadRequest(HttpSettings, (YadWebAuth)Authent, url, instart, inend); var response = (HttpWebResponse)request.GetResponse(); @@ -115,7 +122,12 @@ CustomDisposable ResponseGenerator(long instart, long inend, Fi }; } - var stream = new DownloadStream(ResponseGenerator, afile, start, end); + if (start.HasValue || end.HasValue) + Logger.Debug($"Download: {aFile.FullPath} [{start}-{end}]"); + else + Logger.Debug($"Download: {aFile.FullPath}"); + + var stream = new DownloadStream(ResponseGenerator, aFile, start, end); return stream; } @@ -127,9 +139,9 @@ CustomDisposable ResponseGenerator(long instart, long inend, Fi // .Result; // var url = urldata.Models[0].Data.UploadUrl; - // var result = new YadUploadRequest(HttpSettings, (YadWebAuth)Authent, url, file.OriginalSize); - // return result; - //} + // var result = new YadUploadRequest(HttpSettings, (YadWebAuth)Authent, url, file.OriginalSize); + // return result; + //} public ICloudHasher GetHasher() { diff --git a/MailRuCloud/MailRuCloudApi/Base/Streams/DownloadStream.cs b/MailRuCloud/MailRuCloudApi/Base/Streams/DownloadStream.cs index fe20f427..2e25e70c 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Streams/DownloadStream.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Streams/DownloadStream.cs @@ -23,7 +23,8 @@ internal class DownloadStream : Stream private RingBufferedStream _innerStream; private bool _initialized; - public DownloadStream(Func> responseGenerator, File file, long? start = null, long? end = null) + public DownloadStream(Func> responseGenerator, + File file, long? start = null, long? end = null) : this(responseGenerator, file.Parts, start, end) { } diff --git a/NWebDav/NWebDav.Server.HttpListener/HttpBaseContext.cs b/NWebDav/NWebDav.Server.HttpListener/HttpBaseContext.cs index 98a42238..561f36fe 100644 --- a/NWebDav/NWebDav.Server.HttpListener/HttpBaseContext.cs +++ b/NWebDav/NWebDav.Server.HttpListener/HttpBaseContext.cs @@ -30,8 +30,12 @@ public Task CloseAsync() // Prevent any exceptions try { - // At first send remaining buffered byte to client - _response.OutputStream?.Flush(); + if (!Response.IsAborted) + { + // At first send remaining buffered byte to client + _response.OutputStream?.Flush(); + _response.OutputStream?.Close(); + } } catch { } diff --git a/NWebDav/NWebDav.Server.HttpListener/HttpResponse.cs b/NWebDav/NWebDav.Server.HttpListener/HttpResponse.cs index c05cc43c..3d0c6dc9 100644 --- a/NWebDav/NWebDav.Server.HttpListener/HttpResponse.cs +++ b/NWebDav/NWebDav.Server.HttpListener/HttpResponse.cs @@ -9,9 +9,12 @@ public class HttpResponse : IHttpResponse { private readonly HttpListenerResponse _response; + private bool _isAborted; + internal HttpResponse(HttpListenerResponse response) { _response = response; + _isAborted = false; } public int Status @@ -46,9 +49,12 @@ public void SetHeaderValue(string header, string value) public void Abort() { + _isAborted = true; _response.Abort(); } + public bool IsAborted => _isAborted; + public Stream Stream => _response.OutputStream; } } \ No newline at end of file diff --git a/NWebDav/NWebDav.Server/Http/IHttpResponse.cs b/NWebDav/NWebDav.Server/Http/IHttpResponse.cs index 8d42a601..f07940b6 100644 --- a/NWebDav/NWebDav.Server/Http/IHttpResponse.cs +++ b/NWebDav/NWebDav.Server/Http/IHttpResponse.cs @@ -66,5 +66,7 @@ public interface IHttpResponse Stream Stream { get; } void Abort(); + + bool IsAborted { get; } } } \ No newline at end of file From fe8379756bc679fded54b12fbed03ee7f4618aa0 Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Mon, 16 Oct 2023 19:19:44 +0300 Subject: [PATCH 32/77] =?UTF-8?q?=D0=A3=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=B8=D0=B7=D0=B1=D1=8B=D1=82=D0=BE=D1=87=D0=BD?= =?UTF-8?q?=D0=BE=D0=B3=D0=BE=20=D0=BE=D0=B1=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D1=87=D0=B8=D0=BA=D0=B0=20=D0=BF=D0=BE=D1=81=D0=BB=D0=B5=20?= =?UTF-8?q?=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD=D0=B8=D1=8F=20BaseRe?= =?UTF-8?q?quest.cs=20=D0=B2=20=D1=87=D0=B0=D1=81=D1=82=D0=B8=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=B2=D1=82=D0=BE=D1=80=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BE?= =?UTF-8?q?=D0=B1=D1=80=D0=B0=D1=89=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BF=D1=80?= =?UTF-8?q?=D0=B8=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D0=B5.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- NWebDav/NWebDav.Server/WebDavDispatcher.cs | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/NWebDav/NWebDav.Server/WebDavDispatcher.cs b/NWebDav/NWebDav.Server/WebDavDispatcher.cs index 7d7d5196..cabecb28 100644 --- a/NWebDav/NWebDav.Server/WebDavDispatcher.cs +++ b/NWebDav/NWebDav.Server/WebDavDispatcher.cs @@ -177,19 +177,6 @@ public async Task DispatchRequestAsync(IHttpContext httpContext) s_log.Log(LogLevel.Error, $"Error while handling request (method={request.HttpMethod}, url={request.Url} {httpContext.Response.StatusDescription}"); } - catch (AggregateException aex) when (aex.InnerExceptions.Count == 3 && - /* An error occurred while sending the request. */ - aex.InnerExceptions[0] is System.Net.WebException && - /* An error occurred while sending the request. */ - /*aex.InnerExceptions[1] is System.Net.Http.HttpRequestException &&*/ - /* The response ended prematurely. */ - aex.InnerExceptions[2] is System.Net.WebException - ) - { - // If client didn't wait for operation completion, just do nothing - s_log.Log(LogLevel.Debug, $"Client disconnected. Error while handling request (method={request.HttpMethod}, url={request.Url} {httpContext.Response.StatusDescription}"); - } - catch (Exception exc) { s_log.Log(LogLevel.Error, $"Unexpected exception while handling request (method={request.HttpMethod}, url={request.Url}, source={request.RemoteEndPoint}", exc); From 6cce3f265ee1bc151cb1ad4f9a25d5425d9c04a3 Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Mon, 16 Oct 2023 21:58:26 +0300 Subject: [PATCH 33/77] =?UTF-8?q?=D0=92=D0=BC=D0=B5=D1=81=D1=82=D0=BE=20?= =?UTF-8?q?=D1=82=D1=8F=D0=B6=D0=B5=D0=BB=D0=BE=D0=B3=D0=BE=20lock(){}=20?= =?UTF-8?q?=D0=BF=D1=80=D0=B8=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD=20SemaphoreSli?= =?UTF-8?q?m.=20=D0=98=20=D0=BD=D0=B5=D0=B1=D0=BE=D0=BB=D1=8C=D1=88=D0=B0?= =?UTF-8?q?=D1=8F=20=D0=BF=D0=BE=D0=BB=D0=B8=D1=80=D0=BE=D0=B2=D0=BA=D0=B0?= =?UTF-8?q?=20link=20manager'=D0=B0.=20=D0=A1=D1=83=D0=B1=D1=8A=D0=B5?= =?UTF-8?q?=D0=BA=D1=82=D0=B8=D0=B2=D0=BD=D0=BE,=20=D0=B4=D0=B8=D1=80?= =?UTF-8?q?=D0=B5=D0=BA=D1=82=D0=BE=D1=80=D0=B8=D0=B8=20=D1=81=D1=82=D0=B0?= =?UTF-8?q?=D0=BB=D0=B8=20=D1=87=D0=B8=D1=82=D0=B0=D1=82=D1=8C=D1=81=D1=8F?= =?UTF-8?q?=20=D0=B1=D1=8B=D1=81=D1=82=D1=80=D0=B5=D0=B5.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MailRuCloud/MailRuCloudApi/Cloud.cs | 97 ++++++------- MailRuCloud/MailRuCloudApi/Common/Cached.cs | 27 ++-- MailRuCloud/MailRuCloudApi/Common/Pending.cs | 31 +++-- .../MailRuCloudApi/Links/LinkManager.cs | 131 +++++++++++------- .../Handlers/PropFindHandler.cs | 29 +++- .../Locking/InMemoryLockingManager.cs | 48 ++++++- WebDavMailRuCloudStore/CloudManager.cs | 30 ++-- .../StoreBase/LocalStoreCollection.cs | 13 +- 8 files changed, 263 insertions(+), 143 deletions(-) diff --git a/MailRuCloud/MailRuCloudApi/Cloud.cs b/MailRuCloud/MailRuCloudApi/Cloud.cs index 66d3d0ee..9b3637ee 100644 --- a/MailRuCloud/MailRuCloudApi/Cloud.cs +++ b/MailRuCloud/MailRuCloudApi/Cloud.cs @@ -11,7 +11,6 @@ using Newtonsoft.Json; using YaR.Clouds.Base; using YaR.Clouds.Base.Repos; -using YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests.Types; using YaR.Clouds.Base.Requests.Types; using YaR.Clouds.Common; using YaR.Clouds.Extensions; @@ -108,12 +107,12 @@ public virtual async Task GetItemAsync(string path, ItemType itemType = if (Settings.CacheListingSec > 0) { var cached = CacheGetEntry(path); - if (cached != null) + if (cached is not null) return cached; } //TODO: subject to refact!!! - Link ulink = resolveLinks && LinkManager != null ? await LinkManager.GetItemLink(path) : null; + Link ulink = resolveLinks && LinkManager is not null ? await LinkManager.GetItemLink(path) : null; // bad link detected, just return stub // cause client cannot, for example, delete it if we return NotFound for this item @@ -124,7 +123,7 @@ public virtual async Task GetItemAsync(string path, ItemType itemType = return res; } - if (itemType == ItemType.Unknown && ulink != null) + if (itemType == ItemType.Unknown && ulink is not null) itemType = ulink.ItemType; // TODO: cache (parent) folder for file @@ -135,9 +134,9 @@ public virtual async Task GetItemAsync(string path, ItemType itemType = // //_itemCache.Add(cachefolder.Files); //} - var rp = null == ulink ? RemotePath.Get(path) : RemotePath.Get(ulink); + var rp = ulink is null ? RemotePath.Get(path) : RemotePath.Get(ulink); var entry = await Account.RequestRepo.FolderInfo(rp, depth:Settings.ListDepth); - if (null == entry) + if (entry is null) return null; if (itemType == ItemType.Unknown) @@ -158,10 +157,10 @@ private void FillWithULinks(Folder folder) { if (!folder.IsChildrenLoaded) return; - if (LinkManager != null) + if (LinkManager is not null) { var flinks = LinkManager.GetItems(folder.FullPath); - if (flinks != null && flinks.Any()) + if (flinks is not null && flinks.Any()) { foreach (var flink in flinks) { @@ -178,7 +177,7 @@ private void FillWithULinks(Folder folder) continue; var newFile = new File(linkpath, flink.Size, new PublicLinkInfo(flink.Href)); - if (flink.CreationDate != null) + if (flink.CreationDate is not null) newFile.LastWriteTimeUtc = flink.CreationDate.Value; folder.Files.AddOrUpdate(newFile.FullPath, newFile, (_, _) => newFile); } @@ -217,7 +216,7 @@ public virtual IEntry GetItem(string path, ItemType itemType = ItemType.Unknown, public bool IsFileExists(string filename, List folderPaths) { - if (folderPaths == null) + if (folderPaths is null) return false; var folders = folderPaths @@ -225,12 +224,12 @@ public bool IsFileExists(string filename, List folderPaths) .WithDegreeOfParallelism(Math.Min(MaxInnerParallelRequests, folderPaths.Count)) .Select(async path => (Folder)await GetItemAsync(path, ItemType.Folder, false)); - if (folders == null) + if (folders is null) return false; foreach (var item in folders) { - if (item.Result == null) + if (item.Result is null) continue; Folder folder = item.Result; // Вместо перебора и сравнения всех названий файлов в папке, @@ -362,10 +361,10 @@ public async Task Copy(Folder folder, string destinationPath) destinationPath = WebDavPath.Clean(destinationPath); // if it linked - just clone - if (LinkManager != null) + if (LinkManager is not null) { var link = await LinkManager.GetItemLink(folder.FullPath, false); - if (link != null) + if (link is not null) { var cloneres = await CloneItem(destinationPath, link.Href.OriginalString); if (!cloneres.IsSuccess || WebDavPath.Name(cloneres.Path) == link.Name) @@ -380,10 +379,10 @@ public async Task Copy(Folder folder, string destinationPath) if (!copyRes.IsSuccess) return false; //clone all inner links - if (LinkManager != null) + if (LinkManager is not null) { var links = LinkManager.GetChildren(folder.FullPath); - if (links != null) + if (links is not null) { foreach (var linka in links) { @@ -414,7 +413,7 @@ public async Task Copy(Folder folder, string destinationPath) public async Task Copy(string sourcePath, string destinationPath) { var entry = await GetItemAsync(sourcePath); - if (null == entry) return false; + if (entry is null) return false; return await Copy(entry, destinationPath); } @@ -424,16 +423,16 @@ public async Task Copy(string sourcePath, string destinationPath) /// /// Source item. /// Destination path on the server. - /// Rename target item. + /// Rename target item. /// True or false operation result. - public async Task Copy(IEntry source, string destinationPath, string newname = null) + public async Task Copy(IEntry source, string destinationPath, string newName = null) { - if (source == null) throw new ArgumentNullException(nameof(source)); + if (source is null) throw new ArgumentNullException(nameof(source)); if (string.IsNullOrEmpty(destinationPath)) throw new ArgumentNullException(nameof(destinationPath)); return source switch { - File file => await Copy(file, destinationPath, string.IsNullOrEmpty(newname) ? file.Name : newname), + File file => await Copy(file, destinationPath, string.IsNullOrEmpty(newName) ? file.Name : newName), Folder folder => await Copy(folder, destinationPath), _ => throw new ArgumentException("Source is not a file or folder", nameof(source)) }; @@ -444,22 +443,22 @@ public async Task Copy(IEntry source, string destinationPath, string newna /// /// Source file info. /// Destination path. - /// Rename target file. + /// Rename target file. /// True or false operation result. - public async Task Copy(File file, string destinationPath, string newname) + public async Task Copy(File file, string destinationPath, string newName) { string destPath = destinationPath; - newname = string.IsNullOrEmpty(newname) ? file.Name : newname; - bool doRename = file.Name != newname; + newName = string.IsNullOrEmpty(newName) ? file.Name : newName; + bool doRename = file.Name != newName; - if (LinkManager != null) + if (LinkManager is not null) { var link = await LinkManager.GetItemLink(file.FullPath, false); // копируем не саму ссылку, а её содержимое - if (link != null) + if (link is not null) { var cloneRes = await CloneItem(destPath, link.Href.OriginalString); - if (doRename || WebDavPath.Name(cloneRes.Path) != newname) + if (doRename || WebDavPath.Name(cloneRes.Path) != newName) { string newFullPath = WebDavPath.Combine(destPath, WebDavPath.Name(cloneRes.Path)); var renameRes = await Rename(newFullPath, link.Name); @@ -479,11 +478,11 @@ public async Task Copy(File file, string destinationPath, string newname) var copyRes = await Account.RequestRepo.Copy(pfile.FullPath, destPath, ConflictResolver.Rewrite); if (!copyRes.IsSuccess) return false; - if (!doRename && WebDavPath.Name(copyRes.NewName) == newname) + if (!doRename && WebDavPath.Name(copyRes.NewName) == newName) return true; string newFullPath = WebDavPath.Combine(destPath, WebDavPath.Name(copyRes.NewName)); - return await Rename(newFullPath, pfile.Name.Replace(file.Name, newname)); + return await Rename(newFullPath, pfile.Name.Replace(file.Name, newName)); }); _itemCache.Invalidate(destinationPath); @@ -505,8 +504,10 @@ public async Task Copy(File file, string destinationPath, string newname) /// True or false operation result. public async Task Rename(IEntry source, string newName) { - if (source == null) throw new ArgumentNullException(nameof(source)); - if (string.IsNullOrEmpty(newName)) throw new ArgumentNullException(nameof(newName)); + if (source is null) + throw new ArgumentNullException(nameof(source)); + if (string.IsNullOrEmpty(newName)) + throw new ArgumentNullException(nameof(newName)); return source switch { @@ -555,10 +556,10 @@ public async Task Rename(File file, string newFileName) /// True or false result operation. private async Task Rename(string fullPath, string newName) { - var link = LinkManager==null? null : await LinkManager.GetItemLink(fullPath, false); + var link = LinkManager is null? null : await LinkManager.GetItemLink(fullPath, false); //rename item - if (link == null) + if (link is null) { var data = await Account.RequestRepo.Rename(fullPath, newName); @@ -572,7 +573,7 @@ private async Task Rename(string fullPath, string newName) } //rename link - if (LinkManager != null) + if (LinkManager is not null) { bool res = LinkManager.RenameLink(link, newName); if (res) @@ -594,7 +595,7 @@ private async Task Rename(string fullPath, string newName) /// True or false operation result. public async Task MoveAsync(IEntry source, string destinationPath) { - if (source == null) throw new ArgumentNullException(nameof(source)); + if (source is null) throw new ArgumentNullException(nameof(source)); if (string.IsNullOrEmpty(destinationPath)) throw new ArgumentNullException(nameof(destinationPath)); return source switch @@ -608,7 +609,7 @@ public async Task MoveAsync(IEntry source, string destinationPath) public async Task MoveAsync(string sourcePath, string destinationPath) { var entry = await GetItemAsync(sourcePath); - if (null == entry) + if (entry is null) return false; return await MoveAsync(entry, destinationPath); @@ -628,8 +629,8 @@ public bool Move(string sourcePath, string destinationPath) /// True or false operation result. public async Task MoveAsync(Folder folder, string destinationPath) { - var link = LinkManager == null ? null : await LinkManager.GetItemLink(folder.FullPath, false); - if (link != null) + var link = LinkManager is null ? null : await LinkManager.GetItemLink(folder.FullPath, false); + if (link is not null) { var remapped = await LinkManager.RemapLink(link, destinationPath); if (remapped) @@ -642,7 +643,7 @@ public async Task MoveAsync(Folder folder, string destinationPath) if (!res.IsSuccess) return false; //clone all inner links - if (LinkManager != null) + if (LinkManager is not null) { var links = LinkManager.GetChildren(folder.FullPath).ToList(); foreach (var linka in links) @@ -679,8 +680,8 @@ public async Task MoveAsync(Folder folder, string destinationPath) /// True or false operation result. public async Task MoveAsync(File file, string destinationPath) { - var link = LinkManager == null ? null : await LinkManager.GetItemLink(file.FullPath, false); - if (link != null) + var link = LinkManager is null ? null : await LinkManager.GetItemLink(file.FullPath, false); + if (link is not null) { var remapped = await LinkManager.RemapLink(link, destinationPath); if (remapped) @@ -795,9 +796,9 @@ public virtual async Task Remove(File file, bool removeShareDescription = private async Task Remove(string fullPath) { //TODO: refact - var link = LinkManager == null ? null : await LinkManager.GetItemLink(fullPath, false); + var link = LinkManager is null ? null : await LinkManager.GetItemLink(fullPath, false); - if (link != null) + if (link is not null) { //if folder is linked - do not delete inner files/folders if client deleting recursively //just try to unlink folder @@ -812,7 +813,7 @@ private async Task Remove(string fullPath) return false; //remove inner links - if (LinkManager != null) + if (LinkManager is not null) { var innerLinks = LinkManager.GetChildren(fullPath); LinkManager.RemoveLinks(innerLinks); @@ -1030,7 +1031,7 @@ public void Dispose() public async Task LinkItem(Uri url, string path, string name, bool isFile, long size, DateTime? creationDate) { - if (LinkManager == null) + if (LinkManager is null) return false; var res = await LinkManager.Add(url, path, name, isFile, size, creationDate); @@ -1044,7 +1045,7 @@ public async Task LinkItem(Uri url, string path, string name, bool isFile, public async void RemoveDeadLinks() { - if (LinkManager == null) + if (LinkManager is null) return; var count = await LinkManager.RemoveDeadLinks(true); @@ -1098,7 +1099,7 @@ public async Task CryptInit(Folder folder) string filepath = WebDavPath.Combine(folder.FullPath, CryptFileInfo.FileName); var file = await GetItemAsync(filepath).ConfigureAwait(false); - if (file != null) + if (file is not null) return false; var content = new CryptFileInfo diff --git a/MailRuCloud/MailRuCloudApi/Common/Cached.cs b/MailRuCloud/MailRuCloudApi/Common/Cached.cs index 345c82c8..1bb732d5 100644 --- a/MailRuCloud/MailRuCloudApi/Common/Cached.cs +++ b/MailRuCloud/MailRuCloudApi/Common/Cached.cs @@ -1,4 +1,5 @@ using System; +using System.Threading; namespace YaR.Clouds.Common { @@ -26,26 +27,31 @@ public Cached(Func valueFactory, Func duration) RefreshValueIfNeeded(); } - private readonly object _refreshLock = new(); + private readonly SemaphoreSlim _locker = new SemaphoreSlim(1); private void RefreshValueIfNeeded() { - if (DateTime.Now < _expiration) + if (DateTime.Now < _expiration) return; - lock (_refreshLock) + _locker.Wait(); + try { - if (DateTime.Now < _expiration) + if (DateTime.Now < _expiration) return; - T oldValue = _value is { IsValueCreated: true } ? _value.Value : default; + T oldValue = _value is { IsValueCreated: true } ? _value.Value : default; _value = new Lazy(() => _valueFactory(oldValue)); var duration = _duration(_value.Value); - _expiration = duration == TimeSpan.MaxValue + _expiration = duration == TimeSpan.MaxValue ? DateTime.MaxValue : DateTime.Now.Add(duration); } + finally + { + _locker.Release(); + } } public override string ToString() @@ -55,10 +61,15 @@ public override string ToString() public void Expire() { - lock (_refreshLock) + _locker.Wait(); + try { _expiration = DateTime.MinValue; } + finally + { + _locker.Release(); + } } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Common/Pending.cs b/MailRuCloud/MailRuCloudApi/Common/Pending.cs index 4e9bbbbc..cbace255 100644 --- a/MailRuCloud/MailRuCloudApi/Common/Pending.cs +++ b/MailRuCloud/MailRuCloudApi/Common/Pending.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; namespace YaR.Clouds.Common { @@ -16,33 +17,40 @@ public Pending(int maxLocks, Func valueFactory) _valueFactory = valueFactory; } - private readonly object _lock = new(); + private readonly SemaphoreSlim _locker = new SemaphoreSlim(1); public T Next(T current) { - lock (_lock) + _locker.Wait(); + try { - var item = null == current + var item = current is null ? _items.FirstOrDefault(it => it.LockCount < _maxLocks) : _items.SkipWhile(it => !it.Equals(current)).Skip(1).FirstOrDefault(it => it.LockCount < _maxLocks); - if (null == item) - _items.Add(item = new PendingItem {Item = _valueFactory(), LockCount = 0}); + if (item is null) + _items.Add(item = new PendingItem { Item = _valueFactory(), LockCount = 0 }); item.LockCount++; return item.Item; } + finally + { + _locker.Release(); + } } public void Free(T value) { - if (null == value) + if (value is null) return; - lock (_lock) + _locker.Wait(); + try { foreach (var item in _items) + { if (item.Item.Equals(value)) { switch (item.LockCount) @@ -54,12 +62,13 @@ public void Free(T value) break; } } + } + } + finally + { + _locker.Release(); } } - - - - } class PendingItem diff --git a/MailRuCloud/MailRuCloudApi/Links/LinkManager.cs b/MailRuCloud/MailRuCloudApi/Links/LinkManager.cs index 49f4a5f7..a6777cb1 100644 --- a/MailRuCloud/MailRuCloudApi/Links/LinkManager.cs +++ b/MailRuCloud/MailRuCloudApi/Links/LinkManager.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Linq; using System.Text; +using System.Threading; using System.Threading.Tasks; using Newtonsoft.Json; using YaR.Clouds.Base; @@ -16,6 +17,8 @@ namespace YaR.Clouds.Links /// public class LinkManager { + private readonly TimeSpan SaveAndLoadTimeout = TimeSpan.FromMinutes(5); + private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(LinkManager)); private const string LinkContainerName = "item.links.wdmrc"; @@ -24,13 +27,18 @@ public class LinkManager private ItemList _itemList = new(); private readonly ItemCache _itemCache; - private readonly object _lockContainer = new(); + private readonly SemaphoreSlim _locker; public LinkManager(Cloud cloud) { + _locker = new SemaphoreSlim(1); _cloud = cloud; - _itemCache = new ItemCache(TimeSpan.FromSeconds(60)) { CleanUpPeriod = TimeSpan.FromMinutes(5) }; + _itemCache = new ItemCache(TimeSpan.FromSeconds(60)); + //{ + // Полагаемся на стандартно заданное время очистки + // CleanUpPeriod = TimeSpan.FromMinutes(5) + //}; cloud.FileUploaded += OnFileUploaded; @@ -40,75 +48,92 @@ public LinkManager(Cloud cloud) private void OnFileUploaded(IEnumerable files) { var file = files?.FirstOrDefault(); - if (null == file) return; + if (file is null) return; - if (file.Path == "/" && file.Name == LinkContainerName && file.Size > 3) + if (file.Path == WebDavPath.Root && + file.Name == LinkContainerName && + file.Size > 3) + { Load(); + } } /// /// Сохранить в файл в облаке список ссылок /// - public void Save() + public async void Save() { - lock (_lockContainer) - { - - Logger.Info($"Saving links to {LinkContainerName}"); + Logger.Info($"Saving links to {LinkContainerName}"); - string content = JsonConvert.SerializeObject(_itemList, Formatting.Indented); - string path = WebDavPath.Combine(WebDavPath.Root, LinkContainerName); + if (await _locker.WaitAsync(SaveAndLoadTimeout)) + { try { + string content = JsonConvert.SerializeObject(_itemList, Formatting.Indented); + string path = WebDavPath.Combine(WebDavPath.Root, LinkContainerName); _cloud.FileUploaded -= OnFileUploaded; _cloud.UploadFile(path, content); } finally { _cloud.FileUploaded += OnFileUploaded; + _locker.Release(); } } + else + { + Logger.Info($"Timeout while saving links to {LinkContainerName}"); + } } /// /// Загрузить из файла в облаке список ссылок /// - public void Load() + public async void Load() { - if (!_cloud.Account.IsAnonymous) { Logger.Info($"Loading links from {LinkContainerName}"); - try + if (await _locker.WaitAsync(SaveAndLoadTimeout)) { - lock (_lockContainer) + try { - //throw new Exception("temp"); - string filepath = WebDavPath.Combine(WebDavPath.Root, LinkContainerName); - var file = (File) _cloud.GetItem(filepath, Cloud.ItemType.File, false); + var file = (File)_cloud.GetItem(filepath, Cloud.ItemType.File, false); - if (file != null && file.Size > 3) //some clients put one/two/three-byte file before original file + // If the file is not empty. + // An empty file in UTF-8 with BOM is exactly 3 bytes long. + if (file is not null && file.Size > 3) { _itemList = _cloud.DownloadFileAsJson(file); } + + _itemList ??= new ItemList(); + + foreach (var f in _itemList.Items) + { + f.MapTo = WebDavPath.Clean(f.MapTo); + if (!f.Href.IsAbsoluteUri) + f.Href = new Uri(_cloud.Repo.PublicBaseUrlDefault + f.Href); + } + } + catch (Exception e) + { + Logger.Warn("Cannot load links", e); + } + finally + { + _locker.Release(); } } - catch (Exception e) + else { - Logger.Warn("Cannot load links", e); + Logger.Info($"Timeout while loading links from {LinkContainerName}"); } } _itemList ??= new ItemList(); - - foreach (var f in _itemList.Items) - { - f.MapTo = WebDavPath.Clean(f.MapTo); - if (!f.Href.IsAbsoluteUri) - f.Href = new Uri(_cloud.Repo.PublicBaseUrlDefault + f.Href); - } } /// @@ -137,12 +162,15 @@ public bool RemoveLink(string path, bool doSave = true) var z = _itemList.Items.FirstOrDefault(f => f.MapTo == parent && f.Name == name); - if (z == null) + if (z is null) return false; _itemList.Items.Remove(z); _itemCache.Invalidate(path, parent); - if (doSave) Save(); + + if (doSave) + Save(); + return true; } @@ -150,12 +178,15 @@ public void RemoveLinks(IEnumerable innerLinks, bool doSave = true) { bool removed = false; var lst = innerLinks.ToList(); + foreach (var link in lst) { var res = RemoveLink(link.FullPath, false); if (res) removed = true; } - if (doSave && removed) Save(); + + if (doSave && removed) + Save(); } @@ -169,16 +200,17 @@ public async Task RemoveDeadLinks(bool doWriteHistory) .AsParallel() .WithDegreeOfParallelism(5) .Select(it => GetItemLink(WebDavPath.Combine(it.MapTo, it.Name)).Result) - .Where(itl => - itl.IsBad || - _cloud.GetItemAsync(itl.MapPath, Cloud.ItemType.Folder, false).Result == null) + .Where(itl => + itl.IsBad || + _cloud.GetItemAsync(itl.MapPath, Cloud.ItemType.Folder, false).Result is null) .ToList(); + if (removes.Count == 0) return 0; _itemList.Items.RemoveAll(it => removes.Any(rem => WebDavPath.PathEquals(rem.MapPath, it.MapTo) && rem.Name == it.Name)); - if (removes.Count == 0) + if (removes.Count == 0) return 0; if (doWriteHistory) @@ -212,7 +244,7 @@ public async Task RemoveDeadLinks(bool doWriteHistory) // try // { // var entry = _cloud.GetItem(path).Result; - // return entry != null; + // return entry is not null; // } // catch (AggregateException e) // when ( // let's check if there really no file or just other network error @@ -233,7 +265,7 @@ public async Task RemoveDeadLinks(bool doWriteHistory) public async Task GetItemLink(string path, bool doResolveType = true) { var cached = _itemCache.Get(path); - if (null != cached) + if (cached is not null) return (Link)cached; //TODO: subject to refact @@ -245,10 +277,13 @@ public async Task GetItemLink(string path, bool doResolveType = true) string name = WebDavPath.Name(parent); parent = WebDavPath.Parent(parent); wp = _itemList.Items.FirstOrDefault(ip => parent == ip.MapTo && name == ip.Name); - if (null == wp) right = WebDavPath.Combine(name, right); - } while (parent != WebDavPath.Root && null == wp); + if (wp is null) + right = WebDavPath.Combine(name, right); + + } while (parent != WebDavPath.Root && wp is null); - if (null == wp) return null; + if (wp is null) + return null; string addhref = string.IsNullOrEmpty(right) ? string.Empty @@ -372,14 +407,14 @@ public void ProcessRename(string fullPath, string newName) bool changed = false; foreach (var link in _itemList.Items) { - if (!WebDavPath.IsParentOrSame(fullPath, link.MapTo)) + if (!WebDavPath.IsParentOrSame(fullPath, link.MapTo)) continue; link.MapTo = WebDavPath.ModifyParent(link.MapTo, fullPath, newPath); changed = true; } - if (!changed) + if (!changed) return; _itemCache.Invalidate(fullPath, newPath); @@ -392,7 +427,7 @@ public bool RenameLink(Link link, string newName) if (!link.IsRoot) return false; var ilink = _itemList.Items.FirstOrDefault(it => WebDavPath.PathEquals(it.MapTo, link.MapPath) && it.Name == link.Name); - if (null == ilink) return false; + if (ilink is null) return false; if (ilink.Name == newName) return true; ilink.Name = newName; @@ -419,7 +454,7 @@ public bool RenameLink(Link link, string newName) /// /// Логика хороша, но /// некоторые клиенты сначала делают структуру каталогов, а потом по одному переносят файлы, например, TotalCommander c плагином WebDAV v.2.9 - /// в таких условиях на каждый файл получится свой собственный линк, если делать правильно, т.е. в итоге расплодится миллин линков + /// в таких условиях на каждый файл получится свой собственный линк, если делать правильно, т.е. в итоге расплодится миллион линков /// поэтому делаем неправильно - копируем содержимое линков /// public async Task RemapLink(Link link, string destinationPath, bool doSave = true) @@ -430,7 +465,7 @@ public async Task RemapLink(Link link, string destinationPath, bool doSave if (link.IsRoot) { var rootlink = _itemList.Items.FirstOrDefault(it => WebDavPath.PathEquals(it.MapTo, link.MapPath) && it.Name == link.Name); - if (rootlink == null) + if (rootlink is null) return false; string oldmap = rootlink.MapTo; @@ -452,7 +487,7 @@ public async Task RemapLink(Link link, string destinationPath, bool doSave link.Size, DateTime.Now); - if (!res) + if (!res) return false; if (doSave) Save(); @@ -460,7 +495,5 @@ public async Task RemapLink(Link link, string destinationPath, bool doSave return true; } - - } -} \ No newline at end of file +} diff --git a/NWebDav/NWebDav.Server/Handlers/PropFindHandler.cs b/NWebDav/NWebDav.Server/Handlers/PropFindHandler.cs index c4ebd2e1..442d1063 100644 --- a/NWebDav/NWebDav.Server/Handlers/PropFindHandler.cs +++ b/NWebDav/NWebDav.Server/Handlers/PropFindHandler.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; +using System.Threading; using System.Threading.Tasks; using System.Xml.Linq; @@ -62,6 +63,8 @@ private enum PropertyMode /// public async Task HandleRequestAsync(IHttpContext httpContext, IStore store) { + var prepareWatch = Stopwatch.StartNew(); + // Obtain request and response var request = httpContext.Request; var response = httpContext.Response; @@ -113,13 +116,20 @@ public async Task HandleRequestAsync(IHttpContext httpContext, IStore stor break; } + prepareWatch.Stop(); +#if DEBUG + s_log.Log(LogLevel.Warning, () => $"Prepare for XML generation {prepareWatch.ElapsedMilliseconds} ms"); +#else + s_log.Log(LogLevel.Debug, () => $"Prepare for XML generation {prepareWatch.ElapsedMilliseconds} ms"); +#endif + var sw = Stopwatch.StartNew(); - // Obtain the status document + // Obtain the status document var xMultiStatus = new XElement(WebDavNamespaces.DavNsMultiStatus); var xDocument = new XDocument(xMultiStatus); - object locker = new object(); + SemaphoreSlim locker = new SemaphoreSlim(1); var degree = Environment.ProcessorCount > 1 && entries.Count > 5_000 ? Environment.ProcessorCount - 1 : 1; // Add all the properties @@ -151,7 +161,7 @@ public async Task HandleRequestAsync(IHttpContext httpContext, IStore stor // Check if the entry supports properties var propertyManager = entry.Entry.PropertyManager; - if (propertyManager != null) + if (propertyManager is not null) { // Handle based on the property mode if (propertyMode == PropertyMode.PropertyNames) @@ -187,16 +197,25 @@ public async Task HandleRequestAsync(IHttpContext httpContext, IStore stor // Add the status xPropStatValues.Add(new XElement(WebDavNamespaces.DavNsStatus, "HTTP/1.1 200 OK")); - lock (locker) + await locker.WaitAsync().ConfigureAwait(false); + try { // Add the property xMultiStatus.Add(xResponse); } - + finally + { + locker.Release(); + } }); var elapsed = sw.ElapsedMilliseconds; +#if DEBUG + s_log.Log(LogLevel.Warning, () => $"Response XML generation {elapsed} ms"); +#else s_log.Log(LogLevel.Debug, () => $"Response XML generation {elapsed} ms"); +#endif + // Stream the document await response.SendResponseAsync(DavStatusCode.MultiStatus, xDocument).ConfigureAwait(false); diff --git a/NWebDav/NWebDav.Server/Locking/InMemoryLockingManager.cs b/NWebDav/NWebDav.Server/Locking/InMemoryLockingManager.cs index c3882b80..740c73c7 100644 --- a/NWebDav/NWebDav.Server/Locking/InMemoryLockingManager.cs +++ b/NWebDav/NWebDav.Server/Locking/InMemoryLockingManager.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using System.Xml.Linq; using NWebDav.Server.Stores; @@ -54,7 +55,8 @@ private class ItemLockTypeDictionary : Dictionary private const string TokenScheme = "opaquelocktoken"; - private readonly IDictionary _itemLocks = new Dictionary(); + private readonly IDictionary _itemLocks = + new Dictionary(StringComparer.InvariantCultureIgnoreCase); private static readonly LockEntry[] s_supportedLocks = { @@ -62,6 +64,8 @@ private class ItemLockTypeDictionary : Dictionary new LockEntry(LockScope.Shared, LockType.Write) }; + private readonly SemaphoreSlim _locker = new SemaphoreSlim(1); + public LockResult Lock(IStoreItem item, LockType lockType, LockScope lockScope, XElement owner, WebDavUri lockRootUri, bool recursive, IEnumerable timeouts) { // Determine the expiration based on the first time-out @@ -70,7 +74,8 @@ public LockResult Lock(IStoreItem item, LockType lockType, LockScope lockScope, // Determine the item's key var key = item.UniqueKey; - lock (_itemLocks) + _locker.Wait(); + try { // Make sure the item is in the dictionary if (!_itemLocks.TryGetValue(key, out var itemLockTypeDictionary)) @@ -98,6 +103,10 @@ public LockResult Lock(IStoreItem item, LockType lockType, LockScope lockScope, // Return the active lock return new LockResult(DavStatusCode.Ok, GetActiveLockInfo(itemLockInfo)); } + finally + { + _locker.Release(); + } } public DavStatusCode Unlock(IStoreItem item, WebDavUri lockTokenUri) @@ -110,7 +119,8 @@ public DavStatusCode Unlock(IStoreItem item, WebDavUri lockTokenUri) // Determine the item's key var key = item.UniqueKey; - lock (_itemLocks) + _locker.Wait(); + try { // Make sure the item is in the dictionary if (!_itemLocks.TryGetValue(key, out var itemLockTypeDictionary)) @@ -146,6 +156,10 @@ public DavStatusCode Unlock(IStoreItem item, WebDavUri lockTokenUri) } } } + finally + { + _locker.Release(); + } // Item cannot be unlocked (token cannot be found) return DavStatusCode.PreconditionFailed; @@ -161,7 +175,8 @@ public LockResult RefreshLock(IStoreItem item, bool recursiveLock, IEnumerable GetActiveLockInfo(IStoreItem item) // Determine the item's key var key = item.UniqueKey; - lock (_itemLocks) + _locker.Wait(); + try { // Make sure the item is in the dictionary if (!_itemLocks.TryGetValue(key, out var itemLockTypeDictionary)) @@ -202,6 +222,10 @@ public IEnumerable GetActiveLockInfo(IStoreItem item) // Return all non-expired locks return itemLockTypeDictionary.SelectMany(kv => kv.Value).Where(l => !l.IsExpired).Select(GetActiveLockInfo).ToList(); } + finally + { + _locker.Release(); + } } public IEnumerable GetSupportedLocks(IStoreItem item) @@ -215,7 +239,8 @@ public bool IsLocked(IStoreItem item) // Determine the item's key var key = item.UniqueKey; - lock (_itemLocks) + _locker.Wait(); + try { // Make sure the item is in the dictionary if (_itemLocks.TryGetValue(key, out var itemLockTypeDictionary)) @@ -227,6 +252,10 @@ public bool IsLocked(IStoreItem item) } } } + finally + { + _locker.Release(); + } // No lock return false; @@ -246,7 +275,8 @@ public bool HasLock(IStoreItem item, WebDavUri lockTokenUri) if (lockToken == null) return false; - lock (_itemLocks) + _locker.Wait(); + try { // Make sure the item is in the dictionary if (!_itemLocks.TryGetValue(key, out var itemLockTypeDictionary)) @@ -261,6 +291,10 @@ public bool HasLock(IStoreItem item, WebDavUri lockTokenUri) return true; } } + finally + { + _locker.Release(); + } // No lock return false; diff --git a/WebDavMailRuCloudStore/CloudManager.cs b/WebDavMailRuCloudStore/CloudManager.cs index 6fdc2658..d89ad63e 100644 --- a/WebDavMailRuCloudStore/CloudManager.cs +++ b/WebDavMailRuCloudStore/CloudManager.cs @@ -1,6 +1,8 @@ -using System.Collections.Concurrent; +using System; +using System.Collections.Concurrent; using System.Net; using System.Security.Principal; +using System.Threading; using YaR.Clouds.Base; namespace YaR.Clouds.WebDavStore @@ -9,34 +11,38 @@ public static class CloudManager { private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(CloudManager)); - private static readonly ConcurrentDictionary CloudCache = new(); + private static readonly ConcurrentDictionary CloudCache = new(StringComparer.InvariantCultureIgnoreCase); public static CloudSettings Settings { get; set; } - public static Cloud Instance(IIdentity identityi) + private static SemaphoreSlim _locker = new SemaphoreSlim(1); + + public static Cloud Instance(IIdentity identity) { - var identity = (HttpListenerBasicIdentity) identityi; - string key = identity.Name + identity.Password; + var basicIdentity = (HttpListenerBasicIdentity) identity; + string key = basicIdentity.Name + basicIdentity.Password; if (CloudCache.TryGetValue(key, out var cloud)) return cloud; - lock (Locker) + _locker.Wait(); + try { - if (CloudCache.TryGetValue(key, out cloud)) + if (CloudCache.TryGetValue(key, out cloud)) return cloud; - cloud = CreateCloud(identity); + cloud = CreateCloud(basicIdentity); - if (!CloudCache.TryAdd(key, cloud)) - CloudCache.TryGetValue(key, out cloud); + CloudCache.TryAdd(key, cloud); + } + finally + { + _locker.Release(); } return cloud; } - private static readonly object Locker = new(); - private static Cloud CreateCloud(HttpListenerBasicIdentity identity) { Logger.Info($"Cloud instance created for {identity.Name}"); diff --git a/WebDavMailRuCloudStore/StoreBase/LocalStoreCollection.cs b/WebDavMailRuCloudStore/StoreBase/LocalStoreCollection.cs index 4a44ca13..aaef5c29 100644 --- a/WebDavMailRuCloudStore/StoreBase/LocalStoreCollection.cs +++ b/WebDavMailRuCloudStore/StoreBase/LocalStoreCollection.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Security.Cryptography; +using System.Threading; using System.Threading.Tasks; using NWebDav.Server; using NWebDav.Server.Http; @@ -66,19 +67,25 @@ public IEnumerable Items { get { - if (null != _items) + if (_items is not null) return _items; - lock (_itemsLocker) + _locker.Wait(); + try { _items ??= GetItemsAsync(_context).Result; } + finally + { + _locker.Release(); + } + return _items; } } private IEnumerable _items; - private readonly object _itemsLocker = new(); + private readonly SemaphoreSlim _locker = new SemaphoreSlim(1); //public IEnumerable Folders => Items.Where(it => it is LocalStoreCollection).Cast(); //public IEnumerable Files => Items.Where(it => it is LocalStoreItem).Cast(); From 9dda762513668b2686c9f91e1c68cecdad43a29f Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Mon, 16 Oct 2023 22:08:02 +0300 Subject: [PATCH 34/77] Code cleanup --- .../YandexDisk/YadWebV2/DtoImportYadWeb.cs | 3 +- .../YadWebV2/Requests/YaDCommonRequest.cs | 7 +- .../YadWebV2/Requests/YadAuthLoginRequest.cs | 70 ------------------- .../Requests/YadAuthPreAuthRequestResult.cs | 54 -------------- 4 files changed, 5 insertions(+), 129 deletions(-) delete mode 100644 MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YadAuthLoginRequest.cs delete mode 100644 MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YadAuthPreAuthRequestResult.cs diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/DtoImportYadWeb.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/DtoImportYadWeb.cs index 7c78d0d8..b58c59ba 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/DtoImportYadWeb.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/DtoImportYadWeb.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.Linq; using System.Security.Authentication; -using YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests.Types; using YaR.Clouds.Base.Repos.YandexDisk.YadWebV2.Models; using YaR.Clouds.Base.Requests.Types; @@ -84,7 +83,7 @@ public static File ToFile(this FolderInfoDataResource data) public static File ToFile(this YadItemInfoRequestData data) { - var path = data.Path.Remove(0, 5); // remove "/disk" + var path = data.Path.Remove(0, "/disk".Length); var res = new File(path, data.Meta.Size) { diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YaDCommonRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YaDCommonRequest.cs index 64a951f8..9fe969aa 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YaDCommonRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YaDCommonRequest.cs @@ -49,9 +49,10 @@ public YaDCommonRequest With(T model, out TOut resOUt) return this; } - protected override string RelationalUri => "/models/?_m=" + _postData.Models - .Select(m => m.Name) - .Aggregate((current, next) => current + "," + next); + protected override string RelationalUri + => string.Concat("/models/?_m=", _postData.Models + .Select(m => m.Name) + .Aggregate((current, next) => current + "," + next)); protected override RequestResponse DeserializeMessage( NameValueCollection responseHeaders, System.IO.Stream stream) diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YadAuthLoginRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YadAuthLoginRequest.cs deleted file mode 100644 index 3a5b471e..00000000 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YadAuthLoginRequest.cs +++ /dev/null @@ -1,70 +0,0 @@ -using System.Collections.Generic; -using System.Net; -using System.Net.Http; -using Newtonsoft.Json; -using YaR.Clouds.Base.Requests; - -namespace YaR.Clouds.Base.Repos.YandexDisk.YadWebV2.Requests -{ - class YadAuthLoginRequest : BaseRequestJson - { - private readonly IAuth _auth; - private readonly string _csrf; - private readonly string _uuid; - - public YadAuthLoginRequest(HttpCommonSettings settings, IAuth auth, string csrf, string uuid) - : base(settings, auth) - { - _auth = auth; - _csrf = csrf; - _uuid = uuid; - } - - protected override string RelationalUri => "/registration-validations/auth/multi_step/start"; - - protected override HttpWebRequest CreateRequest(string baseDomain = null) - { - var request = base.CreateRequest("https://passport.yandex.ru"); - request.Referer = "https://passport.yandex.ru/auth/list?from=cloud&origin=disk_landing2_web_signin_ru&retpath=https%3A%2F%2Fdisk.yandex.ru%2Fclient%2Fdisk&backpath=https%3A%2F%2Fdisk.yandex.ru&mode=edit"; - request.Headers["Sec-Fetch-Mode"] = "cors"; - request.Headers["Sec-Fetch-Site"] = "same-origin"; - - return request; - } - - protected override byte[] CreateHttpContent() - { - var keyValues = new List> - { - new("csrf_token", _csrf), - new("login", _auth.Login), - new("process_uuid", _uuid), - new("retpath", "https://disk.yandex.ru/client/disk"), - new("origin", "disk_landing2_web_signin_ru"), - new("service", "cloud") - }; - FormUrlEncodedContent z = new FormUrlEncodedContent(keyValues); - var d = z.ReadAsByteArrayAsync().Result; - return d; - } - } - - - - class YadAuthLoginRequestResult - { - public bool HasError => Status == "error"; - - [JsonProperty("status")] - public string Status { get; set; } - - [JsonProperty("track_id")] - public string TrackId { get; set; } - - [JsonProperty("csrf_token")] - public string Csrf { get; set; } - - [JsonProperty("errors")] - public List Errors { get; set; } - } -} \ No newline at end of file diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YadAuthPreAuthRequestResult.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YadAuthPreAuthRequestResult.cs deleted file mode 100644 index eb595293..00000000 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YadAuthPreAuthRequestResult.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System.Collections.Specialized; -using System.Net; -using System.Text.RegularExpressions; -using YaR.Clouds.Base.Requests; - -namespace YaR.Clouds.Base.Repos.YandexDisk.YadWebV2.Requests -{ - class YadPreAuthRequest : BaseRequestString - { - public YadPreAuthRequest(HttpCommonSettings settings, IAuth auth) - : base(settings, auth) - { - } - - protected override HttpWebRequest CreateRequest(string baseDomain = null) - { - var request = base.CreateRequest("https://passport.yandex.ru"); - return request; - } - - protected override string RelationalUri => "/auth"; - - //protected override byte[] CreateHttpContent() - //{ - // string data = $"Login={Uri.EscapeUriString(Auth.Login)}&Domain={CommonSettings.Domain}&Password={Uri.EscapeUriString(Auth.Password)}"; - - // return Encoding.UTF8.GetBytes(data); - //} - - protected override RequestResponse DeserializeMessage(NameValueCollection responseHeaders, string responseText) - { - var matchCsrf = Regex.Match(responseText, @"""csrf"":""(?.*?)"""); - var matchUuid = Regex.Match(responseText, @"""process_uuid"":""(?.*?)"""); //var matchUuid = Regex.Match(responseText, @"process_uuid(?\S+?)""); - - var msg = new RequestResponse - { - Ok = matchCsrf.Success && matchUuid.Success, - Result = new YadAuthPreAuthRequestResult - { - Csrf = matchCsrf.Success ? matchCsrf.Groups["csrf"].Value : string.Empty, - ProcessUUID = matchUuid.Success ? matchUuid.Groups["uuid"].Value : string.Empty - } - }; - - return msg; - } - } - - class YadAuthPreAuthRequestResult - { - public string Csrf { get; set; } - public string ProcessUUID { get; set; } - } -} From a1c39ee4eafa4c2be21fff265e80c0ac428c259d Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Tue, 17 Oct 2023 10:06:02 +0300 Subject: [PATCH 35/77] =?UTF-8?q?=D0=A1=D0=BC=D0=B5=D0=BD=D0=B0=20=D0=BD?= =?UTF-8?q?=D0=B0=D0=B7=D0=B2=D0=B0=D0=BD=D0=B8=D1=8F=20=D0=BF=D0=B5=D1=80?= =?UTF-8?q?=D0=B5=D0=BC=D0=B5=D0=BD=D0=BD=D0=BE=D0=B9=20=D0=B2=20=D0=BA?= =?UTF-8?q?=D0=BE=D0=BC=D0=BC=D0=B5=D0=BD=D1=82=D0=B0=D1=80=D0=B8=D0=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs index e2537885..1c3a1d7a 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs @@ -111,7 +111,7 @@ public virtual async Task MakeRequestAsync() /* * The debug add the following to a watch list: - * System.Text.Encoding.UTF8.GetString(content) + * System.Text.Encoding.UTF8.GetString(requestContent) */ #if NET48 await requestStream.WriteAsync(requestContent, 0, requestContent.Length).ConfigureAwait(false); From 462aa783415647043ac5fd5da103065d407f81d5 Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Tue, 17 Oct 2023 18:12:18 +0300 Subject: [PATCH 36/77] =?UTF-8?q?=D0=91=D0=BE=D1=80=D1=8C=D0=B1=D0=B0=20?= =?UTF-8?q?=D0=B7=D0=B0=20=D1=81=D0=BA=D0=BE=D1=80=D0=BE=D1=81=D1=82=D1=8C?= =?UTF-8?q?.=20=D0=9A=D1=80=D0=B0=D1=82=D0=BD=D0=BE=D0=B5=20=D1=81=D0=BE?= =?UTF-8?q?=D0=BA=D1=80=D0=B0=D1=89=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B2=D1=80?= =?UTF-8?q?=D0=B5=D0=BC=D0=B5=D0=BD=D0=B8=20=D1=87=D1=82=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20=D0=B1=D0=BE=D0=BB=D1=8C=D1=88=D0=B8=D1=85=20=D0=B4?= =?UTF-8?q?=D0=B8=D1=80=D0=B5=D0=BA=D1=82=D0=BE=D1=80=D0=B8=D0=B9=20=D1=81?= =?UTF-8?q?=20=D0=B4=D0=B5=D1=81=D1=8F=D1=82=D0=BA=D0=B0=D0=BC=D0=B8=20?= =?UTF-8?q?=D1=82=D1=8B=D1=81=D1=8F=D1=87=20=D1=84=D0=B0=D0=B9=D0=BB=D0=BE?= =?UTF-8?q?=D0=B2.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../YandexDisk/YadWebV2/DtoImportYadWeb.cs | 81 ++++++++---- .../YandexDisk/YadWebV2/Models/FolderInfo.cs | 13 +- .../Models/YadResourceStatsPostModel.cs | 3 + .../YandexDisk/YadWebV2/YadWebRequestRepo.cs | 124 ++++++++++++++---- .../Base/Requests/BaseRequest.cs | 39 ++++++ MailRuCloud/MailRuCloudApi/Base/WebDavPath.cs | 116 ++++++++++------ MailRuCloud/MailRuCloudApi/Cloud.cs | 11 +- NWebDav/NWebDav.Server/Locking/ActiveLock.cs | 2 +- WDMRC.Console/Log4NetAdapter.cs | 2 +- WDMRC.Console/Payload.cs | 9 +- .../StoreBase/LocalStoreCollection.cs | 2 +- WinServiceInstaller/ServiceConfigurator.cs | 2 +- 12 files changed, 306 insertions(+), 98 deletions(-) diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/DtoImportYadWeb.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/DtoImportYadWeb.cs index b58c59ba..58738889 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/DtoImportYadWeb.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/DtoImportYadWeb.cs @@ -1,6 +1,6 @@ using System; using System.Collections.Concurrent; -using System.Collections.Generic; +using System.IO; using System.Linq; using System.Security.Authentication; using YaR.Clouds.Base.Repos.YandexDisk.YadWebV2.Models; @@ -33,34 +33,71 @@ public static AccountInfoResult ToAccountInfo(this YadResponseModel( - fi - .Where(it => it.Type == "file") - .Select(f => f.ToFile()) - .ToGroupedFiles() - .Select(item => new KeyValuePair(item.FullPath, item)), - StringComparer.InvariantCultureIgnoreCase); + string diskPath = WebDavPath.Combine("/disk", path); + var fi = folderData.Resources; - res.Folders = new ConcurrentDictionary( - fi - .Where(it => it.Type == "dir") - .Select(f => f.ToFolder()) - .Select(item => new KeyValuePair(item.FullPath, item)), + folder.Files = new ConcurrentDictionary( + fi.Where(it => it.Type == "file") + .Select(f => f.ToFile()) + .ToGroupedFiles() + .Select(f => new System.Collections.Generic.KeyValuePair(f.FullPath, f)), + StringComparer.InvariantCultureIgnoreCase); + folder.Folders = new ConcurrentDictionary( + fi.Where(it => it.Type == "dir" && + // Пропуск элемента с информацией папки о родительской папке, + // этот элемент добавляется в выборки, если читается + // не всё содержимое папки, а делается только вырезка + it.Path != diskPath) + .Select(f => f.ToFolder()) + .Select(f => new System.Collections.Generic.KeyValuePair(f.FullPath, f)), StringComparer.InvariantCultureIgnoreCase); - return res; + return folder; + } + + public static Folder MergeData(this Folder folder, YadFolderInfoRequestData folderData, string path) + { + if (folderData is null) + throw new ArgumentNullException(nameof(folderData)); + if (folder is null) + throw new ArgumentNullException(nameof(folder)); + + string diskPath = WebDavPath.Combine("/disk", path); + var fi = folderData.Resources; + + foreach (var item in fi.Where(it => it.Type == "file") + .Select(f => f.ToFile()) + .ToGroupedFiles()) + { + folder.Files.AddOrUpdate(item.FullPath, item, (_, _) => item); + } + foreach (var item in fi.Where(it => it.Type == "dir" && + // Пропуск элемента с информацией папки о родительской папке, + // этот элемент добавляется в выборки, если читается + // не всё содержимое папки, а делается только вырезка + it.Path != diskPath) + .Select(f => f.ToFolder())) + { + folder.Folders.AddOrUpdate(item.FullPath, item, (_, _) => item); + } + + return folder; } public static File ToFile(this FolderInfoDataResource data) @@ -116,7 +153,7 @@ public static RenameResult ToRenameResult(this YadResponseModel> ToKvp(int index) { @@ -49,8 +50,13 @@ public override IEnumerable> ToKvp(int index) yield return new KeyValuePair($"order.{index}", Order.ToString()); yield return new KeyValuePair($"sort.{index}", SortBy); - yield return new KeyValuePair($"offset.{index}", Offset.ToString()); - yield return new KeyValuePair($"amount.{index}", Amount.ToString()); + if (Offset > 0 || Amount < int.MaxValue) + { + yield return new KeyValuePair($"offset.{index}", Offset.ToString()); + yield return new KeyValuePair($"amount.{index}", Amount.ToString()); + } + if (WithParent) + yield return new KeyValuePair($"withParent.{index}", "1"); } } @@ -124,6 +130,9 @@ class Meta [JsonProperty("short_url")] public string UrlShort { get; set; } + + [JsonProperty("total_results_count")] + public int? TotalEntityCount { get; set; } } class Size diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/YadResourceStatsPostModel.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/YadResourceStatsPostModel.cs index f4ba6502..bb0654dc 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/YadResourceStatsPostModel.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/YadResourceStatsPostModel.cs @@ -28,6 +28,9 @@ public override IEnumerable> ToKvp(int index) class YadResourceStatsRequestData : YadModelDataBase { + /// + /// Здесь количество файлов, только файлов, не директорий, на всех уровнях вложенности. + /// [JsonProperty("files_count")] public long FilesCount { get; set; } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/YadWebRequestRepo.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/YadWebRequestRepo.cs index 8f82e95a..43642e2c 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/YadWebRequestRepo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/YadWebRequestRepo.cs @@ -192,6 +192,8 @@ public async Task DoUpload(HttpClient client, PushStreamConten private const string YadMediaPath = "/Media.wdyad"; + private const int FirstReadEntriesCount = 200; + public async Task FolderInfo(RemotePath path, int offset = 0, int limit = int.MaxValue, int depth = 1) { if (path.IsLink) @@ -201,9 +203,9 @@ public async Task FolderInfo(RemotePath path, int offset = 0, int limit return await MediaFolderInfo(path.Path); // YaD perform async deletion - YadResponseModel itemInfo = null; + YadResponseModel entryInfo = null; YadResponseModel folderInfo = null; - YadResponseModel resourceStats = null; + YadResponseModel entryStats = null; bool hasRemoveOp = _lastRemoveOperation != null && WebDavPath.IsParentOrSame(path.Path, _lastRemoveOperation.Path) && @@ -216,37 +218,110 @@ public async Task FolderInfo(RemotePath path, int offset = 0, int limit Logger.Debug("Has remove op, sleep before"); return doPreSleep; }, - () => new YaDCommonRequest(HttpSettings, (YadWebAuth) Authent) - .With(new YadItemInfoPostModel(path.Path), out itemInfo) - .With(new YadFolderInfoPostModel(path.Path), out folderInfo) - .With(new YadResourceStatsPostModel(path.Path), out resourceStats) + () => new YaDCommonRequest(HttpSettings, (YadWebAuth)Authent) + .With(new YadItemInfoPostModel(path.Path), out entryInfo) + .With(new YadFolderInfoPostModel(path.Path) { WithParent = true, Amount = FirstReadEntriesCount }, out folderInfo) + .With(new YadResourceStatsPostModel(path.Path), out entryStats) .MakeRequestAsync() .Result, _ => { - var doAgain = hasRemoveOp && - folderInfo.Data.Resources.Any(r => - WebDavPath.PathEquals(r.Path.Remove(0, "/disk".Length), _lastRemoveOperation.Path)); + bool doAgain = false; + if (hasRemoveOp && _lastRemoveOperation != null) + { + string cmpPath = WebDavPath.Combine("/disk", _lastRemoveOperation.Path); + doAgain = hasRemoveOp && + folderInfo.Data.Resources.Any(r => WebDavPath.PathEquals(r.Path, cmpPath)); + } if (doAgain) Logger.Debug("Remove op still not finished, let's try again"); return doAgain; - }, + }, TimeSpan.FromMilliseconds(OperationStatusCheckIntervalMs), OperationStatusCheckRetryCount); - - var itdata = itemInfo?.Data; - switch (itdata?.Type) + + var entryData = entryInfo?.Data; + if (entryData?.Type is null) + return null; + if (entryData.Type == "file") + return entryData.ToFile(); + + Folder folder = folderInfo.Data.ToFolder(entryData, entryStats.Data, path.Path); + + // Если количество полученных элементов списка меньше максимального запрошенного числа элементов, + // даже с учетом, что в число элементов сверх запрошенного входит информация + // о папке-контейнере (папке, чей список элементов запросили), то считаем, + // что получен полный список содержимого папки и возвращает данные. + if ((folderInfo.Data.Resources?.Count ?? int.MaxValue) < FirstReadEntriesCount) + return folder; + // В противном случае делаем несколько параллельных выборок для ускорения чтения списков с сервера. + + int entryCount = folderInfo?.Data?.Resources.FirstOrDefault()?.Meta?.TotalEntityCount ?? 1; + int maxParallel = 1; + int parallelCount = int.MaxValue; + if (entryCount < 300 + FirstReadEntriesCount) { - case null: - return null; - case "file": - return itdata.ToFile(); - default: - { - var entry = folderInfo.Data.ToFolder(itemInfo.Data, resourceStats.Data, path.Path); - return entry; - } + maxParallel = 1; + parallelCount = int.MaxValue; + } + else + if (entryCount >= 3000 + FirstReadEntriesCount) + { + maxParallel = 10; + // Читать данные с сервера будем немного внахлест чтобы случайно не пропустить что-либо. + // Дубликаты с одинаковыми путями самоустранятся при сливании в один словарь. + parallelCount = entryCount / 10 + 5; + } + else + { + maxParallel = entryCount / 300 + 1; + parallelCount = 304; } + + Retry.Do( + () => + { + var doPreSleep = hasRemoveOp ? TimeSpan.FromMilliseconds(OperationStatusCheckIntervalMs) : TimeSpan.Zero; + if (doPreSleep > TimeSpan.Zero) + Logger.Debug("Has remove op, sleep before"); + return doPreSleep; + }, + () => + { + Parallel.For(0, maxParallel, (int index) => + { + YadResponseResult noReturn = new YaDCommonRequest(HttpSettings, (YadWebAuth)Authent) + .With(new YadFolderInfoPostModel(path.Path) + { + Offset = FirstReadEntriesCount + parallelCount * index - 2 /* отступ для чтения внахлест */, + Amount = index == maxParallel - 1 ? int.MaxValue : parallelCount + }, out YadResponseModel folderPartInfo) + .MakeRequestAsync() + .Result; + + if (folderPartInfo?.Data is not null && folderPartInfo.Error is null) + folder.MergeData(folderPartInfo.Data, entryData.Path); + }); + YadResponseResult retValue = null; + return retValue; + }, + _ => + { + //TODO: Здесь полностью неправильная проверка + bool doAgain = false; + if (hasRemoveOp && _lastRemoveOperation != null) + { + string cmpPath = WebDavPath.Combine("/disk", _lastRemoveOperation.Path); + doAgain = hasRemoveOp && + folderInfo.Data.Resources.Any(r => WebDavPath.PathEquals(r.Path, cmpPath)); + } + if (doAgain) + Logger.Debug("Remove op still not finished, let's try again"); + return doAgain; + }, + TimeSpan.FromMilliseconds(OperationStatusCheckIntervalMs), OperationStatusCheckRetryCount); + + return folder; } @@ -276,9 +351,10 @@ private async Task MediaFolderInfo(string path) .MakeRequestAsync() .Result; - var entry = folderInfo.Data.ToFolder(null, null, path); + Folder folder = folderInfo.Data.ToFolder(null, null, path); + folder.MergeData(folderInfo.Data, path); - return entry; + return folder; } private async Task MediaFolderRootInfo() diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs index 1c3a1d7a..e7ecabee 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs @@ -3,6 +3,8 @@ using System.Diagnostics; using System.IO; using System.Net; +using System.Net.Sockets; +using System.Threading; using System.Threading.Tasks; using YaR.Clouds.Base.Repos; using YaR.Clouds.Base.Repos.MailRuCloud; @@ -196,6 +198,43 @@ public virtual async Task MakeRequestAsync() #endif } } + catch (WebException iex2) when (iex2?.InnerException is System.Net.Http.HttpRequestException iex1 && + iex1?.InnerException is SocketException iex) + { + /* + * Здесь мы ловим ошибку + * SocketException: Попытка установить соединение была безуспешной, + * т.к. от другого компьютера за требуемое время не получен нужный отклик, + * или было разорвано уже установленное соединение из-за неверного отклика + * уже подключенного компьютера. + * + * Возможно превышено максимальное количество подключений к серверу. + * Просто повторяем запрос после небольшого ожидания. + */ + if (retry <= 0) + { + string msg = "Can not connect to server. Increase timeout in response-timeout-sec parameter."; +#if DEBUG + Logger.Warn(msg); +#else + Logger.Debug(msg); +#endif + throw; + } + else + { + isRetryState = true; + string msg = watch.ElapsedMilliseconds < 1000 + ? "Connection to server terminated immediately, retrying after couple of seconds..." + : $"Connection lost after {watch.ElapsedMilliseconds/1000} seconds, retrying after couple of seconds..."; +#if DEBUG + Logger.Warn(msg); +#else + Logger.Debug(msg); +#endif + Thread.Sleep(TimeSpan.FromSeconds(2)); + } + } // ReSharper disable once RedundantCatchClause #pragma warning disable 168 catch (Exception ex) diff --git a/MailRuCloud/MailRuCloudApi/Base/WebDavPath.cs b/MailRuCloud/MailRuCloudApi/Base/WebDavPath.cs index d83ff0d4..d663b374 100644 --- a/MailRuCloud/MailRuCloudApi/Base/WebDavPath.cs +++ b/MailRuCloud/MailRuCloudApi/Base/WebDavPath.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Text; namespace YaR.Clouds.Base { @@ -13,67 +14,98 @@ public static bool IsFullPath(string path) public static string Combine(string a, string b) { - a = Clean(a); - b = Clean(b); - a = a.Trim('/'); - b = b.TrimStart('/'); - string res = a + (string.IsNullOrEmpty(b) ? "" : "/" + b); - if (!res.StartsWith("/")) res = "/" + res; - return res; + if (a == null) + throw new ArgumentNullException(nameof(a)); + + if (b == null) + return Clean(a, false); + + StringBuilder right = CleanSb(b, false); + + if (a == "/disk") + { + right.Insert(0, a); + if (right[right.Length - 1] == '/') + right.Remove(right.Length - 1, 1); + return right.ToString(); + } + + StringBuilder left = CleanSb(a, false); + +#if NET48 + left.Append(right.ToString()); +#else + left.Append(right); +#endif + + return left.ToString(); } - public static string Clean(string path, bool doAddFinalSeparator = false) + public static StringBuilder CleanSb(string path, bool doAddFinalSeparator = false) { try { - string res = path?.Replace("\\", "/").Replace("//", "/") - ?? throw new ArgumentNullException(nameof(path)); - if (res.Length > 1 && !doAddFinalSeparator) - return res.TrimEnd('/'); - if (doAddFinalSeparator && !res.EndsWith("/")) - res += Separator; - return res; + if (path == null) + throw new ArgumentNullException(nameof(path)); + + StringBuilder text = new StringBuilder(path); + text.Replace("\\", "/").Replace("//", "/"); + if (doAddFinalSeparator) + { + if (text.Length == 0 || text[^1] != '/') + text.Append('/'); + } + else + { + if (text.Length > 1 && text[^1] == '/') + text.Remove(text.Length - 1, 1); + } + + if (text.Length == 0 || text[0] != '/') + text.Insert(0, '/'); + + return text; } catch (Exception e) { Console.WriteLine(e); throw; } + } + public static string Clean(string path, bool doAddFinalSeparator = false) + { + return CleanSb(path, doAddFinalSeparator).ToString(); } public static string Parent(string path, string cmdPrefix = ">>") { - //TODO: refact - path = path.TrimEnd('/'); - // cause we use >> as a sign of special command int cmdPos = path.IndexOf(cmdPrefix, StringComparison.Ordinal); - - int pos = cmdPos > 0 - ? path.LastIndexOf("/", 0, cmdPos + 1, StringComparison.Ordinal) - : path.LastIndexOf("/", StringComparison.Ordinal); - - return pos > 0 - ? path.Substring(0, pos) - : "/"; + int len = path.EndsWith("/") ? path.Length - 1 : path.Length; + if (len == 0) + return Root; + int slash = path.LastIndexOf("/", len - 1, cmdPos < 0 ? len : len - cmdPos, StringComparison.Ordinal); + + return slash > 0 + ? path.Substring(0, slash) + : Root; } public static string Name(string path, string cmdPrefix = ">>") { - //TODO: refact - path = path.TrimEnd('/'); - // cause we use >> as a sign of special command int cmdPos = path.IndexOf(cmdPrefix, StringComparison.Ordinal); + int len = path.EndsWith("/") ? path.Length - 1 : path.Length; + if (len == 0) + return ""; + int slash = path.LastIndexOf("/", len - 1, cmdPos < 0 ? len : len - cmdPos, StringComparison.Ordinal); - int pos = cmdPos > 0 - ? path.LastIndexOf("/", 0, cmdPos + 1, StringComparison.Ordinal) - : path.LastIndexOf("/", StringComparison.Ordinal); + if (slash < 0 && len == path.Length) + return path; - string res = path.Substring(pos+1); - return res; + return path.Substring(slash + 1, len - slash - 1); } public static string Root => "/"; @@ -125,17 +157,25 @@ public static string ModifyParent(string path, string oldParent, string newParen if (!IsParentOrSame(oldParent, path)) return path; - path = Clean(path, true); - oldParent = Clean(oldParent, true); + if (path is null) + throw new ArgumentNullException(nameof(path)); + if (oldParent is null) + throw new ArgumentNullException(nameof(oldParent)); + if (newParent is null) + throw new ArgumentNullException(nameof(newParent)); + if (path.Length < oldParent.Length) + throw new ArgumentException($"Value of {nameof(oldParent)} is longer then length of {nameof(path)}"); - path = path.Remove(0, oldParent.Length); + StringBuilder pathTmp = CleanSb(path, true); + StringBuilder oldParentTmp = CleanSb(oldParent, true); + path = pathTmp.Remove(0, oldParentTmp.Length).ToString(); return Combine(newParent, path); } public static bool PathEquals(string path1, string path2) { - return string.Compare(Clean(path1), Clean(path2), StringComparison.InvariantCultureIgnoreCase) == 0; + return Clean(path1).Equals(Clean(path2), StringComparison.InvariantCultureIgnoreCase); } public static string EscapeDataString(string path) diff --git a/MailRuCloud/MailRuCloudApi/Cloud.cs b/MailRuCloud/MailRuCloudApi/Cloud.cs index 9b3637ee..78224b11 100644 --- a/MailRuCloud/MailRuCloudApi/Cloud.cs +++ b/MailRuCloud/MailRuCloudApi/Cloud.cs @@ -94,10 +94,13 @@ public virtual async Task GetPublicItemAsync(Uri url, ItemType itemType ///// List of the items. public virtual async Task GetItemAsync(string path, ItemType itemType = ItemType.Unknown, bool resolveLinks = true) { - //TODO: вообще, всё плохо стало, всё запуталось, всё надо переписать - var uriMatch = Regex.Match(path, @"\A/(?https://cloud\.mail\.\w+/public/\S+/\S+(/.*)?)\Z"); - if (uriMatch.Success) - return await GetPublicItemAsync(new Uri(uriMatch.Groups["uri"].Value, UriKind.Absolute), itemType); + if (Settings.Protocol == Protocol.WebM1Bin || Settings.Protocol == Protocol.WebV2) + { + //TODO: вообще, всё плохо стало, всё запуталось, всё надо переписать + var uriMatch = Regex.Match(path, @"\A/(?https://cloud\.mail\.\w+/public/\S+/\S+(/.*)?)\Z"); + if (uriMatch.Success) + return await GetPublicItemAsync(new Uri(uriMatch.Groups["uri"].Value, UriKind.Absolute), itemType); + } if (Account.IsAnonymous) return null; diff --git a/NWebDav/NWebDav.Server/Locking/ActiveLock.cs b/NWebDav/NWebDav.Server/Locking/ActiveLock.cs index d62344a6..fb53388b 100644 --- a/NWebDav/NWebDav.Server/Locking/ActiveLock.cs +++ b/NWebDav/NWebDav.Server/Locking/ActiveLock.cs @@ -34,7 +34,7 @@ public XElement ToXml() new XElement(WebDavNamespaces.DavNsLockScope, new XElement(WebDavNamespaces.DavNs + XmlHelper.GetXmlValue(Scope))), new XElement(WebDavNamespaces.DavNsDepth, Depth == int.MaxValue ? "infinity" : Depth.ToString(CultureInfo.InvariantCulture)), new XElement(WebDavNamespaces.DavNsOwner, Owner), - new XElement(WebDavNamespaces.DavNsTimeout, Timeout == -1 ? "Infinite" : "Second-" + Timeout.ToString(CultureInfo.InvariantCulture)), + new XElement(WebDavNamespaces.DavNsTimeout, Timeout == -1 ? "Infinite" : string.Concat("Second-", Timeout.ToString(CultureInfo.InvariantCulture))), new XElement(WebDavNamespaces.DavNsLockToken, new XElement(WebDavNamespaces.DavNsHref, LockToken.AbsoluteUri)), new XElement(WebDavNamespaces.DavNsLockRoot, new XElement(WebDavNamespaces.DavNsHref, LockRoot.AbsoluteUri))); } diff --git a/WDMRC.Console/Log4NetAdapter.cs b/WDMRC.Console/Log4NetAdapter.cs index c3456b4e..f5283826 100644 --- a/WDMRC.Console/Log4NetAdapter.cs +++ b/WDMRC.Console/Log4NetAdapter.cs @@ -37,7 +37,7 @@ public void Log(LogLevel logLevel, Func messageFunc, Exception exception } catch (Exception e) { - msg = "Failed to get error message: " + e.Message; + msg = string.Concat("Failed to get error message: ", e.Message); } Log(logLevel, msg, exception); diff --git a/WDMRC.Console/Payload.cs b/WDMRC.Console/Payload.cs index fcf38bb8..0de62abf 100644 --- a/WDMRC.Console/Payload.cs +++ b/WDMRC.Console/Payload.cs @@ -83,8 +83,9 @@ public static void Run(CommandLineOptions options) httpListener.AuthenticationSchemes = httpListenerOptions.AuthenticationScheme; httpListener.Start(); - Logger.Info( - $"WebDAV server running at {httpListenerOptions.Prefixes.Aggregate((current, next) => current + ", " + next)}"); + Logger.Info( + $"WebDAV server running at {httpListenerOptions.Prefixes.Aggregate( + (current, next) => string.Concat(current, ", ", next))}"); // Start dispatching requests var t = DispatchHttpRequestsAsync(httpListener, options.MaxThreadCount); @@ -250,8 +251,8 @@ private static string GetFrameworkDescription() // detect .NET Mono Type type = Type.GetType("Mono.Runtime"); - if (type == null) - return ".NET Framework " + Environment.Version; + if (type == null) + return string.Concat(".NET Framework ", Environment.Version); MethodInfo displayName = type.GetMethod("GetDisplayName", BindingFlags.NonPublic | BindingFlags.Static); return displayName != null diff --git a/WebDavMailRuCloudStore/StoreBase/LocalStoreCollection.cs b/WebDavMailRuCloudStore/StoreBase/LocalStoreCollection.cs index aaef5c29..1a813e98 100644 --- a/WebDavMailRuCloudStore/StoreBase/LocalStoreCollection.cs +++ b/WebDavMailRuCloudStore/StoreBase/LocalStoreCollection.cs @@ -116,7 +116,7 @@ public Task CreateItemAsync(string name, bool overwrite, IHttpC if (!IsWritable) return Task.FromResult(new StoreItemResult(DavStatusCode.PreconditionFailed)); - var destinationPath = FullPath + "/" + name; + var destinationPath = string.Concat(FullPath, "/", name); var size = httpContext.Request.ContentLength(); diff --git a/WinServiceInstaller/ServiceConfigurator.cs b/WinServiceInstaller/ServiceConfigurator.cs index 83889060..e14117e9 100644 --- a/WinServiceInstaller/ServiceConfigurator.cs +++ b/WinServiceInstaller/ServiceConfigurator.cs @@ -62,7 +62,7 @@ public void Install() string cmd = Assembly.Location; if(!string.IsNullOrWhiteSpace(CommandLine)) - cmd += " " + CommandLine; + cmd = string.Concat(cmd, " ", CommandLine); SetCommandLine(cmd); #endif #if NET7_0_WINDOWS From 75bfa6e98f6744c532a2ee317e22da2d8a28a2f1 Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Tue, 17 Oct 2023 18:27:35 +0300 Subject: [PATCH 37/77] =?UTF-8?q?=D0=9E=D0=B3=D1=80=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D1=87=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B2=D1=80=D0=B5=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=20=D0=BA=D0=B5=D1=88=D0=B8=D1=80=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=20URL'=D0=B0=20=D0=B4=D0=BB=D1=8F=20=D1=81?= =?UTF-8?q?=D0=BA=D0=B0=D1=87=D0=B8=D0=B2=D0=B0=D0=BD=D0=B8=D1=8F=20=D1=84?= =?UTF-8?q?=D0=B0=D0=B9=D0=BB=D0=B0=205=20=D0=BC=D0=B8=D0=BD=D1=83=D1=82?= =?UTF-8?q?=D0=B0=D0=BC=D0=B8.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MailRuCloud/MailRuCloudApi/Base/File.cs | 7 +++++++ .../Base/Repos/YandexDisk/YadWebV2/YadWebRequestRepo.cs | 5 ++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/MailRuCloud/MailRuCloudApi/Base/File.cs b/MailRuCloud/MailRuCloudApi/Base/File.cs index b56f6dce..a734a534 100644 --- a/MailRuCloud/MailRuCloudApi/Base/File.cs +++ b/MailRuCloud/MailRuCloudApi/Base/File.cs @@ -37,10 +37,17 @@ public File(string fullPath, long size, params PublicLinkInfo[] links) private IFileHash _hash; /// + /// Кеш URL на скачивание файла с сервера. /// Заполняется операцией GetDownloadStream, /// чтобы при повторном обращении на чтение файла не тратить время на получения URL'а. /// public string DownloadUrlCache { get; set; } = null; + /// + /// Время, с которого кешем пользоваться нельзя + /// и нужно получить новый URL. + /// + public DateTime DownloadUrlCacheExpirationTime = DateTime.MinValue; + /// /// makes copy of this file with new path diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/YadWebRequestRepo.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/YadWebRequestRepo.cs index 8f82e95a..f7f27475 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/YadWebRequestRepo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/YadWebRequestRepo.cs @@ -85,7 +85,8 @@ CustomDisposable ResponseGenerator(long instart, long inend, Fi // .MakeRequestAsync() // .Result; string url = null; - if (file.DownloadUrlCache == null) + if (file.DownloadUrlCache == null || + file.DownloadUrlCacheExpirationTime <= DateTime.Now) { var _ = new YaDCommonRequest(HttpSettings, (YadWebAuth)Authent) .With(new YadGetResourceUrlPostModel(file.FullPath), @@ -106,7 +107,9 @@ CustomDisposable ResponseGenerator(long instart, long inend, Fi itemInfo?.Data?.Error?.Body?.Title)); } url = "https:" + itemInfo.Data.File; + file.DownloadUrlCache = url; + file.DownloadUrlCacheExpirationTime = DateTime.Now.AddMinutes(1); } else { From 082ca2d2a66c9740f9ac6daef21c7cbe5915f97f Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Sat, 28 Oct 2023 17:55:02 +0300 Subject: [PATCH 38/77] =?UTF-8?q?=D0=92=20=D0=BA=D0=BE=D0=BD=D1=81=D1=82?= =?UTF-8?q?=D1=80=D1=83=D0=BA=D1=82=D0=BE=D1=80=D0=B5=20=D0=BA=D0=B0=D0=B6?= =?UTF-8?q?=D0=B4=D0=BE=D0=B3=D0=BE=20=D0=BF=D1=80=D0=BE=D1=82=D0=BE=D0=BA?= =?UTF-8?q?=D0=BE=D0=BB=D0=B0=20=D0=B5=D1=81=D1=82=D1=8C=20=D1=81=D0=B2?= =?UTF-8?q?=D0=BE=D0=B5=20=D0=B7=D0=B0=D0=BF=D0=BE=D0=BB=D0=BD=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5,=20=D0=BF=D0=BE=D1=8D=D1=82=D0=BE=D0=BC=D1=83=20?= =?UTF-8?q?=D0=B7=D0=B4=D0=B5=D1=81=D1=8C=20=D0=BC=D0=BE=D0=B6=D0=BD=D0=BE?= =?UTF-8?q?=20=D0=B8=20=D0=BD=D1=83=D0=B6=D0=BD=D0=BE=20=D1=83=D0=B1=D1=80?= =?UTF-8?q?=D0=B0=D1=82=D1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MailRuCloud/MailRuCloudApi/Base/Repos/RepoFabric.cs | 3 --- 1 file changed, 3 deletions(-) diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/RepoFabric.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/RepoFabric.cs index 1de42274..2f4364f2 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/RepoFabric.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/RepoFabric.cs @@ -36,9 +36,6 @@ string TwoFaHandler(string login, bool isAutoRelogin) _ => throw new Exception("Unknown protocol") }; - if (!string.IsNullOrWhiteSpace(_settings.UserAgent)) - repo.HttpSettings.UserAgent = _settings.UserAgent; - return repo; } } From 050b83e34229c38f55da2f9f7105d03ec094897b Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Sun, 29 Oct 2023 12:48:44 +0300 Subject: [PATCH 39/77] =?UTF-8?q?=D0=91=D0=B8=D1=82=D0=B2=D0=B0=20=D0=B7?= =?UTF-8?q?=D0=B0=20=D1=81=D0=BA=D0=BE=D1=80=D0=BE=D1=81=D1=82=D1=8C,=20?= =?UTF-8?q?=D0=BA=D0=BB=D1=8E=D1=87=D0=B5=D0=B2=D1=8B=D0=B5=20=D0=B8=D0=B7?= =?UTF-8?q?=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD=D0=B8=D1=8F:=201)=20=D0=9F=D1=80?= =?UTF-8?q?=D0=B8=D0=BD=D1=86=D0=B8=D0=BF=20=D0=B8=D0=B5=D1=80=D0=B0=D1=80?= =?UTF-8?q?=D1=85=D0=B8=D0=B8=20=D1=80=D0=B0=D0=B7=D1=80=D1=83=D1=88=D0=B0?= =?UTF-8?q?=D0=B5=D1=82=D1=81=D1=8F,=20=D1=82=D0=B5=D0=BF=D0=B5=D1=80?= =?UTF-8?q?=D1=8C=20=D1=8D=D1=82=D0=BE=20=D0=B1=D0=BE=D0=BB=D0=B5=D0=B5=20?= =?UTF-8?q?=D0=BB=D0=BE=D0=B3=D0=B8=D1=87=D0=B5=D1=81=D0=BA=D0=B8=D0=B9=20?= =?UTF-8?q?=D0=BA=D0=BE=D0=BD=D1=86=D0=B5=D0=BF=D1=82,=20=D1=87=D0=B5?= =?UTF-8?q?=D0=BC=20=D0=B0=D1=80=D1=85=D0=B8=D1=82=D0=B5=D0=BA=D1=82=D1=83?= =?UTF-8?q?=D1=80=D0=BD=D1=8B=D0=B9=20=D0=B1=D0=B0=D0=B7=D0=B8=D1=81=20?= =?UTF-8?q?=E2=80=93=20=D0=B3=D0=B4=D0=B5=20=D0=BD=D1=83=D0=B6=D0=BD=D0=BE?= =?UTF-8?q?,=20=D0=B8=D0=B5=D1=80=D0=B0=D1=80=D1=85=D0=B8=D1=8F=20=D0=B5?= =?UTF-8?q?=D1=81=D1=82=D1=8C,=20=D0=B3=D0=B4=D0=B5=20=D0=BD=D0=B5=20?= =?UTF-8?q?=D0=BD=D1=83=D0=B6=D0=BD=D0=BE=20=E2=80=93=20=D0=B5=D0=B5=20?= =?UTF-8?q?=D0=BD=D0=B5=D1=82.=20=D0=98=20=D0=BF=D0=BE=D1=82=D0=BE=D0=BC?= =?UTF-8?q?=D1=83,=20Folder=20=D0=B1=D0=BE=D0=BB=D1=8C=D1=88=D0=B5=20?= =?UTF-8?q?=D0=BD=D0=B5=20=D1=8F=D0=B2=D0=BB=D1=8F=D0=B5=D1=82=D1=81=D1=8F?= =?UTF-8?q?=20=D0=BD=D0=BE=D1=81=D0=B8=D1=82=D0=B5=D0=BB=D0=B5=D0=BC=20?= =?UTF-8?q?=D0=B8=D0=B5=D1=80=D0=B0=D1=80=D1=85=D0=B8=D0=B8=20=D1=81=20?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D0=BB=D0=B5=D0=BA=D1=86=D0=B8=D1=8F=D0=BC?= =?UTF-8?q?=D0=B8=20Folders=20=D0=B8=20Files.=20=D0=92=D1=8B=D0=B4=D0=B5?= =?UTF-8?q?=D0=BB=D1=8F=D0=B5=D1=82=D1=81=D1=8F=203=20=D0=B0=D1=80=D1=85?= =?UTF-8?q?=D0=B8=D1=82=D0=B5=D0=BA=D1=82=D1=83=D1=80=D0=BD=D1=8B=D1=85=20?= =?UTF-8?q?=D1=83=D1=80=D0=BE=D0=B2=D0=BD=D1=8F=20=E2=80=93=20=D0=BC=D0=B5?= =?UTF-8?q?=D1=82=D0=BE=D0=B4=D1=8B=20=D0=B2=D1=8B=D0=B1=D0=BE=D1=80=D0=BA?= =?UTF-8?q?=D0=B8=20=D1=81=20=D1=81=D0=B5=D1=80=D0=B2=D0=B5=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=20=E2=80=93=20cloud=20=D0=B8=20=D0=B5=D0=B3=D0=BE=20?= =?UTF-8?q?=D0=BA=D0=B5=D1=88=20=E2=80=93=20LocalStore=20=D0=B8=20=D0=B5?= =?UTF-8?q?=D0=B3=D0=BE=20=D1=80=D0=B0=D0=B7=D0=BD=D0=BE=D0=B2=D0=B8=D0=B4?= =?UTF-8?q?=D0=BD=D0=BE=D1=81=D1=82=D0=B8.=20=D0=9F=D0=B5=D1=80=D0=B5?= =?UTF-8?q?=D0=B4=D0=B0=D1=87=D0=B0=20=D0=B4=D0=B0=D0=BD=D0=BD=D1=8B=D1=85?= =?UTF-8?q?=20=D0=BE=20=D1=84=D0=B0=D0=B9=D0=BB=D0=B0=D1=85=20=D0=B8=20?= =?UTF-8?q?=D0=BF=D0=B0=D0=BF=D0=BA=D0=B0=D1=85=20=D0=BC=D0=B5=D0=B6=D0=B4?= =?UTF-8?q?=D1=83=20=D0=BD=D0=B8=D0=BC=D0=B8=20=D0=B4=D0=B5=D0=BB=D0=B0?= =?UTF-8?q?=D0=B5=D1=82=D1=81=D1=8F=20=D1=87=D0=B5=D1=80=D0=B5=D0=B7=20Ent?= =?UTF-8?q?ry,=20=D0=BA=D0=BE=D1=82=D0=BE=D1=80=D1=8B=D0=B9=20=D0=BC=D0=BE?= =?UTF-8?q?=D0=B6=D0=B5=D1=82=20=D0=B1=D1=8B=D1=82=D1=8C=20=D1=82=D0=B8?= =?UTF-8?q?=D0=BF=D0=B0=20File,=20Folder=20=D0=B8=D0=BB=D0=B8=20Link.=20?= =?UTF-8?q?=D0=A7=D1=82=D0=BE=D0=B1=D1=8B=20Entry=20=D0=BC=D0=BE=D0=B3=20?= =?UTF-8?q?=D0=BF=D0=B5=D1=80=D0=B5=D0=B4=D0=B0=D0=B2=D0=B0=D1=82=D1=8C=20?= =?UTF-8?q?=D1=81=D0=BE=D0=B4=D0=B5=D1=80=D0=B6=D0=B8=D0=BC=D0=BE=D0=B5=20?= =?UTF-8?q?=D0=BF=D0=B0=D0=BF=D0=BA=D0=B8,=20=D1=83=20=D0=BD=D0=B5=D0=B3?= =?UTF-8?q?=D0=BE=20=D0=B5=D1=81=D1=82=D1=8C=20Descendants=20=D1=82=D0=B8?= =?UTF-8?q?=D0=BF=D0=B0=20ImmutableList,=20=D1=87=D1=82=D0=BE=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=B4=D1=87=D0=B5=D1=80=D0=BA=D0=B8=D0=B2=D0=B0=D0=B5?= =?UTF-8?q?=D1=82,=20=D1=87=D1=82=D0=BE=20=D1=8D=D1=82=D0=BE=D1=82=20?= =?UTF-8?q?=D1=81=D0=BF=D0=B8=D1=81=D0=BE=D0=BA=20=D0=BD=D0=B5=20=D0=B4?= =?UTF-8?q?=D0=BB=D1=8F=20=D1=85=D1=80=D0=B0=D0=BD=D0=B5=D0=BD=D0=B8=D1=8F?= =?UTF-8?q?=20=D0=B8=20=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20=D0=B8=D0=BD=D1=84=D0=BE=D1=80=D0=BC=D0=B0=D1=86=D0=B8?= =?UTF-8?q?=D0=B8,=20=D0=B0=20=D1=82=D0=BE=D0=BB=D1=8C=D0=BA=D0=BE=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20=D0=BF=D0=B5=D1=80=D0=B5=D0=B4=D0=B0=D1=87?= =?UTF-8?q?=D0=B8=20=D1=81=20=D0=B0=D1=80=D1=85=D0=B8=D1=82=D0=B5=D0=BA?= =?UTF-8?q?=D1=82=D1=83=D1=80=D0=BD=D0=BE=D0=B3=D0=BE=20=D1=83=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=D0=BD=D1=8F=20=D0=BD=D0=B0=20=D1=83=D1=80=D0=BE=D0=B2?= =?UTF-8?q?=D0=B5=D0=BD=D1=8C.=20Descendants=20=D0=B7=D0=B0=D0=BF=D0=BE?= =?UTF-8?q?=D0=BB=D0=BD=D1=8F=D0=B5=D1=82=D1=81=D1=8F=20=D0=BC=D0=B5=D1=82?= =?UTF-8?q?=D0=BE=D0=B4=D0=B0=D0=BC=D0=B8=20=D1=87=D1=82=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20=D1=81=20=D1=81=D0=B5=D1=80=D0=B2=D0=B5=D1=80=D0=B0=20?= =?UTF-8?q?=D0=B8=20=D0=BE=D1=87=D0=B8=D1=89=D0=B0=D0=B5=D1=82=D1=81=D1=8F?= =?UTF-8?q?=20=D0=BC=D0=B5=D1=82=D0=BE=D0=B4=D0=B0=D0=BC=D0=B8=20=D0=BA?= =?UTF-8?q?=D0=B5=D1=88=D0=B0,=20=D1=80=D0=BE=D0=B2=D0=BD=D0=BE=20=D0=B4?= =?UTF-8?q?=D0=BB=D1=8F=20=D1=82=D0=BE=D0=B3=D0=BE,=20=D1=87=D1=82=D0=BE?= =?UTF-8?q?=D0=B1=D1=8B=20=D0=BD=D0=B5=20=D0=B1=D1=8B=D0=BB=D0=BE=20=D1=81?= =?UTF-8?q?=D0=BE=D0=B1=D0=BB=D0=B0=D0=B7=D0=BD=D0=B0=20=D0=B2=D0=BE=D1=81?= =?UTF-8?q?=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D1=82=D1=8C?= =?UTF-8?q?=D1=81=D1=8F=20=D1=8D=D1=82=D0=B8=D0=BC=20=D1=81=D0=BF=D0=B8?= =?UTF-8?q?=D1=81=D0=BA=D0=BE=D0=BC=20=D0=BA=D0=B0=D0=BA=20=D1=85=D1=80?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D0=BB=D0=B8=D1=89=D0=B5=D0=BC=20=D0=B4=D0=B0?= =?UTF-8?q?=D0=BD=D0=BD=D1=8B=D1=85,=20=D1=82.=D0=BA.=20=D0=BE=D0=BD=20?= =?UTF-8?q?=D1=82=D0=BE=D0=BB=D1=8C=D0=BA=D0=BE=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D0=BF=D0=B5=D1=80=D0=B5=D0=B4=D0=B0=D1=87=D0=B8.=20=D0=A2?= =?UTF-8?q?=D0=BE=D0=BB=D1=8C=D0=BA=D0=BE=20=D0=BD=D0=B0=20=D1=83=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=D0=BD=D0=B5=20LocalStore=20Descendants=20=D0=B8?= =?UTF-8?q?=D0=B3=D1=80=D0=B0=D0=B5=D1=82=20=D1=80=D0=BE=D0=BB=D1=8C=20?= =?UTF-8?q?=D1=85=D1=80=D0=B0=D0=BD=D0=B8=D0=BB=D0=B8=D1=89=D0=B0,=20?= =?UTF-8?q?=D1=82.=D0=BA.=20=D0=B8=D0=BD=D0=BE=D0=B3=D0=BE=20=D0=BC=D0=B5?= =?UTF-8?q?=D1=81=D1=82=D0=B0=20=D0=B4=D0=BB=D1=8F=20=D1=85=D1=80=D0=B0?= =?UTF-8?q?=D0=BD=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BA=D0=BE=D0=BB=D0=BB=D0=B5?= =?UTF-8?q?=D0=BA=D1=86=D0=B8=D0=B8=20=D1=82=D0=B0=D0=BC=20=D0=BD=D0=B5?= =?UTF-8?q?=D1=82,=20=D0=B0=20=D0=BF=D0=B5=D1=80=D0=B5=D0=B4=D0=B5=D0=BB?= =?UTF-8?q?=D1=8B=D0=B2=D0=B0=D1=82=D1=8C=20=D1=81=D0=B8=D0=BB=D1=8C=D0=BD?= =?UTF-8?q?=D0=BE=20=D0=B5=D1=89=D0=B5=20=D0=B8=20=D1=8D=D1=82=D0=BE=D1=82?= =?UTF-8?q?=20=D1=83=D1=80=D0=BE=D0=B2=D0=B5=D0=BD=D1=8C=20=D1=80=D0=B5?= =?UTF-8?q?=D0=B7=D0=BE=D0=BD=D0=BE=D0=B2=20=D0=BD=D0=B5=D1=82.=202)=20?= =?UTF-8?q?=D0=9A=D0=B5=D1=88=20=D0=BF=D0=BE=D0=BB=D0=BD=D0=BE=D1=81=D1=82?= =?UTF-8?q?=D1=8C=D1=8E=20=D0=BF=D0=B5=D1=80=D0=B5=D0=B4=D0=B5=D0=BB=D0=B0?= =?UTF-8?q?=D0=BD,=20=D1=82=D0=B5=D0=BF=D0=B5=D1=80=D1=8C=20=D1=8D=D1=82?= =?UTF-8?q?=D0=BE=20=D0=BD=D0=B5=20=D0=BC=D0=BD=D0=BE=D0=B3=D0=BE=D1=83?= =?UTF-8?q?=D1=80=D0=BE=D0=B2=D0=BD=D0=B5=D0=B2=D0=B0=D1=8F=20=D1=81=D1=82?= =?UTF-8?q?=D1=80=D1=83=D0=BA=D1=82=D1=83=D1=80=D0=B0=20=D1=87=D0=B5=D1=80?= =?UTF-8?q?=D0=B5=D0=B7=20Folder,=20=D0=B0=20=D0=B5=D0=B4=D0=B8=D0=BD?= =?UTF-8?q?=D1=8B=D0=B9=20=D1=81=D0=BB=D0=BE=D0=B2=D0=B0=D1=80=D1=8C,=20?= =?UTF-8?q?=D0=B3=D0=B4=D0=B5=20=D0=B2=D1=81=D0=B5=20=D0=BF=D0=BE=D0=B4?= =?UTF-8?q?=D0=B2=D0=B5=D1=88=D0=B5=D0=BD=D0=BE=20=D0=B7=D0=B0=20FullPath,?= =?UTF-8?q?=20=D1=82=D0=BE=20=D0=B5=D1=81=D1=82=D1=8C=20URL=20=D0=BA=20ent?= =?UTF-8?q?ry.=20=D0=9F=D0=BE=D0=B4=D0=B4=D0=B5=D1=80=D0=B6=D0=B8=D0=B2?= =?UTF-8?q?=D0=B0=D0=B5=D1=82=D1=81=D1=8F=20=D0=BC=D0=BD=D0=BE=D0=B3=D0=BE?= =?UTF-8?q?=D0=BF=D0=BE=D1=82=D0=BE=D1=87=D0=BD=D0=BE=D1=81=D1=82=D1=8C=20?= =?UTF-8?q?=D0=B8=20=D0=BF=D1=80=D0=BE=D0=B2=D0=B5=D1=80=D0=BA=D0=B8=20?= =?UTF-8?q?=D0=B0=D0=BA=D1=82=D0=B8=D0=B2=D0=BD=D1=8B=D1=85=20=D0=BE=D0=BF?= =?UTF-8?q?=D0=B5=D1=80=D0=B0=D1=86=D0=B8=D0=B9=20=D0=BD=D0=B0=20=D1=81?= =?UTF-8?q?=D0=B5=D1=80=D0=B2=D0=B5=D1=80=D0=B5=20(=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D1=82=D0=BE=D0=BA=D0=BE=D0=BB=D0=B0=20YadWeb?= =?UTF-8?q?V2).=20=D0=9F=D1=80=D0=B8=20=D0=BE=D0=BF=D0=B5=D1=80=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D1=8F=D1=85=20=D1=83=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F,=20=D1=81=D0=BE=D0=B7=D0=B4=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F,=20=D0=BF=D0=B5=D1=80=D0=B5=D0=BC=D0=B5=D1=89=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=20=D0=B8=20=D0=B4=D1=80.=20=D0=BA=D0=B5?= =?UTF-8?q?=D1=88=20=D0=BF=D0=B0=D0=BF=D0=BA=D0=B8=20=D0=BD=D0=B5=20=D1=81?= =?UTF-8?q?=D0=B1=D1=80=D0=B0=D1=81=D1=8B=D0=B2=D0=B0=D0=B5=D1=82=D1=81?= =?UTF-8?q?=D1=8F=20=D1=87=D1=82=D0=BE=D0=B1=D1=8B=20=D0=BD=D0=B5=20=D0=BF?= =?UTF-8?q?=D0=B5=D1=80=D0=B5=D1=87=D0=B8=D1=82=D1=8B=D0=B2=D0=B0=D1=82?= =?UTF-8?q?=D1=8C=20=D0=BF=D0=B0=D0=BF=D0=BA=D0=B8=20=D1=81=20=D0=B4=D0=B5?= =?UTF-8?q?=D1=81=D1=8F=D1=82=D0=BA=D0=B0=D0=BC=D0=B8=20=D1=82=D1=8B=D1=81?= =?UTF-8?q?=D1=8F=D1=87=20=D0=B7=D0=B0=D0=BF=D0=B8=D1=81=D0=B5=D0=B9.=203)?= =?UTF-8?q?=20=D0=94=D0=BB=D1=8F=20YadWebV2=20=D1=81=D0=BE=D0=B4=D0=B5?= =?UTF-8?q?=D1=80=D0=B6=D0=B8=D0=BC=D0=BE=D0=B5=20=D0=BF=D0=B0=D0=BF=D0=BE?= =?UTF-8?q?=D0=BA=20=D1=81=20=D1=82=D1=8B=D1=81=D1=8F=D1=87=D0=B0=D0=BC?= =?UTF-8?q?=D0=B8=20=D0=B7=D0=B0=D0=BF=D0=B8=D1=81=D0=B5=D0=B9=20=D1=87?= =?UTF-8?q?=D0=B8=D1=82=D0=B0=D0=B5=D1=82=D1=81=D1=8F=20=D0=BD=D0=B5=D1=81?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=D1=8C=D0=BA=D0=B8=D0=BC=D0=B8=20=D0=BF=D0=BE?= =?UTF-8?q?=D1=82=D0=BE=D0=BA=D0=B0=D0=BC=D0=B8=20=D0=BE=D0=B4=D0=BD=D0=BE?= =?UTF-8?q?=D0=B2=D1=80=D0=B5=D0=BC=D0=B5=D0=BD=D0=BD=D0=BE=20=E2=80=93=20?= =?UTF-8?q?=D0=BF=D0=BE=D1=87=D1=82=D0=B8=20=D0=BD=D0=B0=20=D0=BF=D0=BE?= =?UTF-8?q?=D1=80=D1=8F=D0=B4=D0=BE=D0=BA=20=D1=81=D0=BE=D0=BA=D1=80=D0=B0?= =?UTF-8?q?=D1=82=D0=B8=D0=BB=D0=BE=D1=81=D1=8C=20=D0=B2=D1=80=D0=B5=D0=BC?= =?UTF-8?q?=D1=8F=20=D0=BF=D1=80=D0=B8=20=D0=B4=D0=B5=D1=81=D1=8F=D1=82?= =?UTF-8?q?=D0=BA=D0=B0=D1=85=20=D1=82=D1=8B=D1=81=D1=8F=D1=87=D0=B0=D1=85?= =?UTF-8?q?=20=D1=84=D0=B0=D0=B9=D0=BB=D0=BE=D0=B2=20=D0=B2=20=D0=BF=D0=B0?= =?UTF-8?q?=D0=BF=D0=BA=D0=B5.=204)=20=D0=9D=D0=B0=20=D0=BE=D0=B4=D0=BD?= =?UTF-8?q?=D0=BE=D0=B2=D1=80=D0=B5=D0=BC=D0=B5=D0=BD=D0=BD=D0=BE=D0=B5=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=B4=D0=BA=D0=BB=D1=8E=D1=87=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D0=BA=20=D1=81=D0=B5=D1=80=D0=B2=D0=B5=D1=80=D1=83=20?= =?UTF-8?q?=D0=B2=D0=B2=D0=B5=D0=B4=D0=B5=D0=BD=D0=BE=20=D0=BE=D0=B3=D1=80?= =?UTF-8?q?=D0=B0=D0=BD=D0=B8=D1=87=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=87=D0=B5?= =?UTF-8?q?=D1=80=D0=B5=D0=B7=20SemaphoreSlim,=20=D1=83=D0=BF=D1=80=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D1=8F=D0=B5=D1=82=D1=81=D1=8F=20=D0=BF=D0=B0=D1=80?= =?UTF-8?q?=D0=B0=D0=BC=D0=B5=D1=82=D1=80=D0=BE=D0=BC=20maxconnetions.=20?= =?UTF-8?q?=D0=92=D0=B2=D0=B5=D0=B4=D0=B5=D0=BD=D0=B0=20=D0=B1=D0=BB=D0=BE?= =?UTF-8?q?=D0=BA=D0=B8=D1=80=D0=BE=D0=B2=D0=BA=D0=B0=20=D0=BD=D0=B0=20?= =?UTF-8?q?=D0=BE=D0=B4=D0=BD=D0=BE=D0=B2=D1=80=D0=B5=D0=BC=D0=B5=D0=BD?= =?UTF-8?q?=D0=BD=D0=BE=D0=B5=20=D0=BF=D0=B0=D1=80=D0=B0=D0=BB=D0=BB=D0=B5?= =?UTF-8?q?=D0=BB=D1=8C=D0=BD=D0=BE=D0=B5=20=D1=87=D1=82=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D1=81=D0=BE=D0=B4=D0=B5=D1=80=D0=B6=D0=B8=D0=BC=D0=BE?= =?UTF-8?q?=D0=B5=20=D0=BF=D0=B0=D0=BF=D0=BA=D0=B8=20=D1=81=20=D1=81=D0=B5?= =?UTF-8?q?=D1=80=D0=B2=D0=B5=D1=80=D0=B0=20=D1=81=20=D0=BE=D0=B4=D0=B8?= =?UTF-8?q?=D0=BD=D0=B0=D0=BA=D0=BE=D0=B2=D1=8B=D0=BC=20path=20=E2=80=93?= =?UTF-8?q?=20=D1=87=D0=B8=D1=82=D0=B0=D0=B5=D1=82=20=D1=82=D0=BE=D0=BB?= =?UTF-8?q?=D1=8C=D0=BA=D0=BE=20=D0=BE=D0=B4=D0=B8=D0=BD,=20=D0=BE=D1=81?= =?UTF-8?q?=D1=82=D0=B0=D0=BB=D1=8C=D0=BD=D1=8B=D0=B5=20=D0=BF=D0=BE=D0=BB?= =?UTF-8?q?=D1=8C=D0=B7=D1=83=D1=8E=D1=82=D1=81=D1=8F=20=D1=80=D0=B5=D0=B7?= =?UTF-8?q?=D1=83=D0=BB=D1=8C=D1=82=D0=B0=D1=82=D0=BE=D0=BC.=205)=20=D0=9C?= =?UTF-8?q?=D0=BD=D0=BE=D0=B6=D0=B5=D1=81=D1=82=D0=B2=D0=B5=D0=BD=D0=BD?= =?UTF-8?q?=D1=8B=D0=B5=20=D0=BF=D1=80=D0=B0=D0=B2=D0=BA=D0=B8=20=D1=82?= =?UTF-8?q?=D0=B8=D0=BF=D0=B0=20authent=20->=20authenticator,=20creds=20->?= =?UTF-8?q?=20credentials=20=D1=87=D1=82=D0=BE=D0=B1=D1=8B=20=D1=83=D0=BC?= =?UTF-8?q?=D0=B5=D0=BD=D1=8C=D1=88=D0=B8=D1=82=D1=8C=20=D0=BA=D0=BE=D0=BB?= =?UTF-8?q?=D0=B8=D1=87=D0=B5=D1=81=D1=82=D0=B2=D0=BE=20=D0=BF=D1=80=D0=B5?= =?UTF-8?q?=D0=B4=D1=83=D0=BF=D1=80=D0=B5=D0=B6=D0=B4=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B9=20=D0=BE=D1=82=20Spell=20Checker=E2=80=99=D0=B0.=20?= =?UTF-8?q?=D0=9D=D0=B5=D0=BA=D0=BE=D1=82=D0=BE=D1=80=D1=8B=D0=B5=20=D0=B4?= =?UTF-8?q?=D1=80=D1=83=D0=B3=D0=B8=D0=B5=20=D1=81=D1=82=D0=B8=D0=BB=D0=B8?= =?UTF-8?q?=D1=81=D1=82=D0=B8=D1=87=D0=B5=D1=81=D0=BA=D0=B8=D0=B5=20=D0=BF?= =?UTF-8?q?=D1=80=D0=B8=D1=87=D0=B5=D1=81=D1=8B=D0=B2=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20=D0=BA=D0=BE=D0=B4=D0=B0.=206)=20YadWebV2:=20=D0=9F?= =?UTF-8?q?=D0=BE=D1=81=D0=BB=D0=B5=20=D0=BE=D0=BF=D0=B5=D1=80=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D0=B8=20=D1=83=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD=D0=B8=D1=8F?= =?UTF-8?q?=20=D0=BE=D0=B6=D0=B8=D0=B4=D0=B0=D0=BD=D0=B8=D0=B5=20=D0=B4?= =?UTF-8?q?=D0=B5=D0=BB=D0=B0=D0=B5=D1=82=D1=81=D1=8F=20=D0=BD=D0=B5=20?= =?UTF-8?q?=D0=BF=D0=B5=D1=80=D0=B5=D0=B4=20=D1=87=D1=82=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=D0=BC,=20=D0=B0=20=D1=81=D1=80=D0=B0=D0=B7=D1=83=20?= =?UTF-8?q?=D0=BF=D0=BE=D1=81=D0=BB=D0=B5=20=D0=BE=D0=BF=D0=B5=D1=80=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D0=B8=20=D1=83=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F.=20=D0=92=20=D0=BE=D0=B6=D0=B8=D0=B4=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D0=B8=20=D0=B7=D0=B0=D0=B2=D0=B5=D1=80=D1=88=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20=D0=BE=D0=BF=D0=B5=D1=80=D0=B0=D1=86=D0=B8=D0=B8=20?= =?UTF-8?q?=D0=B2=D0=BC=D0=B5=D1=81=D1=82=D0=BE=20=D1=81=D1=87=D0=B5=D1=82?= =?UTF-8?q?=D1=87=D0=B8=D0=BA=D0=B0=20=D1=81=D0=B4=D0=B5=D0=BB=D0=B0=D0=BD?= =?UTF-8?q?=20=D1=82=D0=B0=D0=B9=D0=BC=D0=B0=D1=83=D1=82.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .editorconfig | 4 + MailRuCloud/MailRuCloudApi/Base/File.cs | 5 +- MailRuCloud/MailRuCloudApi/Base/Folder.cs | 90 +- MailRuCloud/MailRuCloudApi/Base/IEntry.cs | 2 + .../Base/Repos/DtoImportExtensions.cs | 25 +- .../MailRuCloudApi/Base/Repos/IRequestRepo.cs | 4 +- .../Base/Repos/MailRuCloud/DtoImport.cs | 99 +-- .../Base/Repos/MailRuCloud/MailRuBaseRepo.cs | 18 +- .../MailRuCloud/Mobile/MobileRequestRepo.cs | 62 +- .../Base/Repos/MailRuCloud/OAuth.cs | 12 +- .../Base/Repos/MailRuCloud/ShardManager.cs | 11 +- .../WebBin/Requests/DownloadRequest.cs | 13 +- .../MailRuCloud/WebBin/WebBinRequestRepo.cs | 145 ++-- .../MailRuCloud/WebM1/WebM1RequestRepo.cs | 91 +- .../WebV2/Requests/DownloadRequest.cs | 12 +- .../WebV2/Requests/UploadRequest.cs | 10 +- .../Base/Repos/MailRuCloud/WebV2/WebAuth.cs | 27 +- .../MailRuCloud/WebV2/WebV2RequestRepo.cs | 85 +- .../YandexDisk/YadWeb/DtoImportYadWeb.cs | 58 +- .../YadWeb/Requests/YadDownloadRequest.cs | 8 +- .../YadWeb/Requests/YadUploadRequest.cs | 8 +- .../Repos/YandexDisk/YadWeb/YadWebAuth.cs | 19 +- .../YandexDisk/YadWeb/YadWebRequestRepo.cs | 220 ++--- .../YandexDisk/YadWebV2/DtoImportYadWeb.cs | 109 ++- .../YandexDisk/YadWebV2/Models/AccountInfo.cs | 3 +- .../YadWebV2/Models/ActiveOperations.cs | 198 +++++ .../YandexDisk/YadWebV2/Models/FolderInfo.cs | 2 +- .../YadWebV2/Models/OperationStatus.cs | 2 +- .../YadWebV2/Requests/YadDownloadRequest.cs | 8 +- .../YadWebV2/Requests/YadUploadRequest.cs | 8 +- .../Repos/YandexDisk/YadWebV2/YadWebAuth.cs | 23 +- .../YandexDisk/YadWebV2/YadWebRequestRepo.cs | 511 ++++++++---- .../Base/Requests/BaseRequest.cs | 262 +++--- .../Base/Requests/Types/ActiveOperation.cs | 29 + .../Base/Requests/Types/CheckUpInfo.cs | 23 + .../Base/Streams/HttpClientFabric.cs | 2 +- MailRuCloud/MailRuCloudApi/Base/WebDavPath.cs | 59 +- MailRuCloud/MailRuCloudApi/Cloud.cs | 664 ++++++++++----- MailRuCloud/MailRuCloudApi/CloudSettings.cs | 4 +- .../MailRuCloudApi/Common/EntryCache.cs | 777 ++++++++++++++++++ .../MailRuCloudApi/Common/ItemCache.cs | 4 + MailRuCloud/MailRuCloudApi/Common/Retry.cs | 29 + .../MailRuCloudApi/Extensions/Extensions.cs | 26 +- MailRuCloud/MailRuCloudApi/Links/Link.cs | 2 + .../MailRuCloudApi/Links/LinkManager.cs | 72 +- MailRuCloud/MailRuCloudApi/PublishInfo.cs | 6 +- .../Commands/CryptInitCommand.cs | 7 +- .../SpecialCommands/Commands/DeleteCommand.cs | 9 +- .../SpecialCommands/Commands/FishCommand.cs | 2 +- .../SpecialCommands/Commands/ListCommand.cs | 44 +- .../SpecialCommands/Commands/MoveCommand.cs | 11 +- .../SpecialCommands/Commands/ShareCommand.cs | 7 +- .../Commands/SharedFolderLinkCommand.cs | 9 +- .../SpecialCommands/Commands/TestCommand.cs | 10 +- .../Streams/UploadStreamFabric.cs | 15 +- MailRuCloud/MailRuCloudApi/YaR.Clouds.csproj | 9 + .../Handlers/PropFindHandler.cs | 29 +- NWebDav/NWebDav.Server/WebDavDispatcher.cs | 3 +- WDMRC.Console/CommandLineOptions.cs | 11 +- WDMRC.Console/Payload.cs | 8 +- WDMRC.Console/WDMRC.Console.csproj | 3 + WebDAV.Uploader/UploadStub.cs | 12 +- .../CustomHandlers/CopyHandler.cs | 2 +- .../CustomHandlers/DeleteHandler.cs | 4 +- .../CustomHandlers/GetAndHeadHandler.cs | 2 +- .../CustomHandlers/MkcolHandler.cs | 2 +- .../CustomHandlers/MoveHandler.cs | 29 +- WebDavMailRuCloudStore/Extensions.cs | 2 +- .../StoreBase/LocalStore.cs | 24 +- .../StoreBase/LocalStoreCollection.cs | 44 +- .../StoreBase/LocalStoreCollectionProps.cs | 75 +- .../StoreBase/LocalStoreItem.cs | 4 +- 72 files changed, 2968 insertions(+), 1260 deletions(-) create mode 100644 MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/ActiveOperations.cs create mode 100644 MailRuCloud/MailRuCloudApi/Base/Requests/Types/ActiveOperation.cs create mode 100644 MailRuCloud/MailRuCloudApi/Base/Requests/Types/CheckUpInfo.cs create mode 100644 MailRuCloud/MailRuCloudApi/Common/EntryCache.cs diff --git a/.editorconfig b/.editorconfig index 8df6f07c..28baba0e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,6 +1,10 @@ # Remove the line below if you want to inherit .editorconfig settings from higher directories root = true +[*] +vsspell_section_id = b0252c83892c418e88e4674d7eb14041 +vsspell_ignored_words_b0252c83892c418e88e4674d7eb14041 = deduplicate|repo|repos|uninstall|Yad|maxthreads|maxconnections|Auth|Versioning|App|codec|Subentries|Utc|refact|nq|unshare + # C# files [*.cs] diff --git a/MailRuCloud/MailRuCloudApi/Base/File.cs b/MailRuCloud/MailRuCloudApi/Base/File.cs index a734a534..2b10e546 100644 --- a/MailRuCloud/MailRuCloudApi/Base/File.cs +++ b/MailRuCloud/MailRuCloudApi/Base/File.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Collections.Immutable; using System.Diagnostics; using System.IO; using System.Linq; @@ -152,6 +153,8 @@ public IEnumerable GetPublicLinks(Cloud cloud) : PublicLinks.Values; } + public ImmutableList Descendants => ImmutableList.Empty; + /// /// List of physical files contains data /// @@ -221,7 +224,7 @@ public PublishInfo ToPublishInfo(Cloud cloud, bool generateDirectVideoLink, Shar { Path = innerFile.FullPath, Urls = innerFile.PublicLinks.Select(pli => pli.Value.Uri).ToList(), - PlaylistUrl = !isSplitted || cnt > 0 + PlayListUrl = !isSplitted || cnt > 0 ? generateDirectVideoLink ? ConvertToVideoLink(cloud, innerFile.PublicLinks.Values.FirstOrDefault()?.Uri, videoResolution) : null diff --git a/MailRuCloud/MailRuCloudApi/Base/Folder.cs b/MailRuCloud/MailRuCloudApi/Base/Folder.cs index 407de3ef..1faa526c 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Folder.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Folder.cs @@ -1,10 +1,10 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Collections.Immutable; using System.Diagnostics; using System.IO; using System.Linq; -using YaR.Clouds.Common; namespace YaR.Clouds.Base { @@ -12,7 +12,7 @@ namespace YaR.Clouds.Base /// Server file info. /// [DebuggerDisplay("{" + nameof(FullPath) + "}")] - public class Folder : IEntry, ICanForget + public class Folder : IEntry { /// /// Initializes a new instance of the class. @@ -40,22 +40,49 @@ public Folder(FileSize size, string fullPath, IEnumerable public } } - public IEnumerable Entries + /// + /// makes copy of this file with new path + /// + /// + /// + public virtual Folder New(string newFullPath, IEnumerable children = null) { - get + var folder = new Folder(Size, newFullPath, null) { - foreach (var file in Files.Values) - yield return file; - foreach (var folder in Folders.Values) - yield return folder; - } + CreationTimeUtc = CreationTimeUtc, + LastAccessTimeUtc = LastAccessTimeUtc, + LastWriteTimeUtc = LastWriteTimeUtc, + Attributes = Attributes, + ServerFoldersCount = ServerFoldersCount, + ServerFilesCount = ServerFilesCount, + Descendants = children != null + ? ImmutableList.Create(children.ToArray()) + : ImmutableList.Empty, + IsChildrenLoaded = children != null + }; + foreach (var linkPair in PublicLinks) + folder.PublicLinks.AddOrUpdate(linkPair.Key, linkPair.Value, (_, _) => linkPair.Value); + + return folder; } - public ConcurrentDictionary Files { get; set; } - = new(StringComparer.InvariantCultureIgnoreCase); + //public IEnumerable Entries + //{ + // get + // { + // foreach (var file in Files.Values) + // yield return file; + // foreach (var folder in Folders.Values) + // yield return folder; + // } + //} + + // Больше никакой вложенной информации + //public ConcurrentDictionary Files { get; set; } + // = new(StringComparer.InvariantCultureIgnoreCase); - public ConcurrentDictionary Folders { get; set; } - = new(StringComparer.InvariantCultureIgnoreCase); + //public ConcurrentDictionary Folders { get; set; } + // = new(StringComparer.InvariantCultureIgnoreCase); /// @@ -106,7 +133,9 @@ public IEnumerable GetPublicLinks(Cloud cloud) public bool IsFile => false; - public bool IsChildrenLoaded { get; internal set; } + public bool IsChildrenLoaded { get; internal set; } = false; + + public ImmutableList Descendants { get; set; } = ImmutableList.Empty; public int? ServerFoldersCount { get; set; } @@ -120,28 +149,27 @@ public PublishInfo ToPublishInfo() return info; } - //public List> GetLinearChildren() //{ - + //} - public void Forget(object whomKey) - { - string key = whomKey?.ToString(); + //public void Forget(object whomKey) + //{ + // string key = whomKey?.ToString(); - if (string.IsNullOrEmpty(key)) - return; + // if (string.IsNullOrEmpty(key)) + // return; - // Удалять начинаем с директорий, т.к. их обычно меньше, - // а значит поиск должен завершиться в среднем быстрее. + // // Удалять начинаем с директорий, т.к. их обычно меньше, + // // а значит поиск должен завершиться в среднем быстрее. - if (!Folders.TryRemove(key, out _)) - { - // Если по ключу в виде полного пути не удалось удалить директорию, - // пытаемся по этому же ключу удалить файл, если он есть. - // Если ничего не удалилось, значит и удалять нечего. - _ = Files.TryRemove( key, out _); - } - } + // if (!Folders.TryRemove(key, out _)) + // { + // // Если по ключу в виде полного пути не удалось удалить директорию, + // // пытаемся по этому же ключу удалить файл, если он есть. + // // Если ничего не удалилось, значит и удалять нечего. + // _ = Files.TryRemove( key, out _); + // } + //} } } diff --git a/MailRuCloud/MailRuCloudApi/Base/IEntry.cs b/MailRuCloud/MailRuCloudApi/Base/IEntry.cs index a3b68959..53d71f20 100644 --- a/MailRuCloud/MailRuCloudApi/Base/IEntry.cs +++ b/MailRuCloud/MailRuCloudApi/Base/IEntry.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Concurrent; +using System.Collections.Immutable; namespace YaR.Clouds.Base { @@ -11,5 +12,6 @@ public interface IEntry string FullPath { get; } DateTime CreationTimeUtc { get; } ConcurrentDictionary PublicLinks { get; } + ImmutableList Descendants { get; } } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/DtoImportExtensions.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/DtoImportExtensions.cs index cf061c50..7093dbb8 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/DtoImportExtensions.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/DtoImportExtensions.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Security.Authentication; @@ -19,7 +18,7 @@ internal static IEntry ToEntry(this ListRequest.Result data) var entry = itemType == Cloud.ItemType.File ? (IEntry)data.ToFile() - : data.ToFolder(); + : (IEntry)data.ToFolder(); return entry; } @@ -47,7 +46,8 @@ internal static IEnumerable ToGroupedFiles(this IEnumerable list) var groupedFiles = list .GroupBy(f => f.ServiceInfo.CleanName, file => file) - .SelectMany(group => group.Count() == 1 //TODO: DIRTY: if group contains header file, than make SplittedFile, else do not group + //TODO: DIRTY: if group contains header file, than make SplittedFile, else do not group + .SelectMany(group => group.Count() == 1 ? group.Take(1) : group.Any(f => f.Name == f.ServiceInfo.CleanName) ? Enumerable.Repeat(new SplittedFile(group.ToList()), 1) @@ -64,24 +64,23 @@ internal static Folder ToFolder(this ListRequest.Result data) internal static Folder ToFolder(this FsFolder data) { - var res = new Folder((long)data.Size, data.FullPath) { IsChildrenLoaded = data.IsChildrenLoaded }; + var folder = new Folder((long)data.Size, data.FullPath) { IsChildrenLoaded = data.IsChildrenLoaded }; - res.Files = new ConcurrentDictionary( + var children = new List(); + children.AddRange( data.Items .OfType() .Select(f => f.ToFile()) - .ToGroupedFiles() - .Select(item => new KeyValuePair(item.FullPath, item)), - StringComparer.InvariantCultureIgnoreCase); + .ToGroupedFiles()); - res.Folders = new ConcurrentDictionary( + children.AddRange( data.Items .OfType() - .Select(f => f.ToFolder()) - .Select(item => new KeyValuePair(item.FullPath, item)), - StringComparer.InvariantCultureIgnoreCase); + .Select(f => f.ToFolder())); - return res; + folder.Descendants = folder.Descendants.AddRange(children); + + return folder; } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/IRequestRepo.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/IRequestRepo.cs index 0cbf0aa7..61a6a1d0 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/IRequestRepo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/IRequestRepo.cs @@ -12,10 +12,12 @@ namespace YaR.Clouds.Base.Repos { public interface IRequestRepo { - IAuth Authent { get; } + IAuth Authenticator { get; } HttpCommonSettings HttpSettings { get; } + Task ActiveOperationsAsync(); + Stream GetDownloadStream(File file, long? start = null, long? end = null); Task DoUpload(HttpClient client, PushStreamContent content, File file); diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/DtoImport.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/DtoImport.cs index 27474314..067581a7 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/DtoImport.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/DtoImport.cs @@ -1,11 +1,12 @@ using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Net.Http; using YaR.Clouds.Base.Requests.Types; using YaR.Clouds.Extensions; using YaR.Clouds.Links; +using YaR.Clouds.Common; +using System.Collections.Immutable; namespace YaR.Clouds.Base.Repos.MailRuCloud { @@ -196,66 +197,56 @@ private static Folder ToFolder(this FolderInfoResult.FolderInfoBody.FolderInfoPr public static IEntry ToEntry(this FolderInfoResult data, string publicBaseUrl) { if (data.Body.Kind == "file") - { - var file = data.ToFile(publicBaseUrl); - return file; - } - - var folder = new Folder(data.Body.Size, WebDavPath.Combine(data.Body.Home ?? WebDavPath.Root, data.Body.Name)) - { - Folders = data.Body.List.ToFolderDictionary(publicBaseUrl), - Files = data.Body.List.ToFileDictionary(publicBaseUrl), - }; - - + return data.ToFile(publicBaseUrl); + + var folder = new Folder(data.Body.Size, WebDavPath.Combine(data.Body.Home ?? WebDavPath.Root, data.Body.Name)); + + var children = new List(); + children.AddRange( + data.Body.List + .Where(it => it.Kind == "file") + .Select(item => item.ToFile(publicBaseUrl, "")) + .ToGroupedFiles() + .Select(f => f)); + children.AddRange( + data.Body.List + .Where(it => FolderKinds.Contains(it.Kind)) + .Select(item => item.ToFolder(publicBaseUrl)) + .Select(f => f)); + + folder.Descendants = folder.Descendants.AddRange(children); return folder; } - public static Folder ToFolder(this FolderInfoResult data, string publicBaseUrl, string home = null, Link link = null) { PatchEntryPath(data, home, link); - var folder = new Folder(data.Body.Size, data.Body.Home ?? data.Body.Name, data.Body.Weblink.ToPublicLinkInfos(publicBaseUrl)) - { - ServerFoldersCount = data.Body.Count?.Folders, - ServerFilesCount = data.Body.Count?.Files, + var body = data.Body; - Folders = data.Body.List.ToFolderDictionary(publicBaseUrl), - Files = data.Body.List.ToFileDictionary(publicBaseUrl), - IsChildrenLoaded = true + var folder = new Folder(body.Size, body.Home ?? body.Name, body.Weblink.ToPublicLinkInfos(publicBaseUrl)) + { + ServerFoldersCount = body.Count?.Folders, + ServerFilesCount = body.Count?.Files, }; + var children = new List(); + children.AddRange( + body.List + .Where(it => it.Kind == "file") + .Select(item => item.ToFile(publicBaseUrl, "")) + .ToGroupedFiles() + .Select(f => f)); + children.AddRange( + body.List + .Where(it => FolderKinds.Contains(it.Kind)) + .Select(item => item.ToFolder(publicBaseUrl)) + .Select(f => f)); + + folder.Descendants = folder.Descendants.AddRange(children); return folder; } - public static ConcurrentDictionary ToFolderDictionary( - this List data, string publicBaseUrl) - { - if (data == null) - return new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); - - return new ConcurrentDictionary( - data.Where(it => FolderKinds.Contains(it.Kind)) - .Select(item => item.ToFolder(publicBaseUrl)) - .Select(item => new KeyValuePair(item.FullPath, item)), - StringComparer.InvariantCultureIgnoreCase); - } - - public static ConcurrentDictionary ToFileDictionary( - this List data, string publicBaseUrl) - { - if (data == null) - return new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); - - return new ConcurrentDictionary( - data.Where(it => it.Kind == "file") - .Select(item => item.ToFile(publicBaseUrl, "")) - .ToGroupedFiles() - .Select(item => new KeyValuePair(item.FullPath, item)), - StringComparer.InvariantCultureIgnoreCase); - } - /// /// When it's a linked item, need to shift paths /// @@ -280,11 +271,11 @@ private static void PatchEntryPath(FolderInfoResult data, string home, Link link public static File ToFile(this FolderInfoResult data, string publicBaseUrl, string home = null, Link ulink = null, string fileName = null, string nameReplacement = null) { - if (ulink == null || ulink.IsLinkedToFileSystem) - if (string.IsNullOrEmpty(fileName)) - { - return new File(WebDavPath.Combine(data.Body.Home ?? "", data.Body.Name), data.Body.Size); - } + if ((ulink == null || ulink.IsLinkedToFileSystem) && + string.IsNullOrEmpty(fileName)) + { + return new File(WebDavPath.Combine(data.Body.Home ?? "", data.Body.Name), data.Body.Size); + } PatchEntryPath(data, home, ulink); @@ -345,7 +336,7 @@ private static DateTime UnixTimeStampToDateTime(ulong unixTimeStamp, DateTime de { // Unix timestamp is seconds past epoch var dtDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); - dtDateTime = dtDateTime.AddSeconds(unixTimeStamp); //.ToLocalTime(); - doesn't need, clients usially convert to localtime by itself + dtDateTime = dtDateTime.AddSeconds(unixTimeStamp); //.ToLocalTime(); - doesn't need, clients usually convert to localtime by itself return dtDateTime; } catch (Exception e) diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/MailRuBaseRepo.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/MailRuBaseRepo.cs index 2497ff82..cb4824bd 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/MailRuBaseRepo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/MailRuBaseRepo.cs @@ -17,11 +17,11 @@ abstract class MailRuBaseRepo public static readonly string[] AvailDomains = {"mail", "inbox", "bk", "list"}; - protected MailRuBaseRepo(IBasicCredentials creds) + protected MailRuBaseRepo(IBasicCredentials credentials) { - Credentials = creds; + Credentials = credentials; - if (AvailDomains.Any(d => creds.Login.Contains($"@{d}."))) + if (AvailDomains.Any(d => credentials.Login.Contains($"@{d}."))) return; string domains = AvailDomains.Aggregate((c, n) => c + ", @" + n); @@ -30,7 +30,7 @@ protected MailRuBaseRepo(IBasicCredentials creds) protected readonly IBasicCredentials Credentials; - public IAuth Authent { get; protected set; } + public IAuth Authenticator { get; protected set; } public abstract HttpCommonSettings HttpSettings { get; } public abstract Task GetShardInfo(ShardType shardType); @@ -56,7 +56,7 @@ public ICloudHasher GetHasher() private HttpRequestMessage UploadClientRequest(PushStreamContent content, File file) { var shard = GetShardInfo(ShardType.Upload).Result; - var url = new Uri($"{shard.Url}?token={Authent.AccessToken}"); //cloud_domain=2&x-email={Authent.Login.Replace("@", "%40")}& + var url = new Uri($"{shard.Url}?token={Authenticator.AccessToken}"); //cloud_domain=2&x-email={Authenticator.Login.Replace("@", "%40")}& var request = new HttpRequestMessage { @@ -78,11 +78,11 @@ private HttpRequestMessage UploadClientRequest(PushStreamContent content, File f //request.Headers.Add("Accept-Encoding", "gzip, deflate, br"); //request.Headers.Add("Accept-Language", "ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7"); //request.Headers.TryAddWithoutValidation("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36"); - //request.Headers.Add("X-CSRF-Token", Authent.AccessToken); - //request.Headers.Add("Token", Authent.AccessToken); - //request.Headers.Add("Access-token", Authent.AccessToken); + //request.Headers.Add("X-CSRF-Token", Authenticator.AccessToken); + //request.Headers.Add("Token", Authenticator.AccessToken); + //request.Headers.Add("Access-token", Authenticator.AccessToken); //content.Headers.ContentType = new MediaTypeHeaderValue("image/png"); - + request.Content = content; request.Content.Headers.ContentLength = file.OriginalSize; diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/MobileRequestRepo.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/MobileRequestRepo.cs index 97ff8552..cb0c441a 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/MobileRequestRepo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/MobileRequestRepo.cs @@ -1,7 +1,9 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; using System.IO; using System.Net; +using System.Threading; using System.Threading.Tasks; using YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests; using YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests.Types; @@ -19,33 +21,42 @@ class MobileRequestRepo : MailRuBaseRepo, IRequestRepo { private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(MobileRequestRepo)); + private readonly SemaphoreSlim _connectionLimiter; + public override HttpCommonSettings HttpSettings { get; } = new() { ClientId = "cloud-win", UserAgent = "CloudDiskOWindows 17.12.0009 beta WzBbt1Ygbm" }; - public MobileRequestRepo(IWebProxy proxy, IAuth auth, int listDepth) - :base(new Credentials(auth.Login, auth.Password)) + public MobileRequestRepo(CloudSettings settings, IWebProxy proxy, IAuth auth, int listDepth) + : base(new Credentials(auth.Login, auth.Password)) { - _listDepth = listDepth; + _connectionLimiter = new SemaphoreSlim(settings.MaxConnectionCount); + _listDepth = listDepth; - HttpSettings.Proxy = proxy; + HttpSettings.CloudSettings = settings; + HttpSettings.Proxy = proxy; - Authent = auth; + Authenticator = auth; _metaServer = new Cached(_ => { Logger.Debug("MetaServer expired, refreshing."); - var server = new MobMetaServerRequest(HttpSettings).MakeRequestAsync().Result; + var server = new MobMetaServerRequest(HttpSettings).MakeRequestAsync(_connectionLimiter).Result; return server; }, _ => TimeSpan.FromSeconds(MetaServerExpiresSec)); + ServicePointManager.DefaultConnectionLimit = int.MaxValue; + + // required for Windows 7 breaking connection + ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13 | SecurityProtocolType.Tls12; + //_downloadServer = new Cached(old => // { // Logger.Debug("DownloadServer expired, refreshing."); - // var server = new GetServerRequest(HttpSettings).MakeRequestAsync().Result; + // var server = new GetServerRequest(HttpSettings).MakeRequestAsync(_connectionLimiter).Result; // return server; // }, // value => TimeSpan.FromSeconds(DownloadServerExpiresSec)); @@ -76,14 +87,14 @@ public Stream GetDownloadStream(File file, long? start = null, long? end = null) //public HttpWebRequest DownloadRequest(long instart, long inend, File file, ShardInfo shard) //{ - // string url = $"{_downloadServer.Value.Url}{Uri.EscapeDataString(file.FullPath)}?token={Authent.AccessToken}&client_id={HttpSettings.ClientId}"; + // string url = $"{_downloadServer.Value.Url}{Uri.EscapeDataString(file.FullPath)}?token={Authenticator.AccessToken}&client_id={HttpSettings.ClientId}"; // var request = (HttpWebRequest)WebRequest.Create(url); // request.Headers.Add("Accept-Ranges", "bytes"); // request.AddRange(instart, inend); // request.Proxy = HttpSettings.Proxy; - // request.CookieContainer = Authent.Cookies; + // request.CookieContainer = Authenticator.Cookies; // request.Method = "GET"; // request.ContentType = MediaTypeNames.Application.Octet; // request.Accept = "*/*"; @@ -135,14 +146,15 @@ public async Task FolderInfo(RemotePath path, int offset = 0, int limit if (path.IsLink) throw new NotImplementedException(nameof(FolderInfo)); - var req = new ListRequest(HttpSettings, Authent, _metaServer.Value.Url, path.Path, _listDepth); - var res = await req.MakeRequestAsync(); + var req = new ListRequest(HttpSettings, Authenticator, _metaServer.Value.Url, path.Path, _listDepth); + var res = await req.MakeRequestAsync(_connectionLimiter); switch (res.Item) { case FsFolder fsFolder: { - var f = new Folder(fsFolder.Size == null ? 0 : (long)fsFolder.Size.Value, fsFolder.FullPath); + var folder = new Folder(fsFolder.Size == null ? 0 : (long)fsFolder.Size.Value, fsFolder.FullPath); + var children = new List(); foreach (var fsi in fsFolder.Items) { switch (fsi) @@ -154,20 +166,21 @@ public async Task FolderInfo(RemotePath path, int offset = 0, int limit CreationTimeUtc = fsfi.ModifDate, LastWriteTimeUtc = fsfi.ModifDate }; - f.Files.TryAdd(fi.FullPath, fi); + children.Add(fi); break; } case FsFolder fsfo: { var fo = new Folder(fsfo.Size == null ? 0 : (long) fsfo.Size.Value, fsfo.FullPath); - f.Folders.TryAdd(fo.FullPath, fo); + children.Add(fo); break; } default: throw new Exception($"Unknown item type {fsi.GetType()}"); } } - return f; + folder.Descendants = folder.Descendants.AddRange(children); + return folder; } case FsFile fsFile: { @@ -193,7 +206,7 @@ public Task ItemInfo(RemotePath path, int offset = 0, int limi public async Task AccountInfo() { - var req = await new AccountInfoRequest(HttpSettings, Authent).MakeRequestAsync(); + var req = await new AccountInfoRequest(HttpSettings, Authenticator).MakeRequestAsync(_connectionLimiter); var res = req.ToAccountInfo(); return res; } @@ -217,8 +230,8 @@ public async Task Rename(string fullPath, string newName) { string target = WebDavPath.Combine(WebDavPath.Parent(fullPath), newName); - await new MoveRequest(HttpSettings, Authent, _metaServer.Value.Url, fullPath, target) - .MakeRequestAsync(); + await new MoveRequest(HttpSettings, Authenticator, _metaServer.Value.Url, fullPath, target) + .MakeRequestAsync(_connectionLimiter); var res = new RenameResult { IsSuccess = true }; return res; } @@ -240,17 +253,20 @@ public void CleanTrash() public async Task CreateFolder(string path) { - return (await new CreateFolderRequest(HttpSettings, Authent, _metaServer.Value.Url, path).MakeRequestAsync()) - .ToCreateFolderResult(); + var folerRequest = await new CreateFolderRequest(HttpSettings, Authenticator, _metaServer.Value.Url, path) + .MakeRequestAsync(_connectionLimiter); + return folerRequest.ToCreateFolderResult(); } public async Task AddFile(string fileFullPath, IFileHash fileHash, FileSize fileSize, DateTime dateTime, ConflictResolver? conflictResolver) { - var res = await new MobAddFileRequest(HttpSettings, Authent, _metaServer.Value.Url, + var res = await new MobAddFileRequest(HttpSettings, Authenticator, _metaServer.Value.Url, fileFullPath, fileHash.Hash.Value, fileSize, dateTime, conflictResolver) - .MakeRequestAsync(); + .MakeRequestAsync(_connectionLimiter); return res.ToAddFileResult(); } + + public async Task ActiveOperationsAsync() => await Task.FromResult(null); } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/OAuth.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/OAuth.cs index de20e139..8eae3307 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/OAuth.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/OAuth.cs @@ -1,5 +1,6 @@ using System; using System.Net; +using System.Threading; using System.Threading.Tasks; using YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests; using YaR.Clouds.Base.Requests; @@ -12,14 +13,17 @@ internal class OAuth : IAuth { private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(OAuth)); + private readonly SemaphoreSlim _connectionLimiter; private readonly HttpCommonSettings _settings; private readonly IBasicCredentials _creds; private readonly AuthCodeRequiredDelegate _onAuthCodeRequired; - public OAuth(HttpCommonSettings settings, IBasicCredentials creds, AuthCodeRequiredDelegate onAuthCodeRequired) + public OAuth(SemaphoreSlim connectionLimiter, + HttpCommonSettings settings, IBasicCredentials creds, AuthCodeRequiredDelegate onAuthCodeRequired) { _settings = settings; + _connectionLimiter = connectionLimiter; _creds = creds; _onAuthCodeRequired = onAuthCodeRequired; Cookies = new CookieContainer(); @@ -59,7 +63,7 @@ private async Task Auth() if (_creds.IsAnonymous) return null; - var req = await new OAuthRequest(_settings, _creds).MakeRequestAsync(); + var req = await new OAuthRequest(_settings, _creds).MakeRequestAsync(_connectionLimiter); var res = req.ToAuthTokenResult(); if (!res.IsSecondStepRequired) @@ -73,7 +77,7 @@ private async Task Auth() throw new Exception("Empty 2Factor code"); var ssreq = await new OAuthSecondStepRequest(_settings, _creds.Login, res.TsaToken, code) - .MakeRequestAsync(); + .MakeRequestAsync(_connectionLimiter); res = ssreq.ToAuthTokenResult(); @@ -82,7 +86,7 @@ private async Task Auth() private async Task Refresh(string refreshToken) { - var req = await new OAuthRefreshRequest(_settings, refreshToken).MakeRequestAsync(); + var req = await new OAuthRefreshRequest(_settings, refreshToken).MakeRequestAsync(_connectionLimiter); var res = req.ToAuthTokenResult(refreshToken); return res; } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/ShardManager.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/ShardManager.cs index 438ca074..5f83475d 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/ShardManager.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/ShardManager.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests; using YaR.Clouds.Base.Requests.Types; using YaR.Clouds.Common; @@ -12,14 +13,14 @@ class ShardManager private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(ShardManager)); - public ShardManager(IRequestRepo repo) + public ShardManager(SemaphoreSlim connectionLimiter, IRequestRepo repo) { var httpsettings = repo.HttpSettings; _metaServer = new Cached(_ => { Logger.Debug("Requesting new meta server"); - var server = new MobMetaServerRequest(httpsettings).MakeRequestAsync().Result; + var server = new MobMetaServerRequest(httpsettings).MakeRequestAsync(connectionLimiter).Result; return server; }, _ => TimeSpan.FromSeconds(MetaServerExpiresSec)); @@ -36,7 +37,7 @@ public ShardManager(IRequestRepo repo) DownloadServersPending = new Pending>(8, () => new Cached(_ => { - var server = new GetServerRequest(httpsettings).MakeRequestAsync().Result; + var server = new GetServerRequest(httpsettings).MakeRequestAsync(connectionLimiter).Result; Logger.Debug($"Download server changed to {server.Url}"); return server; }, @@ -45,7 +46,7 @@ public ShardManager(IRequestRepo repo) UploadServer = new Cached(_ => { - var server = new GetUploadServerRequest(httpsettings).MakeRequestAsync().Result; + var server = new GetUploadServerRequest(httpsettings).MakeRequestAsync(connectionLimiter).Result; Logger.Debug($"Upload server changed to {server.Url}"); return new ShardInfo { Count = 0, Type = ShardType.Upload, Url = server.Url }; }, @@ -55,7 +56,7 @@ public ShardManager(IRequestRepo repo) WeblinkDownloadServersPending = new Pending>(8, () => new Cached(_ => { - var data = new WeblinkGetServerRequest(httpsettings).MakeRequestAsync().Result; + var data = new WeblinkGetServerRequest(httpsettings).MakeRequestAsync(connectionLimiter).Result; var serverUrl = data.Body.WeblinkGet[0].Url; Logger.Debug($"weblink Download server changed to {serverUrl}"); var res = new ServerRequestResult { Url = serverUrl }; diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebBin/Requests/DownloadRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebBin/Requests/DownloadRequest.cs index 9f6531bf..fb428c0e 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebBin/Requests/DownloadRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebBin/Requests/DownloadRequest.cs @@ -9,16 +9,17 @@ namespace YaR.Clouds.Base.Repos.MailRuCloud.WebBin.Requests { class DownloadRequest { - public DownloadRequest(HttpCommonSettings settings, IAuth authent, File file, long instart, long inend, string downServerUrl, IEnumerable publicBaseUrls) + public DownloadRequest(HttpCommonSettings settings, IAuth authenticator, + File file, long instart, long inend, string downServerUrl, IEnumerable publicBaseUrls) { - Request = CreateRequest(settings, authent, file, instart, inend, downServerUrl, publicBaseUrls); + Request = CreateRequest(settings, authenticator, file, instart, inend, downServerUrl, publicBaseUrls); } public HttpWebRequest Request { get; } private static HttpWebRequest CreateRequest(HttpCommonSettings settings, - IAuth authent, File file, long instart, long inend, string downServerUrl, IEnumerable publicBaseUrls) - //(IAuth authent, IWebProxy proxy, string url, long instart, long inend, string userAgent) + IAuth authenticator, File file, long instart, long inend, string downServerUrl, IEnumerable publicBaseUrls) + //(IAuth authenticator, IWebProxy proxy, string url, long instart, long inend, string userAgent) { bool isLinked = !file.PublicLinks.IsEmpty; @@ -39,7 +40,7 @@ private static HttpWebRequest CreateRequest(HttpCommonSettings settings, else { url = $"{downServerUrl}{Uri.EscapeDataString(file.FullPath.TrimStart('/'))}"; - url += $"?client_id={settings.ClientId}&token={authent.AccessToken}"; + url += $"?client_id={settings.ClientId}&token={authenticator.AccessToken}"; } var uri = new Uri(url); @@ -52,7 +53,7 @@ private static HttpWebRequest CreateRequest(HttpCommonSettings settings, request.AddRange(instart, inend); request.Proxy = settings.Proxy; - //request.CookieContainer = authent.Cookies; + //request.CookieContainer = authenticator.Cookies; request.Method = "GET"; //request.Accept = "*/*"; //request.UserAgent = settings.UserAgent; diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebBin/WebBinRequestRepo.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebBin/WebBinRequestRepo.cs index d129400d..8483ce1b 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebBin/WebBinRequestRepo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebBin/WebBinRequestRepo.cs @@ -19,6 +19,8 @@ using AccountInfoRequest = YaR.Clouds.Base.Repos.MailRuCloud.WebM1.Requests.AccountInfoRequest; using CreateFolderRequest = YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests.CreateFolderRequest; using MoveRequest = YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests.MoveRequest; +using System.Threading; +using YaR.Clouds.Extensions; namespace YaR.Clouds.Base.Repos.MailRuCloud.WebBin { @@ -29,14 +31,13 @@ class WebBinRequestRepo : MailRuBaseRepo, IRequestRepo { private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(WebBinRequestRepo)); - private readonly CloudSettings _settings; + private readonly SemaphoreSlim _connectionLimiter; private readonly AuthCodeRequiredDelegate _onAuthCodeRequired; - protected ShardManager ShardManager => _shardManager ??= new ShardManager(this); - private ShardManager _shardManager; + protected ShardManager ShardManager { get; private set; } - protected IRequestRepo AnonymousRepo => _anonymousRepo ??= new AnonymousRepo(_settings, Credentials, - _onAuthCodeRequired); + protected IRequestRepo AnonymousRepo => _anonymousRepo ??= + new AnonymousRepo(HttpSettings.CloudSettings, Credentials, _onAuthCodeRequired); private IRequestRepo _anonymousRepo; @@ -45,33 +46,38 @@ class WebBinRequestRepo : MailRuBaseRepo, IRequestRepo ClientId = "cloud-android" }; - public WebBinRequestRepo(CloudSettings settings, IBasicCredentials creds, AuthCodeRequiredDelegate onAuthCodeRequired) - :base(creds) + public WebBinRequestRepo(CloudSettings settings, IBasicCredentials credentials, AuthCodeRequiredDelegate onAuthCodeRequired) + : base(credentials) { - _settings = settings; + _connectionLimiter = new SemaphoreSlim(settings.MaxConnectionCount); + + HttpSettings.CloudSettings = settings; + HttpSettings.UserAgent = settings.UserAgent; + HttpSettings.Proxy = settings.Proxy; + _onAuthCodeRequired = onAuthCodeRequired; - ServicePointManager.DefaultConnectionLimit = int.MaxValue; + ShardManager = new ShardManager(_connectionLimiter, this); + + ServicePointManager.DefaultConnectionLimit = int.MaxValue; // required for Windows 7 breaking connection ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13 | SecurityProtocolType.Tls12; - HttpSettings.Proxy = settings.Proxy; - HttpSettings.UserAgent = settings.UserAgent; - Authent = new OAuth(HttpSettings, creds, onAuthCodeRequired); + Authenticator = new OAuth(_connectionLimiter, HttpSettings, credentials, onAuthCodeRequired); } - - public Stream GetDownloadStream(File afile, long? start = null, long? end = null) + + public Stream GetDownloadStream(File file, long? start = null, long? end = null) { - var istream = GetDownloadStreamInternal(afile, start, end); + var istream = GetDownloadStreamInternal(file, start, end); return istream; } - private DownloadStream GetDownloadStreamInternal(File afile, long? start = null, long? end = null) + private DownloadStream GetDownloadStreamInternal(File file, long? start = null, long? end = null) { - bool isLinked = !afile.PublicLinks.IsEmpty; + bool isLinked = !file.PublicLinks.IsEmpty; Cached downServer = null; var pendingServers = isLinked @@ -86,7 +92,7 @@ CustomDisposable ResponseGenerator(long instart, long inend, Fi { downServer = pendingServers.Next(downServer); - request = new DownloadRequest(HttpSettings, Authent, file, instart, inend, downServer.Value.Url, PublicBaseUrls); + request = new DownloadRequest(HttpSettings, Authenticator, file, instart, inend, downServer.Value.Url, PublicBaseUrls); watch.Start(); var response = (HttpWebResponse)request.GetResponse(); @@ -101,8 +107,8 @@ CustomDisposable ResponseGenerator(long instart, long inend, Fi } }; }, - exception => - ((exception as WebException)?.Response as HttpWebResponse)?.StatusCode == HttpStatusCode.NotFound, + exception => + exception is WebException { Response: HttpWebResponse { StatusCode: HttpStatusCode.NotFound } }, exception => { pendingServers.Free(downServer); @@ -113,10 +119,10 @@ CustomDisposable ResponseGenerator(long instart, long inend, Fi return resp; } - var stream = new DownloadStream(ResponseGenerator, afile, start, end); + var stream = new DownloadStream(ResponseGenerator, file, start, end); return stream; } - + /// /// Get shard info that to do post get request. Can be use for anonymous user. /// @@ -136,7 +142,7 @@ public override async Task GetShardInfo(ShardType shardType) var banned = ShardManager.BannedShards.Value; if (banned.All(bsh => bsh.Url != ishard.Url)) { - if (refreshed) Authent.ExpireDownloadToken(); + if (refreshed) Authenticator.ExpireDownloadToken(); return ishard; } ShardManager.CachedShards.Expire(); @@ -152,26 +158,26 @@ public override async Task GetShardInfo(ShardType shardType) public async Task CloneItem(string fromUrl, string toPath) { - var req = await new CloneItemRequest(HttpSettings, Authent, fromUrl, toPath).MakeRequestAsync(); + var req = await new CloneItemRequest(HttpSettings, Authenticator, fromUrl, toPath).MakeRequestAsync(_connectionLimiter); var res = req.ToCloneItemResult(); return res; } public async Task Copy(string sourceFullPath, string destinationPath, ConflictResolver? conflictResolver = null) { - var req = await new CopyRequest(HttpSettings, Authent, sourceFullPath, destinationPath, conflictResolver).MakeRequestAsync(); + var req = await new CopyRequest(HttpSettings, Authenticator, sourceFullPath, destinationPath, conflictResolver).MakeRequestAsync(_connectionLimiter); var res = req.ToCopyResult(); return res; } public async Task Move(string sourceFullPath, string destinationPath, ConflictResolver? conflictResolver = null) { - //var req = await new MoveRequest(HttpSettings, Authent, sourceFullPath, destinationPath).MakeRequestAsync(); + //var req = await new MoveRequest(HttpSettings, Authent, sourceFullPath, destinationPath).MakeRequestAsync(_connectionLimiter); //var res = req.ToCopyResult(); //return res; - var req = await new MoveRequest(HttpSettings, Authent, ShardManager.MetaServer.Url, sourceFullPath, destinationPath) - .MakeRequestAsync(); + var req = await new MoveRequest(HttpSettings, Authenticator, ShardManager.MetaServer.Url, sourceFullPath, destinationPath) + .MakeRequestAsync(_connectionLimiter); var res = req.ToCopyResult(WebDavPath.Name(destinationPath)); return res; @@ -180,46 +186,39 @@ public async Task Move(string sourceFullPath, string destinationPath private async Task FolderInfo(string path, int depth = 1) { - ListRequest.Result datares; try { - datares = await new ListRequest(HttpSettings, Authent, ShardManager.MetaServer.Url, path, depth) - .MakeRequestAsync(); + ListRequest.Result dataRes = + await new ListRequest(HttpSettings, Authenticator, ShardManager.MetaServer.Url, path, depth) + .MakeRequestAsync(_connectionLimiter); // если файл разбит или зашифрован - то надо взять все куски // в протоколе V2 на запрос к файлу сразу приходит листинг каталога, в котором он лежит // здесь (протокол Bin) приходит информация именно по указанному файлу // поэтому вот такой костыль с двойным запросом //TODO: переделать двойной запрос к файлу - if (datares.Item is FsFile { Size: < 2048 }) + if (dataRes.Item is FsFile { Size: < 2048 }) { string name = WebDavPath.Name(path); path = WebDavPath.Parent(path); - datares = await new ListRequest(HttpSettings, Authent, ShardManager.MetaServer.Url, path, 1) - .MakeRequestAsync(); + dataRes = await new ListRequest(HttpSettings, Authenticator, ShardManager.MetaServer.Url, path, 1) + .MakeRequestAsync(_connectionLimiter); - var zz = datares.ToFolder(); + var folder = dataRes.ToFolder(); - // return zz.Files.Values.FirstOrDefault(f => f.Name == name); - // Вариант без перебора предпочтительнее - if (zz.Files.TryGetValue(path, out var file)) - return file; - return null; + return folder.Descendants.FirstOrDefault(f => f.Name == name); } + return dataRes.ToEntry(); } catch (RequestException re) when (re.StatusCode == HttpStatusCode.NotFound) { return null; } - catch (WebException e) when ((e.Response as HttpWebResponse)?.StatusCode == HttpStatusCode.NotFound) + catch (WebException e) when (e.Response is HttpWebResponse { StatusCode: HttpStatusCode.NotFound }) { return null; } - - var z = datares.ToEntry(); - - return z; } public async Task FolderInfo(RemotePath path, int offset = 0, int limit = int.MaxValue, int depth = 1) @@ -233,10 +232,10 @@ public async Task FolderInfo(RemotePath path, int offset = 0, int limit FolderInfoResult datares; try { - datares = await new FolderInfoRequest(HttpSettings, Authent, path, offset, limit) - .MakeRequestAsync(); + datares = await new FolderInfoRequest(HttpSettings, Authenticator, path, offset, limit) + .MakeRequestAsync(_connectionLimiter); } - catch (WebException e) when ((e.Response as HttpWebResponse)?.StatusCode == HttpStatusCode.NotFound) + catch (WebException e) when (e.Response is HttpWebResponse { StatusCode: HttpStatusCode.NotFound }) { return null; } @@ -259,8 +258,8 @@ public async Task FolderInfo(RemotePath path, int offset = 0, int limit home: WebDavPath.Parent(path.Path ?? string.Empty), ulink: path.Link, fileName: path.Link == null ? WebDavPath.Name(path.Path) : path.Link.OriginalName, - nameReplacement: path.Link?.IsLinkedToFileSystem ?? true ? WebDavPath.Name(path.Path) : path.Link.Name ) - : datares.ToFolder(PublicBaseUrlDefault, path.Path, path.Link); + nameReplacement: path.Link?.IsLinkedToFileSystem ?? true ? WebDavPath.Name(path.Path) : path.Link.Name) + : (IEntry)datares.ToFolder(PublicBaseUrlDefault, path.Path, path.Link); return entry; } @@ -271,25 +270,25 @@ public async Task FolderInfo(RemotePath path, int offset = 0, int limit public async Task ItemInfo(RemotePath path, int offset = 0, int limit = int.MaxValue) { - var req = await new ItemInfoRequest(HttpSettings, Authent, path, offset, limit).MakeRequestAsync(); + var req = await new ItemInfoRequest(HttpSettings, Authenticator, path, offset, limit).MakeRequestAsync(_connectionLimiter); return req; } public async Task AccountInfo() { - var req = await new AccountInfoRequest(HttpSettings, Authent).MakeRequestAsync(); + var req = await new AccountInfoRequest(HttpSettings, Authenticator).MakeRequestAsync(_connectionLimiter); var res = req.ToAccountInfo(); return res; } public async Task Publish(string fullPath) { - var req = await new PublishRequest(HttpSettings, Authent, fullPath).MakeRequestAsync(); + var req = await new PublishRequest(HttpSettings, Authenticator, fullPath).MakeRequestAsync(_connectionLimiter); var res = req.ToPublishResult(); if (res.IsSuccess) { - CachedSharedList.Value[fullPath] = new [] {new PublicLinkInfo(PublicBaseUrlDefault + res.Url)}; + CachedSharedList.Value[fullPath] = new[] { new PublicLinkInfo(PublicBaseUrlDefault + res.Url) }; } return res; @@ -303,27 +302,27 @@ public async Task Unpublish(Uri publicLink, string fullPath) CachedSharedList.Value.Remove(item.Key); } - var req = await new UnpublishRequest(this, HttpSettings, Authent, publicLink.OriginalString).MakeRequestAsync(); + var req = await new UnpublishRequest(this, HttpSettings, Authenticator, publicLink.OriginalString).MakeRequestAsync(_connectionLimiter); var res = req.ToUnpublishResult(); return res; } public async Task Remove(string fullPath) { - var req = await new RemoveRequest(HttpSettings, Authent, fullPath).MakeRequestAsync(); + var req = await new RemoveRequest(HttpSettings, Authenticator, fullPath).MakeRequestAsync(_connectionLimiter); var res = req.ToRemoveResult(); return res; } public async Task Rename(string fullPath, string newName) { - //var req = await new RenameRequest(HttpSettings, Authent, fullPath, newName).MakeRequestAsync(); + //var req = await new RenameRequest(HttpSettings, Authent, fullPath, newName).MakeRequestAsync(_connectionLimiter); //var res = req.ToRenameResult(); //return res; string newFullPath = WebDavPath.Combine(WebDavPath.Parent(fullPath), newName); - var req = await new MoveRequest(HttpSettings, Authent, ShardManager.MetaServer.Url, fullPath, newFullPath) - .MakeRequestAsync(); + var req = await new MoveRequest(HttpSettings, Authenticator, ShardManager.MetaServer.Url, fullPath, newFullPath) + .MakeRequestAsync(_connectionLimiter); var res = req.ToRenameResult(); return res; @@ -331,9 +330,10 @@ public async Task Rename(string fullPath, string newName) public Dictionary GetShardInfo1() { - return Authent.IsAnonymous - ? new Clouds.Base.Repos.MailRuCloud.WebV2.Requests.ShardInfoRequest(HttpSettings, Authent).MakeRequestAsync().Result.ToShardInfo() - : new ShardInfoRequest(HttpSettings, Authent).MakeRequestAsync().Result.ToShardInfo(); + return Authenticator.IsAnonymous + ? new WebV2.Requests + .ShardInfoRequest(HttpSettings, Authenticator).MakeRequestAsync(_connectionLimiter).Result.ToShardInfo() + : new ShardInfoRequest(HttpSettings, Authenticator).MakeRequestAsync(_connectionLimiter).Result.ToShardInfo(); } @@ -360,15 +360,15 @@ public Cached>> CachedSharedList private async Task GetShareListInner() { - var res = await new SharedListRequest(HttpSettings, Authent) - .MakeRequestAsync(); + var res = await new SharedListRequest(HttpSettings, Authenticator) + .MakeRequestAsync(_connectionLimiter); return res; } public IEnumerable GetShareLinks(string path) { - if (!CachedSharedList.Value.TryGetValue(path, out var links)) + if (!CachedSharedList.Value.TryGetValue(path, out var links)) yield break; foreach (var link in links) @@ -382,28 +382,29 @@ public void CleanTrash() public async Task CreateFolder(string path) { - //return (await new CreateFolderRequest(HttpSettings, Authent, path).MakeRequestAsync()) + //return (await new CreateFolderRequest(HttpSettings, Authenticator, path).MakeRequestAsync()) // .ToCreateFolderResult(); - return (await new CreateFolderRequest(HttpSettings, Authent, ShardManager.MetaServer.Url, path).MakeRequestAsync()) + return (await new CreateFolderRequest(HttpSettings, Authenticator, ShardManager.MetaServer.Url, path).MakeRequestAsync(_connectionLimiter)) .ToCreateFolderResult(); } public async Task AddFile(string fileFullPath, IFileHash fileHash, FileSize fileSize, DateTime dateTime, ConflictResolver? conflictResolver) { - //var res = await new CreateFileRequest(Proxy, Authent, fileFullPath, fileHash, fileSize, conflictResolver) - // .MakeRequestAsync(); + //var res = await new CreateFileRequest(Proxy, Authenticator, fileFullPath, fileHash, fileSize, conflictResolver) + // .MakeRequestAsync(_connectionLimiter); //return res.ToAddFileResult(); //using Mobile request because of supporting file modified time //TODO: refact, make mixed repo - var req = await new MobAddFileRequest(HttpSettings, Authent, ShardManager.MetaServer.Url, fileFullPath, fileHash.Hash.Value, fileSize, dateTime, conflictResolver) - .MakeRequestAsync(); + var req = await new MobAddFileRequest(HttpSettings, Authenticator, ShardManager.MetaServer.Url, fileFullPath, fileHash.Hash.Value, fileSize, dateTime, conflictResolver) + .MakeRequestAsync(_connectionLimiter); var res = req.ToAddFileResult(); return res; } + + public async Task ActiveOperationsAsync() => await Task.FromResult(null); } } - diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/WebM1RequestRepo.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/WebM1RequestRepo.cs index cf52b7a8..8a20d14f 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/WebM1RequestRepo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/WebM1RequestRepo.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Net; using System.Net.Mime; +using System.Threading; using System.Threading.Tasks; using YaR.Clouds.Base.Repos.MailRuCloud.WebM1.Requests; using YaR.Clouds.Base.Requests; @@ -25,9 +26,9 @@ abstract class WebM1RequestRepo : MailRuBaseRepo, IRequestRepo private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(WebM1RequestRepo)); private readonly AuthCodeRequiredDelegate _onAuthCodeRequired; - protected ShardManager ShardManager => _shardManager ??= new ShardManager(this); - private ShardManager _shardManager; + private readonly SemaphoreSlim _connectionLimiter; + protected ShardManager ShardManager { get; private set; } protected IRequestRepo AnonymousRepo => throw new NotImplementedException(); @@ -37,15 +38,22 @@ abstract class WebM1RequestRepo : MailRuBaseRepo, IRequestRepo UserAgent = "CloudDiskOWindows 17.12.0009 beta WzBbt1Ygbm" }; - protected WebM1RequestRepo(IWebProxy proxy, IBasicCredentials creds, AuthCodeRequiredDelegate onAuthCodeRequired) - :base(creds) + protected WebM1RequestRepo(CloudSettings settings, IWebProxy proxy, + IBasicCredentials credentials, AuthCodeRequiredDelegate onAuthCodeRequired) + : base(credentials) { + _connectionLimiter = new SemaphoreSlim(settings.MaxConnectionCount); + ShardManager = new ShardManager(_connectionLimiter, this); + HttpSettings.Proxy = proxy; + HttpSettings.CloudSettings = settings; _onAuthCodeRequired = onAuthCodeRequired; ServicePointManager.DefaultConnectionLimit = int.MaxValue; - HttpSettings.Proxy = proxy; - Authent = new OAuth(HttpSettings, creds, onAuthCodeRequired); + // required for Windows 7 breaking connection + ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13 | SecurityProtocolType.Tls12; + + Authenticator = new OAuth(_connectionLimiter, HttpSettings, credentials, onAuthCodeRequired); CachedSharedList = new Cached>>(_ => { @@ -63,9 +71,9 @@ protected WebM1RequestRepo(IWebProxy proxy, IBasicCredentials creds, AuthCodeReq - public Stream GetDownloadStream(File afile, long? start = null, long? end = null) + public Stream GetDownloadStream(File file, long? start = null, long? end = null) { - var istream = GetDownloadStreamInternal(afile, start, end); + var istream = GetDownloadStreamInternal(file, start, end); return istream; } @@ -89,7 +97,7 @@ CustomDisposable ResponseGenerator(long instart, long inend, Fi string url =(isLinked ? $"{downServer.Value.Url}{WebDavPath.EscapeDataString(file.PublicLinks.Values.FirstOrDefault()?.Uri.PathAndQuery)}" : $"{downServer.Value.Url}{Uri.EscapeDataString(file.FullPath.TrimStart('/'))}") + - $"?client_id={HttpSettings.ClientId}&token={Authent.AccessToken}"; + $"?client_id={HttpSettings.ClientId}&token={Authenticator.AccessToken}"; var uri = new Uri(url); #pragma warning disable SYSLIB0014 // Type or member is obsolete @@ -98,7 +106,7 @@ CustomDisposable ResponseGenerator(long instart, long inend, Fi request.AddRange(instart, inend); request.Proxy = HttpSettings.Proxy; - request.CookieContainer = Authent.Cookies; + request.CookieContainer = Authenticator.Cookies; request.Method = "GET"; request.Accept = "*/*"; request.UserAgent = HttpSettings.UserAgent; @@ -130,7 +138,7 @@ CustomDisposable ResponseGenerator(long instart, long inend, Fi }; }, exception => - ((exception as WebException)?.Response as HttpWebResponse)?.StatusCode == HttpStatusCode.NotFound, + exception is WebException { Response: HttpWebResponse { StatusCode: HttpStatusCode.NotFound } }, exception => { pendingServers.Free(downServer); @@ -149,11 +157,11 @@ CustomDisposable ResponseGenerator(long instart, long inend, Fi //public HttpWebRequest UploadRequest(File file, UploadMultipartBoundary boundary) //{ // var shard = GetShardInfo(ShardType.Upload).Result; - // var url = new Uri($"{shard.Url}?client_id={HttpSettings.ClientId}&token={Authent.AccessToken}"); + // var url = new Uri($"{shard.Url}?client_id={HttpSettings.ClientId}&token={Authenticator.AccessToken}"); // var request = (HttpWebRequest)WebRequest.Create(url); // request.Proxy = HttpSettings.Proxy; - // request.CookieContainer = Authent.Cookies; + // request.CookieContainer = Authenticator.Cookies; // request.Method = "PUT"; // request.ContentLength = file.OriginalSize; // request.Accept = "*/*"; @@ -183,7 +191,7 @@ public override async Task GetShardInfo(ShardType shardType) var banned = ShardManager.BannedShards.Value; if (banned.All(bsh => bsh.Url != ishard.Url)) { - if (refreshed) Authent.ExpireDownloadToken(); + if (refreshed) Authenticator.ExpireDownloadToken(); return ishard; } ShardManager.CachedShards.Expire(); @@ -199,26 +207,26 @@ public override async Task GetShardInfo(ShardType shardType) public async Task CloneItem(string fromUrl, string toPath) { - var req = await new CloneItemRequest(HttpSettings, Authent, fromUrl, toPath).MakeRequestAsync(); + var req = await new CloneItemRequest(HttpSettings, Authenticator, fromUrl, toPath).MakeRequestAsync(_connectionLimiter); var res = req.ToCloneItemResult(); return res; } public async Task Copy(string sourceFullPath, string destinationPath, ConflictResolver? conflictResolver = null) { - var req = await new CopyRequest(HttpSettings, Authent, sourceFullPath, destinationPath, conflictResolver).MakeRequestAsync(); + var req = await new CopyRequest(HttpSettings, Authenticator, sourceFullPath, destinationPath, conflictResolver).MakeRequestAsync(_connectionLimiter); var res = req.ToCopyResult(); return res; } public async Task Move(string sourceFullPath, string destinationPath, ConflictResolver? conflictResolver = null) { - //var req = await new MoveRequest(HttpSettings, Authent, sourceFullPath, destinationPath).MakeRequestAsync(); + //var req = await new MoveRequest(HttpSettings, Authenticator, sourceFullPath, destinationPath).MakeRequestAsync(_connectionLimiter); //var res = req.ToCopyResult(); //return res; - var req = await new MoveRequest(HttpSettings, Authent, ShardManager.MetaServer.Url, sourceFullPath, destinationPath) - .MakeRequestAsync(); + var req = await new MoveRequest(HttpSettings, Authenticator, ShardManager.MetaServer.Url, sourceFullPath, destinationPath) + .MakeRequestAsync(_connectionLimiter); var res = req.ToCopyResult(WebDavPath.Name(destinationPath)); return res; @@ -236,10 +244,10 @@ public async Task FolderInfo(RemotePath path, int offset = 0, int limit FolderInfoResult datares; try { - datares = await new FolderInfoRequest(HttpSettings, Authent, path, offset, limit) - .MakeRequestAsync(); + datares = await new FolderInfoRequest(HttpSettings, Authenticator, path, offset, limit) + .MakeRequestAsync(_connectionLimiter); } - catch (WebException e) when ((e.Response as HttpWebResponse)?.StatusCode == HttpStatusCode.NotFound) + catch (WebException e) when (e.Response is HttpWebResponse { StatusCode: HttpStatusCode.NotFound }) { return null; } @@ -263,27 +271,27 @@ public async Task FolderInfo(RemotePath path, int offset = 0, int limit ulink: path.Link, fileName: path.Link == null ? WebDavPath.Name(path.Path) : path.Link.OriginalName, nameReplacement: path.Link?.IsLinkedToFileSystem ?? true ? WebDavPath.Name(path.Path) : null ) - : datares.ToFolder(PublicBaseUrlDefault, path.Path, path.Link); + : (IEntry)datares.ToFolder(PublicBaseUrlDefault, path.Path, path.Link); return entry; } public async Task ItemInfo(RemotePath path, int offset = 0, int limit = int.MaxValue) { - var req = await new ItemInfoRequest(HttpSettings, Authent, path, offset, limit).MakeRequestAsync(); + var req = await new ItemInfoRequest(HttpSettings, Authenticator, path, offset, limit).MakeRequestAsync(_connectionLimiter); return req; } public async Task AccountInfo() { - var req = await new AccountInfoRequest(HttpSettings, Authent).MakeRequestAsync(); + var req = await new AccountInfoRequest(HttpSettings, Authenticator).MakeRequestAsync(_connectionLimiter); var res = req.ToAccountInfo(); return res; } public async Task Publish(string fullPath) { - var req = await new PublishRequest(HttpSettings, Authent, fullPath).MakeRequestAsync(); + var req = await new PublishRequest(HttpSettings, Authenticator, fullPath).MakeRequestAsync(_connectionLimiter); var res = req.ToPublishResult(); if (res.IsSuccess) @@ -302,27 +310,27 @@ public async Task Unpublish(Uri publicLink, string fullPath = n CachedSharedList.Value.Remove(item.Key); } - var req = await new UnpublishRequest(this, HttpSettings, Authent, publicLink.OriginalString).MakeRequestAsync(); + var req = await new UnpublishRequest(this, HttpSettings, Authenticator, publicLink.OriginalString).MakeRequestAsync(_connectionLimiter); var res = req.ToUnpublishResult(); return res; } public async Task Remove(string fullPath) { - var req = await new RemoveRequest(HttpSettings, Authent, fullPath).MakeRequestAsync(); + var req = await new RemoveRequest(HttpSettings, Authenticator, fullPath).MakeRequestAsync(_connectionLimiter); var res = req.ToRemoveResult(); return res; } public async Task Rename(string fullPath, string newName) { - //var req = await new RenameRequest(HttpSettings, Authent, fullPath, newName).MakeRequestAsync(); + //var req = await new RenameRequest(HttpSettings, Authenticator, fullPath, newName).MakeRequestAsync(_connectionLimiter); //var res = req.ToRenameResult(); //return res; string newFullPath = WebDavPath.Combine(WebDavPath.Parent(fullPath), newName); - var req = await new MoveRequest(HttpSettings, Authent, ShardManager.MetaServer.Url, fullPath, newFullPath) - .MakeRequestAsync(); + var req = await new MoveRequest(HttpSettings, Authenticator, ShardManager.MetaServer.Url, fullPath, newFullPath) + .MakeRequestAsync(_connectionLimiter); var res = req.ToRenameResult(); return res; @@ -330,9 +338,10 @@ public async Task Rename(string fullPath, string newName) public Dictionary GetShardInfo1() { - return Authent.IsAnonymous - ? new Clouds.Base.Repos.MailRuCloud.WebV2.Requests.ShardInfoRequest(HttpSettings, Authent).MakeRequestAsync().Result.ToShardInfo() - : new ShardInfoRequest(HttpSettings, Authent).MakeRequestAsync().Result.ToShardInfo(); + return Authenticator.IsAnonymous + ? new WebV2.Requests + .ShardInfoRequest(HttpSettings, Authenticator).MakeRequestAsync(_connectionLimiter).Result.ToShardInfo() + : new ShardInfoRequest(HttpSettings, Authenticator).MakeRequestAsync(_connectionLimiter).Result.ToShardInfo(); } @@ -340,8 +349,8 @@ public Dictionary GetShardInfo1() private async Task GetShareListInner() { - var res = await new SharedListRequest(HttpSettings, Authent) - .MakeRequestAsync(); + var res = await new SharedListRequest(HttpSettings, Authenticator) + .MakeRequestAsync(_connectionLimiter); return res; } @@ -362,17 +371,21 @@ public void CleanTrash() public async Task CreateFolder(string path) { - //return (await new CreateFolderRequest(HttpSettings, Authent, path).MakeRequestAsync()) + //return (await new CreateFolderRequest(HttpSettings, Authenticator, path).MakeRequestAsync()) // .ToCreateFolderResult(); - return (await new CreateFolderRequest(HttpSettings, Authent, ShardManager.MetaServer.Url, path).MakeRequestAsync()) - .ToCreateFolderResult(); + var folderReqest = await new CreateFolderRequest(HttpSettings, Authenticator, ShardManager.MetaServer.Url, path) + .MakeRequestAsync(_connectionLimiter); + + return folderReqest.ToCreateFolderResult(); } public Task AddFile(string fileFullPath, IFileHash fileHash, FileSize fileSize, DateTime dateTime, ConflictResolver? conflictResolver) { throw new NotImplementedException(); } + + public async Task ActiveOperationsAsync() => await Task.FromResult(null); } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/DownloadRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/DownloadRequest.cs index 70a1e899..a7ecc709 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/DownloadRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/DownloadRequest.cs @@ -11,20 +11,20 @@ namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2.Requests { class DownloadRequest { - public DownloadRequest(File file, long instart, long inend, IAuth authent, HttpCommonSettings settings, Cached> shards) + public DownloadRequest(File file, long instart, long inend, IAuth authenticator, HttpCommonSettings settings, Cached> shards) { - Request = CreateRequest(authent, settings.Proxy, file, instart, inend, settings.UserAgent, shards); + Request = CreateRequest(authenticator, settings.Proxy, file, instart, inend, settings.UserAgent, shards); } public HttpWebRequest Request { get; } - private static HttpWebRequest CreateRequest(IAuth authent, IWebProxy proxy, File file, long instart, long inend, string userAgent, Cached> shards) + private static HttpWebRequest CreateRequest(IAuth authenticator, IWebProxy proxy, File file, long instart, long inend, string userAgent, Cached> shards) { bool isLinked = !file.PublicLinks.IsEmpty; string downloadkey = isLinked - ? authent.DownloadToken - : authent.AccessToken; + ? authenticator.DownloadToken + : authenticator.AccessToken; var shard = isLinked ? shards.Value[ShardType.WeblinkGet] @@ -41,7 +41,7 @@ private static HttpWebRequest CreateRequest(IAuth authent, IWebProxy proxy, File request.Headers.Add("Accept-Ranges", "bytes"); request.AddRange(instart, inend); request.Proxy = proxy; - request.CookieContainer = authent.Cookies; + request.CookieContainer = authenticator.Cookies; request.Method = "GET"; request.ContentType = MediaTypeNames.Application.Octet; request.Accept = "*/*"; diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/UploadRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/UploadRequest.cs index cd0d8562..b3d9c509 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/UploadRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/UploadRequest.cs @@ -6,22 +6,22 @@ namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2.Requests { class UploadRequest { - public UploadRequest(string shardUrl, File file, IAuth authent, HttpCommonSettings settings) + public UploadRequest(string shardUrl, File file, IAuth authenticator, HttpCommonSettings settings) { - Request = CreateRequest(shardUrl, authent, settings.Proxy, file, settings.UserAgent); + Request = CreateRequest(shardUrl, authenticator, settings.Proxy, file, settings.UserAgent); } public HttpWebRequest Request { get; } - private static HttpWebRequest CreateRequest(string shardUrl, IAuth authent, IWebProxy proxy, File file, string userAgent) + private static HttpWebRequest CreateRequest(string shardUrl, IAuth authenticator, IWebProxy proxy, File file, string userAgent) { - var url = new Uri($"{shardUrl}?cloud_domain=2&{authent.Login}"); + var url = new Uri($"{shardUrl}?cloud_domain=2&{authenticator.Login}"); #pragma warning disable SYSLIB0014 // Type or member is obsolete var request = (HttpWebRequest)WebRequest.Create(url.OriginalString); #pragma warning restore SYSLIB0014 // Type or member is obsolete request.Proxy = proxy; - request.CookieContainer = authent.Cookies; + request.CookieContainer = authenticator.Cookies; request.Method = "PUT"; request.ContentLength = file.OriginalSize; // + boundary.Start.LongLength + boundary.End.LongLength; request.Referer = $"{ConstSettings.CloudDomain}/home/{Uri.EscapeDataString(file.Path)}"; diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/WebAuth.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/WebAuth.cs index 8e73c3ef..8b0f97a7 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/WebAuth.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/WebAuth.cs @@ -1,6 +1,7 @@ using System; using System.Net; using System.Security.Authentication; +using System.Threading; using System.Threading.Tasks; using YaR.Clouds.Base.Repos.MailRuCloud.WebV2.Requests; using YaR.Clouds.Base.Requests; @@ -13,18 +14,20 @@ class WebAuth : IAuth { private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(WebAuth)); + private readonly SemaphoreSlim _connectionLimiter; public CookieContainer Cookies { get; } private readonly HttpCommonSettings _settings; private readonly IBasicCredentials _creds; - public WebAuth(HttpCommonSettings settings, IBasicCredentials creds, AuthCodeRequiredDelegate onAuthCodeRequired) + public WebAuth(SemaphoreSlim connectionLimiter, HttpCommonSettings settings, IBasicCredentials creds, AuthCodeRequiredDelegate onAuthCodeRequired) { + _connectionLimiter = connectionLimiter; _settings = settings; _creds = creds; Cookies = new CookieContainer(); - var logged = MakeLogin(onAuthCodeRequired).Result; + var logged = MakeLogin(connectionLimiter, onAuthCodeRequired).Result; if (!logged) throw new AuthenticationException($"Cannot log in {creds.Login}"); @@ -35,38 +38,40 @@ public WebAuth(HttpCommonSettings settings, IBasicCredentials creds, AuthCodeReq if (creds.IsAnonymous) return null; - var token = Auth().Result; + var token = Auth(connectionLimiter).Result; return token; }, _ => TimeSpan.FromSeconds(AuthTokenExpiresInSec)); - _cachedDownloadToken = new Cached(_ => new DownloadTokenRequest(_settings, this).MakeRequestAsync().Result.ToToken(), - _ => TimeSpan.FromSeconds(DownloadTokenExpiresSec)); + _cachedDownloadToken = + new Cached(_ => new DownloadTokenRequest(_settings, this) + .MakeRequestAsync(_connectionLimiter).Result.ToToken(), + _ => TimeSpan.FromSeconds(DownloadTokenExpiresSec)); } - public async Task MakeLogin(AuthCodeRequiredDelegate onAuthCodeRequired) + public async Task MakeLogin(SemaphoreSlim connectionLimiter, AuthCodeRequiredDelegate onAuthCodeRequired) { var loginResult = await new LoginRequest(_settings, this) - .MakeRequestAsync(); + .MakeRequestAsync(connectionLimiter); // 2FA if (!string.IsNullOrEmpty(loginResult.Csrf)) { string authCode = onAuthCodeRequired(_creds.Login, false); await new SecondStepAuthRequest(_settings, loginResult.Csrf, authCode) - .MakeRequestAsync(); + .MakeRequestAsync(connectionLimiter); } await new EnsureSdcCookieRequest(_settings, this) - .MakeRequestAsync(); + .MakeRequestAsync(connectionLimiter); return true; } - public async Task Auth() + public async Task Auth(SemaphoreSlim connectionLimiter) { - var req = await new AuthTokenRequest(_settings, this).MakeRequestAsync(); + var req = await new AuthTokenRequest(_settings, this).MakeRequestAsync(connectionLimiter); var res = req.ToAuthTokenResult(); return res; } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/WebV2RequestRepo.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/WebV2RequestRepo.cs index 864fbaf1..92b5b927 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/WebV2RequestRepo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/WebV2RequestRepo.cs @@ -3,6 +3,7 @@ using System.IO; using System.Linq; using System.Net; +using System.Threading; using System.Threading.Tasks; using YaR.Clouds.Base.Repos.MailRuCloud.WebV2.Requests; using YaR.Clouds.Base.Requests; @@ -16,27 +17,37 @@ class WebV2RequestRepo: MailRuBaseRepo, IRequestRepo { private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(WebV2RequestRepo)); + private readonly SemaphoreSlim _connectionLimiter; + public sealed override HttpCommonSettings HttpSettings { get; } = new() { ClientId = string.Empty }; - public WebV2RequestRepo(CloudSettings settings, IBasicCredentials creds, AuthCodeRequiredDelegate onAuthCodeRequired) - :base(creds) + public WebV2RequestRepo(CloudSettings settings, IBasicCredentials credentials, AuthCodeRequiredDelegate onAuthCodeRequired) + : base(credentials) { - HttpSettings.Proxy = settings.Proxy; + _connectionLimiter = new SemaphoreSlim(settings.MaxConnectionCount); HttpSettings.UserAgent = settings.UserAgent; - - Authent = new WebAuth(HttpSettings, creds, onAuthCodeRequired); + HttpSettings.CloudSettings = settings; + HttpSettings.Proxy = settings.Proxy; _bannedShards = new Cached>(_ => new List(), _ => TimeSpan.FromMinutes(2)); - _cachedShards = new Cached>(_ => new ShardInfoRequest(HttpSettings, Authent).MakeRequestAsync().Result.ToShardInfo(), + _cachedShards = new Cached>( + _ => new ShardInfoRequest(HttpSettings, Authenticator).MakeRequestAsync(_connectionLimiter).Result.ToShardInfo(), _ => TimeSpan.FromSeconds(ShardsExpiresInSec)); + + ServicePointManager.DefaultConnectionLimit = int.MaxValue; + + // required for Windows 7 breaking connection + ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13 | SecurityProtocolType.Tls12; + + Authenticator = new WebAuth(_connectionLimiter, HttpSettings, credentials, onAuthCodeRequired); } - + @@ -49,18 +60,18 @@ public WebV2RequestRepo(CloudSettings settings, IBasicCredentials creds, AuthCod //{ // var shard = GetShardInfo(ShardType.Upload).Result; - // var url = new Uri($"{shard.Url}?cloud_domain=2&{Authent.Login}"); + // var url = new Uri($"{shard.Url}?cloud_domain=2&{Authenticator.Login}"); - // var result = new UploadRequest(url.OriginalString, file, Authent, HttpSettings); + // var result = new UploadRequest(url.OriginalString, file, Authenticator, HttpSettings); // return result; //} - public Stream GetDownloadStream(File afile, long? start = null, long? end = null) + public Stream GetDownloadStream(File file, long? start = null, long? end = null) { CustomDisposable ResponseGenerator(long instart, long inend, File file) { - HttpWebRequest request = new DownloadRequest(file, instart, inend, Authent, HttpSettings, _cachedShards); + HttpWebRequest request = new DownloadRequest(file, instart, inend, Authenticator, HttpSettings, _cachedShards); var response = (HttpWebResponse)request.GetResponse(); return new CustomDisposable @@ -75,7 +86,7 @@ CustomDisposable ResponseGenerator(long instart, long inend, Fi }; } - var stream = new DownloadStream(ResponseGenerator, afile, start, end); + var stream = new DownloadStream(ResponseGenerator, file, start, end); return stream; } @@ -83,7 +94,7 @@ CustomDisposable ResponseGenerator(long instart, long inend, Fi //{ // string downloadkey = string.Empty; // if (shard.Type == ShardType.WeblinkGet) - // downloadkey = Authent.DownloadToken; + // downloadkey = Authenticator.DownloadToken; // string url = shard.Type == ShardType.Get // ? $"{shard.Url}{Uri.EscapeDataString(file.FullPath)}" @@ -94,7 +105,7 @@ CustomDisposable ResponseGenerator(long instart, long inend, Fi // request.Headers.Add("Accept-Ranges", "bytes"); // request.AddRange(instart, inend); // request.Proxy = HttpSettings.Proxy; - // request.CookieContainer = Authent.Cookies; + // request.CookieContainer = Authenticator.Cookies; // request.Method = "GET"; // request.ContentType = MediaTypeNames.Application.Octet; // request.Accept = "*/*"; @@ -133,7 +144,7 @@ public override async Task GetShardInfo(ShardType shardType) var banned = _bannedShards.Value; if (banned.All(bsh => bsh.Url != ishard.Url)) { - if (refreshed) Authent.ExpireDownloadToken(); + if (refreshed) Authenticator.ExpireDownloadToken(); return ishard; } _cachedShards.Expire(); @@ -149,21 +160,21 @@ public override async Task GetShardInfo(ShardType shardType) public async Task CloneItem(string fromUrl, string toPath) { - var req = await new CloneItemRequest(HttpSettings, Authent, fromUrl, toPath).MakeRequestAsync(); + var req = await new CloneItemRequest(HttpSettings, Authenticator, fromUrl, toPath).MakeRequestAsync(_connectionLimiter); var res = req.ToCloneItemResult(); return res; } public async Task Copy(string sourceFullPath, string destinationPath, ConflictResolver? conflictResolver = null) { - var req = await new CopyRequest(HttpSettings, Authent, sourceFullPath, destinationPath, conflictResolver).MakeRequestAsync(); + var req = await new CopyRequest(HttpSettings, Authenticator, sourceFullPath, destinationPath, conflictResolver).MakeRequestAsync(_connectionLimiter); var res = req.ToCopyResult(); return res; } public async Task Move(string sourceFullPath, string destinationPath, ConflictResolver? conflictResolver = null) { - var req = await new MoveRequest(HttpSettings, Authent, sourceFullPath, destinationPath).MakeRequestAsync(); + var req = await new MoveRequest(HttpSettings, Authenticator, sourceFullPath, destinationPath).MakeRequestAsync(_connectionLimiter); var res = req.ToCopyResult(); return res; } @@ -179,20 +190,21 @@ public async Task Move(string sourceFullPath, string destinationPath public async Task FolderInfo(RemotePath path, int offset = 0, int limit = int.MaxValue, int depth = 1) { - FolderInfoResult datares; + FolderInfoResult dataRes; try { - datares = await new FolderInfoRequest(HttpSettings, Authent, path, offset, limit).MakeRequestAsync(); + dataRes = await new FolderInfoRequest(HttpSettings, Authenticator, path, offset, limit) + .MakeRequestAsync(_connectionLimiter); } - catch (WebException e) when ((e.Response as HttpWebResponse)?.StatusCode == HttpStatusCode.NotFound) + catch (WebException e) when (e.Response is HttpWebResponse { StatusCode: HttpStatusCode.NotFound }) { return null; } Cloud.ItemType itemType; if (null == path.Link || path.Link.ItemType == Cloud.ItemType.Unknown) - itemType = datares.Body.Home == path.Path || - WebDavPath.PathEquals("/" + datares.Body.Weblink, path.Path) + itemType = dataRes.Body.Home == path.Path || + WebDavPath.PathEquals("/" + dataRes.Body.Weblink, path.Path) //datares.body.list.Any(fi => "/" + fi.weblink == path) ? Cloud.ItemType.Folder : Cloud.ItemType.File; @@ -201,62 +213,63 @@ public async Task FolderInfo(RemotePath path, int offset = 0, int limit var entry = itemType == Cloud.ItemType.File - ? (IEntry)datares.ToFile( + ? (IEntry)dataRes.ToFile( PublicBaseUrlDefault, home: WebDavPath.Parent(path.Path ?? string.Empty), ulink: path.Link, fileName: path.Link == null ? WebDavPath.Name(path.Path) : path.Link.OriginalName, nameReplacement: path.Link?.IsLinkedToFileSystem ?? true ? WebDavPath.Name(path.Path) : null) - : datares.ToFolder(PublicBaseUrlDefault, path.Path, path.Link); + : (IEntry)dataRes.ToFolder(PublicBaseUrlDefault, path.Path, path.Link); return entry; } public async Task ItemInfo(RemotePath path, int offset = 0, int limit = int.MaxValue) { - var req = await new ItemInfoRequest(HttpSettings, Authent, path, offset, limit).MakeRequestAsync(); + var req = await new ItemInfoRequest(HttpSettings, Authenticator, path, offset, limit) + .MakeRequestAsync(_connectionLimiter); return req; } public async Task AccountInfo() { - var req = await new AccountInfoRequest(HttpSettings, Authent).MakeRequestAsync(); + var req = await new AccountInfoRequest(HttpSettings, Authenticator).MakeRequestAsync(_connectionLimiter); var res = req.ToAccountInfo(); return res; } public async Task Publish(string fullPath) { - var req = await new PublishRequest(HttpSettings, Authent, fullPath).MakeRequestAsync(); + var req = await new PublishRequest(HttpSettings, Authenticator, fullPath).MakeRequestAsync(_connectionLimiter); var res = req.ToPublishResult(); return res; } public async Task Unpublish(Uri publicLink, string fullPath = null) { - var req = await new UnpublishRequest(HttpSettings, Authent, publicLink.OriginalString).MakeRequestAsync(); + var req = await new UnpublishRequest(HttpSettings, Authenticator, publicLink.OriginalString).MakeRequestAsync(_connectionLimiter); var res = req.ToUnpublishResult(); return res; } public async Task Remove(string fullPath) { - var req = await new RemoveRequest(HttpSettings, Authent, fullPath).MakeRequestAsync(); + var req = await new RemoveRequest(HttpSettings, Authenticator, fullPath).MakeRequestAsync(_connectionLimiter); var res = req.ToRemoveResult(); return res; } public async Task Rename(string fullPath, string newName) { - var req = await new RenameRequest(HttpSettings, Authent, fullPath, newName).MakeRequestAsync(); + var req = await new RenameRequest(HttpSettings, Authenticator, fullPath, newName).MakeRequestAsync(_connectionLimiter); var res = req.ToRenameResult(); return res; } public Dictionary GetShardInfo1() { - return new ShardInfoRequest(HttpSettings, Authent).MakeRequestAsync().Result.ToShardInfo(); + return new ShardInfoRequest(HttpSettings, Authenticator).MakeRequestAsync(_connectionLimiter).Result.ToShardInfo(); } public IEnumerable GetShareLinks(string fullPath) @@ -271,7 +284,7 @@ public void CleanTrash() public async Task CreateFolder(string path) { - return (await new CreateFolderRequest(HttpSettings, Authent, path).MakeRequestAsync()) + return (await new CreateFolderRequest(HttpSettings, Authenticator, path).MakeRequestAsync(_connectionLimiter)) .ToCreateFolderResult(); } @@ -279,10 +292,12 @@ public async Task AddFile(string fileFullPath, IFileHash fileHash { var hash = fileHash.Hash.Value; - var res = await new CreateFileRequest(HttpSettings, Authent, fileFullPath, hash, fileSize, conflictResolver) - .MakeRequestAsync(); + var res = await new CreateFileRequest(HttpSettings, Authenticator, fileFullPath, hash, fileSize, conflictResolver) + .MakeRequestAsync(_connectionLimiter); return res.ToAddFileResult(); } + + public async Task ActiveOperationsAsync() => await Task.FromResult(null); } } \ No newline at end of file diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/DtoImportYadWeb.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/DtoImportYadWeb.cs index b533fcb4..3999aaed 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/DtoImportYadWeb.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/DtoImportYadWeb.cs @@ -1,9 +1,10 @@ using System; -using System.Collections.Concurrent; using System.Collections.Generic; +using System.Collections.Immutable; using System.Linq; using YaR.Clouds.Base.Repos.YandexDisk.YadWeb.Models; using YaR.Clouds.Base.Requests.Types; +using YaR.Clouds.Common; namespace YaR.Clouds.Base.Repos.YandexDisk.YadWeb { @@ -30,34 +31,41 @@ public static AccountInfoResult ToAccountInfo(this YadResponseModel( - fi - .Where(it => it.Type == "file") - .Select(f => f.ToFile(publicBaseUrl)) - .ToGroupedFiles() - .Select(item => new KeyValuePair(item.FullPath, item)), - StringComparer.InvariantCultureIgnoreCase); - - res.Folders = new ConcurrentDictionary( - fi - .Where(it => it.Type == "dir") - .Select(f => f.ToFolder()) - .Select(item => new KeyValuePair(item.FullPath, item)), - StringComparer.InvariantCultureIgnoreCase); - - return res; + string diskPath = WebDavPath.Combine("/disk", path); + var fi = folderData.Resources; + var children = new List(); + + children.AddRange( + fi.Where(it => it.Type == "file") + .Select(f => f.ToFile(publicBaseUrl)) + .ToGroupedFiles()); + children.AddRange( + fi.Where(it => it.Type == "dir" && + // Пропуск элемента с информацией папки о родительской папке, + // этот элемент добавляется в выборки, если читается + // не всё содержимое папки, а делается только вырезка + it.Path != diskPath) + .Select(f => f.ToFolder())); + + folder.Descendants = folder.Descendants.AddRange(children); + + return folder; } public static File ToFile(this FolderInfoDataResource data, string publicBaseUrl) @@ -102,7 +110,7 @@ public static File ToFile(this YadItemInfoRequestData data, string publicBaseUrl public static Folder ToFolder(this FolderInfoDataResource resource) { - var path = resource.Path.Remove(0, "/disk".Length); + var path = resource.Path.Remove(0, "/disk".Length); var res = new Folder(path) { IsChildrenLoaded = false }; diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadDownloadRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadDownloadRequest.cs index a3f3c0d0..4807547f 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadDownloadRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadDownloadRequest.cs @@ -6,14 +6,14 @@ namespace YaR.Clouds.Base.Repos.YandexDisk.YadWeb.Requests { class YadDownloadRequest { - public YadDownloadRequest(HttpCommonSettings settings, IAuth authent, string url, long instart, long inend) + public YadDownloadRequest(HttpCommonSettings settings, IAuth authenticator, string url, long instart, long inend) { - Request = CreateRequest(authent, settings.Proxy, url, instart, inend, settings.UserAgent); + Request = CreateRequest(authenticator, settings.Proxy, url, instart, inend, settings.UserAgent); } public HttpWebRequest Request { get; } - private static HttpWebRequest CreateRequest(IAuth authent, IWebProxy proxy, string url, long instart, long inend, string userAgent) + private static HttpWebRequest CreateRequest(IAuth authenticator, IWebProxy proxy, string url, long instart, long inend, string userAgent) { #pragma warning disable SYSLIB0014 // Type or member is obsolete var request = (HttpWebRequest)WebRequest.Create(url); @@ -26,7 +26,7 @@ private static HttpWebRequest CreateRequest(IAuth authent, IWebProxy proxy, stri request.AddRange(instart, inend); request.Proxy = proxy; - request.CookieContainer = authent.Cookies; + request.CookieContainer = authenticator.Cookies; request.Method = "GET"; request.ContentType = MediaTypeNames.Application.Octet; request.Accept = "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3"; diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadUploadRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadUploadRequest.cs index d091f6e6..397607b8 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadUploadRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadUploadRequest.cs @@ -6,20 +6,20 @@ namespace YaR.Clouds.Base.Repos.YandexDisk.YadWeb.Requests { class YadUploadRequest { - public YadUploadRequest(HttpCommonSettings settings, YadWebAuth authent, string url, long size) + public YadUploadRequest(HttpCommonSettings settings, YadWebAuth authenticator, string url, long size) { - Request = CreateRequest(url, authent, settings.Proxy, size, settings.UserAgent); + Request = CreateRequest(url, authenticator, settings.Proxy, size, settings.UserAgent); } public HttpWebRequest Request { get; } - private HttpWebRequest CreateRequest(string url, YadWebAuth authent, IWebProxy proxy, long size, string userAgent) + private HttpWebRequest CreateRequest(string url, YadWebAuth authenticator, IWebProxy proxy, long size, string userAgent) { #pragma warning disable SYSLIB0014 // Type or member is obsolete var request = (HttpWebRequest)WebRequest.Create(url); #pragma warning restore SYSLIB0014 // Type or member is obsolete request.Proxy = proxy; - request.CookieContainer = authent.Cookies; + request.CookieContainer = authenticator.Cookies; request.Method = "PUT"; request.ContentLength = size; request.Referer = "https://disk.yandex.ru/client/disk"; diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebAuth.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebAuth.cs index d387b549..92f82955 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebAuth.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebAuth.cs @@ -2,6 +2,7 @@ using System.Linq; using System.Net; using System.Security.Authentication; +using System.Threading; using System.Threading.Tasks; using YaR.Clouds.Base.Repos.YandexDisk.YadWeb.Requests; using YaR.Clouds.Base.Requests; @@ -10,22 +11,22 @@ namespace YaR.Clouds.Base.Repos.YandexDisk.YadWeb { class YadWebAuth : IAuth { - public YadWebAuth(HttpCommonSettings settings, IBasicCredentials creds) + public YadWebAuth(SemaphoreSlim connectionLimiter, HttpCommonSettings settings, IBasicCredentials creds) { _settings = settings; _creds = creds; Cookies = new CookieContainer(); - var _ = MakeLogin().Result; + var _ = MakeLogin(connectionLimiter).Result; } private readonly IBasicCredentials _creds; private readonly HttpCommonSettings _settings; - public async Task MakeLogin() + public async Task MakeLogin(SemaphoreSlim connectionLimiter) { var preAuthResult = await new YadPreAuthRequest(_settings, this) - .MakeRequestAsync(); + .MakeRequestAsync(connectionLimiter); if (string.IsNullOrWhiteSpace(preAuthResult.Csrf)) throw new AuthenticationException($"{nameof(YadPreAuthRequest)} error parsing csrf"); if (string.IsNullOrWhiteSpace(preAuthResult.ProcessUUID)) @@ -34,28 +35,28 @@ public async Task MakeLogin() Uuid = preAuthResult.ProcessUUID; var loginAuth = await new YadAuthLoginRequest(_settings, this, preAuthResult.Csrf, preAuthResult.ProcessUUID) - .MakeRequestAsync(); + .MakeRequestAsync(connectionLimiter); if (loginAuth.HasError) throw new AuthenticationException($"{nameof(YadAuthLoginRequest)} error"); var passwdAuth = await new YadAuthPasswordRequest(_settings, this, preAuthResult.Csrf, loginAuth.TrackId) - .MakeRequestAsync(); + .MakeRequestAsync(connectionLimiter); if (passwdAuth.HasError) throw new AuthenticationException($"{nameof(YadAuthPasswordRequest)} errors: {passwdAuth.Errors.Aggregate((f,s) => f + "," + s)}"); var accsAuth = await new YadAuthAccountsRequest(_settings, this, preAuthResult.Csrf) - .MakeRequestAsync(); + .MakeRequestAsync(connectionLimiter); if (accsAuth.HasError) throw new AuthenticationException($"{nameof(YadAuthAccountsRequest)} error"); var askv2 = await new YadAuthAskV2Request(_settings, this, accsAuth.Csrf, passwdAuth.DefaultUid) - .MakeRequestAsync(); + .MakeRequestAsync(connectionLimiter); if (accsAuth.HasError) throw new AuthenticationException($"{nameof(YadAuthAskV2Request)} error"); var skReq = await new YadAuthDiskSkRequest(_settings, this) - .MakeRequestAsync(); + .MakeRequestAsync(connectionLimiter); if (skReq.HasError) throw new AuthenticationException($"{nameof(YadAuthDiskSkRequest)} error, response: {skReq.HtmlResponse}"); DiskSk = skReq.DiskSk; diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebRequestRepo.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebRequestRepo.cs index 4f593d3c..1f81e7ba 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebRequestRepo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebRequestRepo.cs @@ -1,9 +1,9 @@ using System; -using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Net; using System.Net.Http; +using System.Threading; using System.Threading.Tasks; using YaR.Clouds.Base.Repos.MailRuCloud; using YaR.Clouds.Base.Repos.YandexDisk.YadWeb.Models; @@ -21,6 +21,8 @@ class YadWebRequestRepo : IRequestRepo { private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(YadWebRequestRepo)); + private readonly SemaphoreSlim _connectionLimiter; + private ItemOperation _lastRemoveOperation; private const int OperationStatusCheckIntervalMs = 300; @@ -28,32 +30,31 @@ class YadWebRequestRepo : IRequestRepo private readonly IBasicCredentials _creds; - public YadWebRequestRepo(CloudSettings settings, IWebProxy proxy, IBasicCredentials creds) + public YadWebRequestRepo(CloudSettings settings, IWebProxy proxy, IBasicCredentials credentials) { - ServicePointManager.DefaultConnectionLimit = int.MaxValue; + _connectionLimiter = new SemaphoreSlim(settings.MaxConnectionCount); HttpSettings = new() { - /* - * Оригинальная версия содержит именно такую инициализацию. - * Есть вероятность, что для этого user-agent Яндекс не выпендривается - * и не начинает принудительно переводить на вход по СМС. - */ - UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36", - //UserAgent = settings.UserAgent, + UserAgent = settings.UserAgent, CloudSettings = settings, + Proxy = proxy, }; - HttpSettings.Proxy = proxy; - _creds = creds; + _creds = credentials; + + ServicePointManager.DefaultConnectionLimit = int.MaxValue; + + // required for Windows 7 breaking connection + ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13 | SecurityProtocolType.Tls12; } private async Task>> GetShareListInner() { - await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authent) + await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) .With(new YadFolderInfoPostModel("/", "/published"), out YadResponseModel folderInfo) - .MakeRequestAsync(); + .MakeRequestAsync(_connectionLimiter); var res = folderInfo.Data.Resources .Where(it => !string.IsNullOrEmpty(it.Meta?.UrlShort)) @@ -64,10 +65,10 @@ private async Task>> GetShareList return res; } - public IAuth Authent => CachedAuth.Value; + public IAuth Authenticator => CachedAuth.Value; private Cached CachedAuth => _cachedAuth ??= - new Cached(_ => new YadWebAuth(HttpSettings, _creds), _ => TimeSpan.FromHours(23)); + new Cached(_ => new YadWebAuth(_connectionLimiter, HttpSettings, _creds), _ => TimeSpan.FromHours(23)); private Cached _cachedAuth; public Cached>> CachedSharedList @@ -87,17 +88,17 @@ public Stream GetDownloadStream(File afile, long? start = null, long? end = null { CustomDisposable ResponseGenerator(long instart, long inend, File file) { - //var urldata = new YadGetResourceUrlRequest(HttpSettings, (YadWebAuth)Authent, file.FullPath) - // .MakeRequestAsync() + //var urldata = new YadGetResourceUrlRequest(HttpSettings, (YadWebAuth)Authenticator, file.FullPath) + // .MakeRequestAsync(_connectionLimiter) // .Result; - var _ = new YaDCommonRequest(HttpSettings, (YadWebAuth) Authent) + var _ = new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) .With(new YadGetResourceUrlPostModel(file.FullPath), out YadResponseModel itemInfo) - .MakeRequestAsync().Result; + .MakeRequestAsync(_connectionLimiter).Result; var url = "https:" + itemInfo.Data.File; - HttpWebRequest request = new YadDownloadRequest(HttpSettings, (YadWebAuth)Authent, url, instart, inend); + HttpWebRequest request = new YadDownloadRequest(HttpSettings, (YadWebAuth)Authenticator, url, instart, inend); var response = (HttpWebResponse)request.GetResponse(); return new CustomDisposable @@ -114,12 +115,12 @@ CustomDisposable ResponseGenerator(long instart, long inend, Fi //public HttpWebRequest UploadRequest(File file, UploadMultipartBoundary boundary) //{ // var urldata = - // new YadGetResourceUploadUrlRequest(HttpSettings, (YadWebAuth)Authent, file.FullPath, file.OriginalSize) - // .MakeRequestAsync() + // new YadGetResourceUploadUrlRequest(HttpSettings, (YadWebAuth)Authenticator, file.FullPath, file.OriginalSize) + // .MakeRequestAsync(_connectionLimiter) // .Result; // var url = urldata.Models[0].Data.UploadUrl; - // var result = new YadUploadRequest(HttpSettings, (YadWebAuth)Authent, url, file.OriginalSize); + // var result = new YadUploadRequest(HttpSettings, (YadWebAuth)Authenticator, url, file.OriginalSize); // return result; //} @@ -134,12 +135,12 @@ public ICloudHasher GetHasher() private HttpRequestMessage CreateUploadClientRequest(PushStreamContent content, File file) { var hash = (FileHashYad?) file.Hash; - var _ = new YaDCommonRequest(HttpSettings, (YadWebAuth) Authent) + var _ = new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) .With(new YadGetResourceUploadUrlPostModel(file.FullPath, file.OriginalSize, hash?.HashSha256.Value ?? string.Empty, hash?.HashMd5.Value ?? string.Empty), out YadResponseModel itemInfo) - .MakeRequestAsync().Result; + .MakeRequestAsync(_connectionLimiter).Result; var url = itemInfo.Data.UploadUrl; var request = new HttpRequestMessage @@ -188,6 +189,8 @@ public async Task FolderInfo(RemotePath path, int offset = 0, int limit bool hasRemoveOp = _lastRemoveOperation != null && WebDavPath.IsParentOrSame(path.Path, _lastRemoveOperation.Path) && (DateTime.Now - _lastRemoveOperation.DateTime).TotalMilliseconds < 1_000; + Logger.Debug($"Listing path {path.Path}"); + Retry.Do( () => { @@ -196,11 +199,11 @@ public async Task FolderInfo(RemotePath path, int offset = 0, int limit Logger.Debug("Has remove op, sleep before"); return doPreSleep; }, - () => new YaDCommonRequest(HttpSettings, (YadWebAuth) Authent) + () => new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) .With(new YadItemInfoPostModel(path.Path), out itemInfo) - .With(new YadFolderInfoPostModel(path.Path), out folderInfo) + .With(new YadFolderInfoPostModel(path.Path) { Amount = limit }, out folderInfo) .With(new YadResourceStatsPostModel(path.Path), out resourceStats) - .MakeRequestAsync() + .MakeRequestAsync(_connectionLimiter) .Result, _ => { @@ -217,79 +220,92 @@ public async Task FolderInfo(RemotePath path, int offset = 0, int limit var itdata = itemInfo?.Data; switch (itdata?.Type) { - case null: - return null; - case "file": - return itdata.ToFile(PublicBaseUrlDefault); - default: - { - var entry = folderInfo.Data.ToFolder(itemInfo.Data, resourceStats.Data, path.Path, PublicBaseUrlDefault); - return entry; - } + case null: + return null; + case "file": + return itdata.ToFile(PublicBaseUrlDefault); + default: + Folder folder = folderInfo.Data.ToFolder(itemInfo.Data, resourceStats.Data, path.Path, PublicBaseUrlDefault); + folder.IsChildrenLoaded = true; + return folder; } } private async Task MediaFolderInfo(string path) { - if (await MediaFolderRootInfo() is not Folder root) + var entry = await MediaFolderRootInfo(); + + if (entry == null || entry is not Folder root) return null; - + if (WebDavPath.PathEquals(path, YadMediaPath)) return root; - //string albumName = WebDavPath.Name(path); - //var album = root.Folders.Values.FirstOrDefault(f => f.Name == albumName); - //if (null == album) - // return null; - // Вариант без перебора предпочтительнее - if (!root.Folders.TryGetValue(path, out var album)) + string albumName = WebDavPath.Name(path); + var child = entry.Descendants.FirstOrDefault(child => child.Name == albumName); + if (child is null) return null; + var album = child; + var key = album.PublicLinks.Values.FirstOrDefault()?.Key; if (key == null) return null; - _ = new YaDCommonRequest(HttpSettings, (YadWebAuth)Authent) + _ = new YaDCommonRequest(HttpSettings, (YadWebAuth)Authenticator) .With(new YadFolderInfoPostModel(key, "/album"), out YadResponseModel folderInfo) - .MakeRequestAsync() + .MakeRequestAsync(_connectionLimiter) .Result; - var entry = folderInfo.Data.ToFolder(null, null, path, PublicBaseUrlDefault); + Folder folder = folderInfo.Data.ToFolder(null, null, path, PublicBaseUrlDefault); + folder.IsChildrenLoaded = true; - return entry; + return folder; } private async Task MediaFolderRootInfo() { - var res = new Folder(YadMediaPath); + Folder res = new Folder(YadMediaPath); - _ = await new YaDCommonRequest(HttpSettings, (YadWebAuth)Authent) + _ = await new YaDCommonRequest(HttpSettings, (YadWebAuth)Authenticator) .With(new YadGetAlbumsSlicesPostModel(), out YadResponseModel slices) .With(new YadAlbumsPostModel(), out YadResponseModel albums) - .MakeRequestAsync(); + .MakeRequestAsync(_connectionLimiter); + + var children = new List(); if (slices.Data.Albums.Camera != null) { - Folder folder = new Folder($"{YadMediaPath}/.{slices.Data.Albums.Camera.Id}") - { ServerFilesCount = (int)slices.Data.Albums.Camera.Count }; - res.Folders.TryAdd(folder.FullPath, folder); + Folder folder = + new Folder($"{YadMediaPath}/.{slices.Data.Albums.Camera.Id}") + { + ServerFilesCount = (int)slices.Data.Albums.Camera.Count + }; + children.Add(folder); } if (slices.Data.Albums.Photounlim != null) { - Folder folder = new Folder($"{YadMediaPath}/.{slices.Data.Albums.Photounlim.Id}") - { ServerFilesCount = (int)slices.Data.Albums.Photounlim.Count }; - res.Folders.TryAdd(folder.FullPath, folder); + Folder folder = + new Folder($"{YadMediaPath}/.{slices.Data.Albums.Photounlim.Id}") + { + ServerFilesCount = (int)slices.Data.Albums.Photounlim.Count + }; + children.Add(folder); } if (slices.Data.Albums.Videos != null) { - Folder folder = new Folder($"{YadMediaPath}/.{slices.Data.Albums.Videos.Id}") - { ServerFilesCount = (int)slices.Data.Albums.Videos.Count }; - res.Folders.TryAdd(folder.FullPath, folder); + Folder folder = + new Folder($"{YadMediaPath}/.{slices.Data.Albums.Videos.Id}") + { + ServerFilesCount = (int)slices.Data.Albums.Videos.Count + }; + children.Add(folder); } + res.Descendants = res.Descendants.AddRange(children); foreach (var item in albums.Data) { @@ -311,12 +327,12 @@ public Task ItemInfo(RemotePath path, int offset = 0, int limi public async Task AccountInfo() { - //var req = await new YadAccountInfoRequest(HttpSettings, (YadWebAuth)Authent).MakeRequestAsync(); + //var req = await new YadAccountInfoRequest(HttpSettings, (YadWebAuth)Authenticator).MakeRequestAsync(_connectionLimiter); - await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authent) + await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) .With(new YadAccountInfoPostModel(), out YadResponseModel itemInfo) - .MakeRequestAsync(); + .MakeRequestAsync(_connectionLimiter); var res = itemInfo.ToAccountInfo(); return res; @@ -324,13 +340,13 @@ public async Task AccountInfo() public async Task CreateFolder(string path) { - //var req = await new YadCreateFolderRequest(HttpSettings, (YadWebAuth)Authent, path) - // .MakeRequestAsync(); + //var req = await new YadCreateFolderRequest(HttpSettings, (YadWebAuth)Authenticator, path) + // .MakeRequestAsync(_connectionLimiter); - await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authent) + await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) .With(new YadCreateFolderPostModel(path), out YadResponseModel itemInfo) - .MakeRequestAsync(); + .MakeRequestAsync(_connectionLimiter); var res = itemInfo.Params.ToCreateFolderResult(); return res; @@ -341,10 +357,10 @@ public async Task AddFile(string fileFullPath, IFileHash fileHash { var hash = (FileHashYad?)fileHash; - var _ = new YaDCommonRequest(HttpSettings, (YadWebAuth) Authent) + var _ = new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) .With(new YadGetResourceUploadUrlPostModel(fileFullPath, fileSize, hash?.HashSha256.Value, hash?.HashMd5.Value), out YadResponseModel itemInfo) - .MakeRequestAsync().Result; + .MakeRequestAsync(_connectionLimiter).Result; var res = new AddFileResult { @@ -364,13 +380,13 @@ public async Task Copy(string sourceFullPath, string destinationPath { string destFullPath = WebDavPath.Combine(destinationPath, WebDavPath.Name(sourceFullPath)); - //var req = await new YadCopyRequest(HttpSettings, (YadWebAuth)Authent, sourceFullPath, destFullPath) - // .MakeRequestAsync(); + //var req = await new YadCopyRequest(HttpSettings, (YadWebAuth)Authenticator, sourceFullPath, destFullPath) + // .MakeRequestAsync(_connectionLimiter); - await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authent) + await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) .With(new YadCopyPostModel(sourceFullPath, destFullPath), out YadResponseModel itemInfo) - .MakeRequestAsync(); + .MakeRequestAsync(_connectionLimiter); var res = itemInfo.ToCopyResult(); return res; @@ -380,9 +396,9 @@ public async Task Move(string sourceFullPath, string destinationPath { string destFullPath = WebDavPath.Combine(destinationPath, WebDavPath.Name(sourceFullPath)); - await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authent) + await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) .With(new YadMovePostModel(sourceFullPath, destFullPath), out YadResponseModel itemInfo) - .MakeRequestAsync(); + .MakeRequestAsync(_connectionLimiter); var res = itemInfo.ToMoveResult(); @@ -394,12 +410,15 @@ public async Task Move(string sourceFullPath, string destinationPath private void WaitForOperation(string operationOid) { + if (string.IsNullOrWhiteSpace(operationOid)) + return; + YadResponseModel itemInfo = null; Retry.Do( () => TimeSpan.Zero, - () => new YaDCommonRequest(HttpSettings, (YadWebAuth) Authent) + () => new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) .With(new YadOperationStatusPostModel(operationOid), out itemInfo) - .MakeRequestAsync() + .MakeRequestAsync(_connectionLimiter) .Result, _ => { @@ -413,9 +432,9 @@ private void WaitForOperation(string operationOid) public async Task Publish(string fullPath) { - await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authent) + await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) .With(new YadPublishPostModel(fullPath, false), out YadResponseModel itemInfo) - .MakeRequestAsync(); + .MakeRequestAsync(_connectionLimiter); var res = itemInfo.ToPublishResult(); @@ -433,9 +452,9 @@ public async Task Unpublish(Uri publicLink, string fullPath) CachedSharedList.Value.Remove(item.Key); } - await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authent) + await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) .With(new YadPublishPostModel(fullPath, true), out YadResponseModel itemInfo) - .MakeRequestAsync(); + .MakeRequestAsync(_connectionLimiter); var res = itemInfo.ToUnpublishResult(); @@ -444,13 +463,13 @@ public async Task Unpublish(Uri publicLink, string fullPath) public async Task Remove(string fullPath) { - //var req = await new YadDeleteRequest(HttpSettings, (YadWebAuth)Authent, fullPath) - // .MakeRequestAsync(); + //var req = await new YadDeleteRequest(HttpSettings, (YadWebAuth)Authenticator, fullPath) + // .MakeRequestAsync(_connectionLimiter); - await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authent) + await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) .With(new YadDeletePostModel(fullPath), out YadResponseModel itemInfo) - .MakeRequestAsync(); + .MakeRequestAsync(_connectionLimiter); var res = itemInfo.ToRemoveResult(); @@ -465,12 +484,12 @@ public async Task Rename(string fullPath, string newName) string destPath = WebDavPath.Parent(fullPath); destPath = WebDavPath.Combine(destPath, newName); - //var req = await new YadMoveRequest(HttpSettings, (YadWebAuth)Authent, fullPath, destPath).MakeRequestAsync(); + //var req = await new YadMoveRequest(HttpSettings, (YadWebAuth)Authenticator, fullPath, destPath).MakeRequestAsync(_connectionLimiter); - await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authent) + await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) .With(new YadMovePostModel(fullPath, destPath), out YadResponseModel itemInfo) - .MakeRequestAsync(); + .MakeRequestAsync(_connectionLimiter); var res = itemInfo.ToRenameResult(); @@ -502,10 +521,10 @@ public IEnumerable GetShareLinks(string path) public async void CleanTrash() { - await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authent) + await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) .With(new YadCleanTrashPostModel(), out YadResponseModel _) - .MakeRequestAsync(); + .MakeRequestAsync(_connectionLimiter); } @@ -527,20 +546,7 @@ public string ConvertToVideoLink(Uri publicLink, SharedVideoResolution videoReso { throw new NotImplementedException("Yad not implemented ConvertToVideoLink"); } - } - - //public static class Zzz - //{ - // private static Stopwatch _sw = new Stopwatch(); - // static Zzz() - // { - // _sw.Start(); - // } - - // public static long ElapsedMs() - // { - // return _sw.ElapsedMilliseconds; - // } - //} + public async Task ActiveOperationsAsync() => await Task.FromResult(null); + } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/DtoImportYadWeb.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/DtoImportYadWeb.cs index 58738889..c65b27d9 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/DtoImportYadWeb.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/DtoImportYadWeb.cs @@ -1,6 +1,5 @@ using System; -using System.Collections.Concurrent; -using System.IO; +using System.Collections.Generic; using System.Linq; using System.Security.Authentication; using YaR.Clouds.Base.Repos.YandexDisk.YadWebV2.Models; @@ -34,72 +33,98 @@ public static AccountInfoResult ToAccountInfo(this YadResponseModel activeOps) { if (folderData is null) throw new ArgumentNullException(nameof(folderData)); + List paths = new List(); + foreach (var op in activeOps) + { + string p = YadWebRequestRepo.GetOpPath(op.Data.Target); + if (!string.IsNullOrEmpty(p)) + paths.Add(p); + p = YadWebRequestRepo.GetOpPath(op.Data.Source); + if (!string.IsNullOrEmpty(p)) + paths.Add(p); + } + if (path.StartsWith("/disk")) path = path.Remove(0, "/disk".Length); - var folder = new Folder(entryStats?.Size ?? entryData?.Meta?.Size ?? 0, path) { IsChildrenLoaded = true }; + var folder = new Folder(entryStats?.Size ?? entryData?.Meta?.Size ?? 0, path) { IsChildrenLoaded = false }; if (!string.IsNullOrEmpty(entryData?.Meta?.UrlShort)) { PublicLinkInfo item = new PublicLinkInfo("short", entryData.Meta.UrlShort); folder.PublicLinks.TryAdd(item.Uri.AbsoluteUri, item); } + if (paths.Any(x => WebDavPath.IsParentOrSame(x, folder.FullPath))) + { + // Признак операции на сервере, под которую подпадает папка + folder.Attributes |= System.IO.FileAttributes.Offline; + } string diskPath = WebDavPath.Combine("/disk", path); var fi = folderData.Resources; + var children = new List(); - folder.Files = new ConcurrentDictionary( + children.AddRange( fi.Where(it => it.Type == "file") .Select(f => f.ToFile()) - .ToGroupedFiles() - .Select(f => new System.Collections.Generic.KeyValuePair(f.FullPath, f)), - StringComparer.InvariantCultureIgnoreCase); - folder.Folders = new ConcurrentDictionary( + .ToGroupedFiles()); + children.AddRange( fi.Where(it => it.Type == "dir" && // Пропуск элемента с информацией папки о родительской папке, // этот элемент добавляется в выборки, если читается // не всё содержимое папки, а делается только вырезка it.Path != diskPath) - .Select(f => f.ToFolder()) - .Select(f => new System.Collections.Generic.KeyValuePair(f.FullPath, f)), - StringComparer.InvariantCultureIgnoreCase); - - return folder; - } - - public static Folder MergeData(this Folder folder, YadFolderInfoRequestData folderData, string path) - { - if (folderData is null) - throw new ArgumentNullException(nameof(folderData)); - if (folder is null) - throw new ArgumentNullException(nameof(folder)); + .Select(f => f.ToFolder())); - string diskPath = WebDavPath.Combine("/disk", path); - var fi = folderData.Resources; - - foreach (var item in fi.Where(it => it.Type == "file") - .Select(f => f.ToFile()) - .ToGroupedFiles()) + folder.Descendants = folder.Descendants.AddRange(children); + folder.Descendants.ForEach(entry => { - folder.Files.AddOrUpdate(item.FullPath, item, (_, _) => item); - } - foreach (var item in fi.Where(it => it.Type == "dir" && - // Пропуск элемента с информацией папки о родительской папке, - // этот элемент добавляется в выборки, если читается - // не всё содержимое папки, а делается только вырезка - it.Path != diskPath) - .Select(f => f.ToFolder())) - { - folder.Folders.AddOrUpdate(item.FullPath, item, (_, _) => item); - } + if (paths.Any(x => WebDavPath.IsParentOrSame(x, entry.FullPath))) + { + // Признак операции на сервере, под которую подпадает папка + folder.Attributes |= System.IO.FileAttributes.Offline; + } + }); + + folder.ServerFilesCount ??= folder.Descendants.Count(f => f.IsFile); + folder.ServerFoldersCount ??= folder.Descendants.Count(f => !f.IsFile); return folder; } + //public static IEntry MergeData(this Folder folder, YadFolderInfoRequestData folderData, string path) + //{ + // if (folderData is null) + // throw new ArgumentNullException(nameof(folderData)); + // if (folder is null) + // throw new ArgumentNullException(nameof(folder)); + + // string diskPath = WebDavPath.Combine("/disk", path); + // var fi = folderData.Resources; + // var children = new List(); + + // children.AddRange( + // fi.Where(it => it.Type == "file") + // .Select(f => f.ToFile()) + // .ToGroupedFiles()); + // children.AddRange( + // fi.Where(it => it.Type == "dir" && + // // Пропуск элемента с информацией папки о родительской папке, + // // этот элемент добавляется в выборки, если читается + // // не всё содержимое папки, а делается только вырезка + // it.Path != diskPath) + // .Select(f => f.ToFolder())); + + // folder.Descendants = folder.Descendants.AddRange(children); + + // return folder; + //} + public static File ToFile(this FolderInfoDataResource data) { var path = data.Path.Remove(0, "/disk".Length); @@ -142,7 +167,7 @@ public static File ToFile(this YadItemInfoRequestData data) public static Folder ToFolder(this FolderInfoDataResource resource) { - var path = resource.Path.Remove(0, "/disk".Length); + var path = resource.Path.Remove(0, "/disk".Length); var res = new Folder(path) { IsChildrenLoaded = false }; @@ -153,7 +178,7 @@ public static RenameResult ToRenameResult(this YadResponseModel> ToKvp(int index) + { + foreach (var pair in base.ToKvp(index)) + yield return pair; + } + } + + + internal class YadActiveOperationsData : YadModelDataBase + { + [JsonProperty("ycrid")] + public string Ycrid { get; set; } + + [JsonProperty("ctime")] + public long Ctime { get; set; } + + [JsonProperty("dtime")] + public long Dtime { get; set; } + + [JsonProperty("mtime")] + public long Mtime { get; set; } + + /// + /// Это идентификатор для передачи параметром в метод + /// + [JsonProperty("id")] + public string Oid { get; set; } + + /// + /// ID пользователя, запустившего операцию + /// + [JsonProperty("uid")] + public long Uid { get; set; } + + /// + /// Тип операции, например 'move' + /// + [JsonProperty("type")] + public string Type { get; set; } + + /// + /// Подтип операции, например, "disk_disk" или "disk" при upload + /// + [JsonProperty("subtype")] + public string Subtype { get; set; } + + [JsonProperty("md5")] + public string Md5 { get; set; } + + /// + /// Пример: '1', то есть число 1 + /// + [JsonProperty("state")] + public string State { get; set; } + + [JsonProperty("data")] + public YadActiveOperationsSubData Data { get; set; } + } + + internal class YadActiveOperationsSubData : YadModelDataBase + { + /// + /// Пример: "force": 0 + /// + [JsonProperty("force")] + public string Force { get; set; } + + /// + /// Пример: "callback": "" + /// + [JsonProperty("callback")] + public string Callback { get; set; } + + /// + /// Пример: "connection_id": "9380702481698343978485" + /// + [JsonProperty("connection_id")] + public string ConnectionId { get; set; } + + /// + /// Пример: "skip_check_rights": false + /// + [JsonProperty("skip_check_rights")] + public bool SkipCheckRights { get; set; } + + /// + /// Пример: "source_resource_id": "12-it's-uid-34:2401665ecf5513706a85f868f67ca316da48a17f3e6e3d9cdd0544fb0120f30c" + /// + [JsonProperty("source_resource_id")] + public string SourceResourceId { get; set; } + + /// + /// Пример: "file_id": "2401665ecf5513706a85f868f67ca316da48a17f3e6e3d9cdd0544fb0120f30c" + /// + [JsonProperty("file_id")] + public string FileId { get; set; } + + //"filedata": {}, + //"stages": {}, + + [JsonProperty("at_version")] + public long AtVersion { get; set; } + + /// + /// Пример: "target": "12-it's-uid-34:/disk/destination-folder" + /// + [JsonProperty("target")] + public string Target { get; set; } + + /// + /// Пример: "source": "12-it's-uid-34:/disk/source-folder" + /// + [JsonProperty("source")] + public string Source { get; set; } + } + + internal class YadActiveOperationsParams + { + } + + internal struct YadOperation + { + /// + /// Пользователь, запустивший операцию на сервере. + /// + public long Uid { get; set; } + + /// + /// Полный путь к файлу/папке - источнику данных операции. + /// + public string SourcePath { get; set; } + + /// + /// Полный путь к файлу/папке - месту назначения данных операции. + /// + public string TargetPath { get; set; } + + /// + /// Тип операции, например 'move'. + /// + public string Type { get; set; } + + /// + /// Идентификатор операции, который можно передавать параметром в метод . + /// + public string Oid { get; set; } + } +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/FolderInfo.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/FolderInfo.cs index 000ff79f..94b8d690 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/FolderInfo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/FolderInfo.cs @@ -114,7 +114,7 @@ class Meta public List Sizes { get; set; } [JsonProperty("mediatype", NullValueHandling = NullValueHandling.Ignore)] - public string Mediatype { get; set; } + public string MediaType { get; set; } [JsonProperty("etime", NullValueHandling = NullValueHandling.Ignore)] public long? Etime { get; set; } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/OperationStatus.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/OperationStatus.cs index 51b30ab1..0323fd6c 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/OperationStatus.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/OperationStatus.cs @@ -17,7 +17,7 @@ public override IEnumerable> ToKvp(int index) { foreach (var pair in base.ToKvp(index)) yield return pair; - + yield return new KeyValuePair($"oid.{index}", Oid); } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YadDownloadRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YadDownloadRequest.cs index 40f6409b..ee3c4783 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YadDownloadRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YadDownloadRequest.cs @@ -6,14 +6,14 @@ namespace YaR.Clouds.Base.Repos.YandexDisk.YadWebV2.Requests { class YadDownloadRequest { - public YadDownloadRequest(HttpCommonSettings settings, IAuth authent, string url, long instart, long inend) + public YadDownloadRequest(HttpCommonSettings settings, IAuth authenticator, string url, long instart, long inend) { - Request = CreateRequest(authent, settings.Proxy, url, instart, inend, settings.UserAgent); + Request = CreateRequest(authenticator, settings.Proxy, url, instart, inend, settings.UserAgent); } public HttpWebRequest Request { get; } - private static HttpWebRequest CreateRequest(IAuth authent, IWebProxy proxy, string url, long instart, long inend, string userAgent) + private static HttpWebRequest CreateRequest(IAuth Authenticator, IWebProxy proxy, string url, long instart, long inend, string userAgent) { #pragma warning disable SYSLIB0014 // Type or member is obsolete var request = (HttpWebRequest)WebRequest.Create(url); @@ -26,7 +26,7 @@ private static HttpWebRequest CreateRequest(IAuth authent, IWebProxy proxy, stri request.AddRange(instart, inend); request.Proxy = proxy; - request.CookieContainer = authent.Cookies; + request.CookieContainer = Authenticator.Cookies; request.Method = "GET"; request.ContentType = MediaTypeNames.Application.Octet; request.Accept = "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3"; diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YadUploadRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YadUploadRequest.cs index 608a6073..fa4ca5d3 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YadUploadRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YadUploadRequest.cs @@ -6,20 +6,20 @@ namespace YaR.Clouds.Base.Repos.YandexDisk.YadWebV2.Requests { class YadUploadRequest { - public YadUploadRequest(HttpCommonSettings settings, YadWebAuth authent, string url, long size) + public YadUploadRequest(HttpCommonSettings settings, YadWebAuth authenticator, string url, long size) { - Request = CreateRequest(url, authent, settings.Proxy, size, settings.UserAgent); + Request = CreateRequest(url, authenticator, settings.Proxy, size, settings.UserAgent); } public HttpWebRequest Request { get; } - private HttpWebRequest CreateRequest(string url, YadWebAuth authent, IWebProxy proxy, long size, string userAgent) + private HttpWebRequest CreateRequest(string url, YadWebAuth authenticator, IWebProxy proxy, long size, string userAgent) { #pragma warning disable SYSLIB0014 // Type or member is obsolete var request = (HttpWebRequest)WebRequest.Create(url); #pragma warning restore SYSLIB0014 // Type or member is obsolete request.Proxy = proxy; - request.CookieContainer = authent.Cookies; + request.CookieContainer = authenticator.Cookies; request.Method = "PUT"; request.ContentLength = size; request.Referer = "https://disk.yandex.ru/client/disk"; diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/YadWebAuth.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/YadWebAuth.cs index 8c2bc603..6ccc0ca7 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/YadWebAuth.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/YadWebAuth.cs @@ -4,6 +4,7 @@ using System.Net; using System.Net.Http; using System.Security.Authentication; +using System.Threading; using System.Threading.Tasks; using Newtonsoft.Json; using YaR.Clouds.Base.Repos.YandexDisk.YadWebV2.Models; @@ -16,11 +17,11 @@ class YadWebAuth : IAuth { private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(YadWebAuth)); - public YadWebAuth(HttpCommonSettings settings, IBasicCredentials creds) + public YadWebAuth(SemaphoreSlim connectionLimiter, HttpCommonSettings settings, IBasicCredentials credentials) { _settings = settings; - _creds = creds; + _creds = credentials; Cookies = new CookieContainer(); bool doRegularLogin = true; @@ -35,23 +36,23 @@ public YadWebAuth(HttpCommonSettings settings, IBasicCredentials creds) // Check file with cookies is created path = Path.Combine( settings.CloudSettings.BrowserAuthenticatorCacheDir, - creds.Login); + credentials.Login); if (System.IO.File.Exists(path)) { - var testAuthent = new YadWebAuth(_settings, _creds, path); + var testAuthenticator = new YadWebAuth(_settings, _creds, path); // Try to get user info using cached cookie - new YaDCommonRequest(_settings, testAuthent) + new YaDCommonRequest(_settings, testAuthenticator) .With(new YadAccountInfoPostModel(), out YadResponseModel itemInfo) - .MakeRequestAsync().Wait(); + .MakeRequestAsync(connectionLimiter).Wait(); var res = itemInfo.ToAccountInfo(); // Request for user info using cached cookie finished successfully - Cookies = testAuthent.Cookies; - DiskSk = testAuthent.DiskSk; - Uuid = testAuthent.Uuid; + Cookies = testAuthenticator.Cookies; + DiskSk = testAuthenticator.DiskSk; + Uuid = testAuthenticator.Uuid; doRegularLogin = false; Logger.Info($"Authentication refreshed using cached cookie"); } @@ -78,10 +79,10 @@ public YadWebAuth(HttpCommonSettings settings, IBasicCredentials creds) } } - public YadWebAuth(HttpCommonSettings settings, IBasicCredentials creds, string path) + public YadWebAuth(HttpCommonSettings settings, IBasicCredentials credentials, string path) { _settings = settings; - _creds = creds; + _creds = credentials; Cookies = new CookieContainer(); string content = System.IO.File.ReadAllText(path); diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/YadWebRequestRepo.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/YadWebRequestRepo.cs index e6e188c9..f70bcb30 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/YadWebRequestRepo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/YadWebRequestRepo.cs @@ -1,9 +1,12 @@ using System; using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; using System.IO; using System.Linq; using System.Net; using System.Net.Http; +using System.Threading; using System.Threading.Tasks; using YaR.Clouds.Base.Repos.MailRuCloud; using YaR.Clouds.Base.Repos.YandexDisk.YadWebV2.Models; @@ -21,33 +24,48 @@ class YadWebRequestRepo : IRequestRepo { private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(YadWebRequestRepo)); - private ItemOperation _lastRemoveOperation; + private readonly SemaphoreSlim _connectionLimiter; + + //private ItemOperation _lastRemoveOperation; private const int OperationStatusCheckIntervalMs = 300; private const int OperationStatusCheckRetryCount = 8; + private readonly TimeSpan OperationStatusCheckRetryTimeout = TimeSpan.FromMinutes(5); private readonly IBasicCredentials _creds; - public YadWebRequestRepo(CloudSettings settings, IWebProxy proxy, IBasicCredentials creds) + private struct ParallelInfo { - ServicePointManager.DefaultConnectionLimit = int.MaxValue; + public int Offset; + public int Amount; + public YadFolderInfoRequestData Result; + } + + public YadWebRequestRepo(CloudSettings settings, IWebProxy proxy, IBasicCredentials credentials) + { + _connectionLimiter = new SemaphoreSlim(settings.MaxConnectionCount); HttpSettings = new() { UserAgent = settings.UserAgent, CloudSettings = settings, + Proxy = proxy, }; - HttpSettings.Proxy = proxy; - _creds = creds; + _creds = credentials; + + ServicePointManager.DefaultConnectionLimit = int.MaxValue; + + // required for Windows 7 breaking connection + ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13 | SecurityProtocolType.Tls12; } private async Task>> GetShareListInner() { - await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authent) + await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) .With(new YadFolderInfoPostModel("/", "/published"), out YadResponseModel folderInfo) - .MakeRequestAsync(); + .MakeRequestAsync(_connectionLimiter); var res = folderInfo.Data.Resources .Where(it => !string.IsNullOrEmpty(it.Meta?.UrlShort)) @@ -58,10 +76,10 @@ private async Task>> GetShareList return res; } - public IAuth Authent => CachedAuth.Value; + public IAuth Authenticator => CachedAuth.Value; private Cached CachedAuth => _cachedAuth ??= - new Cached(_ => new YadWebAuth(HttpSettings, _creds), _ => TimeSpan.FromHours(23)); + new Cached(_ => new YadWebAuth(_connectionLimiter, HttpSettings, _creds), _ => TimeSpan.FromHours(23)); private Cached _cachedAuth; public Cached>> CachedSharedList @@ -81,17 +99,17 @@ public Stream GetDownloadStream(File aFile, long? start = null, long? end = null { CustomDisposable ResponseGenerator(long instart, long inend, File file) { - //var urldata = new YadGetResourceUrlRequest(HttpSettings, (YadWebAuth)Authent, file.FullPath) - // .MakeRequestAsync() + //var urldata = new YadGetResourceUrlRequest(HttpSettings, (YadWebAuth)Authenticator, file.FullPath) + // .MakeRequestAsync(_connectionLimiter) // .Result; string url = null; if (file.DownloadUrlCache == null || file.DownloadUrlCacheExpirationTime <= DateTime.Now) { - var _ = new YaDCommonRequest(HttpSettings, (YadWebAuth)Authent) + var _ = new YaDCommonRequest(HttpSettings, (YadWebAuth)Authenticator) .With(new YadGetResourceUrlPostModel(file.FullPath), out YadResponseModel itemInfo) - .MakeRequestAsync().Result; + .MakeRequestAsync(_connectionLimiter).Result; if (itemInfo == null || itemInfo.Error != null || @@ -115,7 +133,7 @@ CustomDisposable ResponseGenerator(long instart, long inend, Fi { url = file.DownloadUrlCache; } - HttpWebRequest request = new YadDownloadRequest(HttpSettings, (YadWebAuth)Authent, url, instart, inend); + HttpWebRequest request = new YadDownloadRequest(HttpSettings, (YadWebAuth)Authenticator, url, instart, inend); var response = (HttpWebResponse)request.GetResponse(); return new CustomDisposable @@ -137,14 +155,14 @@ CustomDisposable ResponseGenerator(long instart, long inend, Fi //public HttpWebRequest UploadRequest(File file, UploadMultipartBoundary boundary) //{ // var urldata = - // new YadGetResourceUploadUrlRequest(HttpSettings, (YadWebAuth)Authent, file.FullPath, file.OriginalSize) - // .MakeRequestAsync() + // new YadGetResourceUploadUrlRequest(HttpSettings, (YadWebAuth)Authenticator, file.FullPath, file.OriginalSize) + // .MakeRequestAsync(_connectionLimiter) // .Result; // var url = urldata.Models[0].Data.UploadUrl; - // var result = new YadUploadRequest(HttpSettings, (YadWebAuth)Authent, url, file.OriginalSize); - // return result; - //} + // var result = new YadUploadRequest(HttpSettings, (YadWebAuth)Authenticator, url, file.OriginalSize); + // return result; + //} public ICloudHasher GetHasher() { @@ -154,15 +172,16 @@ public ICloudHasher GetHasher() public bool SupportsAddSmallFileByHash => false; public bool SupportsDeduplicate => true; - private HttpRequestMessage CreateUploadClientRequest(PushStreamContent content, File file) + private (HttpRequestMessage, string oid) CreateUploadClientRequest(PushStreamContent content, File file) { var hash = (FileHashYad?) file.Hash; - var _ = new YaDCommonRequest(HttpSettings, (YadWebAuth) Authent) + var _ = new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) .With(new YadGetResourceUploadUrlPostModel(file.FullPath, file.OriginalSize, hash?.HashSha256.Value ?? string.Empty, hash?.HashMd5.Value ?? string.Empty), out YadResponseModel itemInfo) - .MakeRequestAsync().Result; + .MakeRequestAsync(_connectionLimiter).Result; + var url = itemInfo.Data.UploadUrl; var request = new HttpRequestMessage @@ -178,24 +197,31 @@ private HttpRequestMessage CreateUploadClientRequest(PushStreamContent content, request.Content.Headers.ContentLength = file.OriginalSize; - return request; + return (request,itemInfo?.Data?.Oid); } public async Task DoUpload(HttpClient client, PushStreamContent content, File file) { - var request = CreateUploadClientRequest(content, file); + (var request, string oid) = CreateUploadClientRequest(content, file); var responseMessage = await client.SendAsync(request); var ures = responseMessage.ToUploadPathResult(); ures.NeedToAddFile = false; - //await Task.Delay(1_000);; + //await Task.Delay(1_000); + + if (!string.IsNullOrEmpty(oid)) + WaitForOperation(oid); return ures; } private const string YadMediaPath = "/Media.wdyad"; - private const int FirstReadEntriesCount = 200; + /// + /// Сколько записей папки читать в первом обращении, до параллельного чтения. + /// Яндекс читает по 40 записей, путь тоже будет 40. + /// + private const int FirstReadEntriesCount = 40; public async Task FolderInfo(RemotePath path, int offset = 0, int limit = int.MaxValue, int depth = 1) { @@ -209,39 +235,65 @@ public async Task FolderInfo(RemotePath path, int offset = 0, int limit YadResponseModel entryInfo = null; YadResponseModel folderInfo = null; YadResponseModel entryStats = null; + YadResponseModel, YadActiveOperationsParams> operInfo = null; + + //bool hasRemoveOp = _lastRemoveOperation != null && + // WebDavPath.IsParentOrSame(path.Path, _lastRemoveOperation.Path) && + // (DateTime.Now - _lastRemoveOperation.DateTime).TotalMilliseconds < 1_000; + + int maxParallel = Math.Max(_connectionLimiter.CurrentCount - 1, 1); + // Если доступных подключений к серверу 2 или менее, то не делаем параллельного чтения + int firstReadLimit = maxParallel <= 2 ? int.MaxValue : FirstReadEntriesCount; - bool hasRemoveOp = _lastRemoveOperation != null && - WebDavPath.IsParentOrSame(path.Path, _lastRemoveOperation.Path) && - (DateTime.Now - _lastRemoveOperation.DateTime).TotalMilliseconds < 1_000; Retry.Do( () => { - var doPreSleep = hasRemoveOp ? TimeSpan.FromMilliseconds(OperationStatusCheckIntervalMs) : TimeSpan.Zero; - if (doPreSleep > TimeSpan.Zero) - Logger.Debug("Has remove op, sleep before"); - return doPreSleep; + //var doPreSleep = hasRemoveOp ? TimeSpan.FromMilliseconds(OperationStatusCheckIntervalMs) : TimeSpan.Zero; + //if (doPreSleep > TimeSpan.Zero) + // Logger.Debug("Has remove op, sleep before"); + //return doPreSleep; + return TimeSpan.Zero; }, - () => new YaDCommonRequest(HttpSettings, (YadWebAuth)Authent) + () => new YaDCommonRequest(HttpSettings, (YadWebAuth)Authenticator) .With(new YadItemInfoPostModel(path.Path), out entryInfo) - .With(new YadFolderInfoPostModel(path.Path) { WithParent = true, Amount = FirstReadEntriesCount }, out folderInfo) + .With(new YadFolderInfoPostModel(path.Path) { WithParent = true, Amount = firstReadLimit }, out folderInfo) .With(new YadResourceStatsPostModel(path.Path), out entryStats) - .MakeRequestAsync() + .With(new YadActiveOperationsPostModel(), out operInfo) + .MakeRequestAsync(_connectionLimiter) .Result, - _ => - { - bool doAgain = false; - if (hasRemoveOp && _lastRemoveOperation != null) - { - string cmpPath = WebDavPath.Combine("/disk", _lastRemoveOperation.Path); - doAgain = hasRemoveOp && - folderInfo.Data.Resources.Any(r => WebDavPath.PathEquals(r.Path, cmpPath)); - } - if (doAgain) - Logger.Debug("Remove op still not finished, let's try again"); - return doAgain; - }, - TimeSpan.FromMilliseconds(OperationStatusCheckIntervalMs), OperationStatusCheckRetryCount); - + _ => false, + //_ => + //{ + // bool doAgain = false; + // if (hasRemoveOp && _lastRemoveOperation != null) + // { + // string cmpPath = WebDavPath.Combine("/disk", _lastRemoveOperation.Path); + // doAgain = hasRemoveOp && + // folderInfo.Data.Resources.Any(r => WebDavPath.PathEquals(r.Path, cmpPath)); + // } + // if (doAgain) + // Logger.Debug("Remove op still not finished, let's try again"); + // return doAgain; + //}, + TimeSpan.FromMilliseconds(OperationStatusCheckIntervalMs), OperationStatusCheckRetryTimeout); + + + if (entryInfo?.Error != null || + (entryInfo?.Data?.Error?.Id ?? "HTTP_404") != "HTTP_404" || + entryStats?.Error != null || + (entryStats?.Data?.Error?.Id ?? "HTTP_404") != "HTTP_404" || + folderInfo?.Error != null || + (folderInfo?.Data?.Error?.Id ?? "HTTP_404") != "HTTP_404") + { + throw new IOException(string.Concat("Error reading file or directory information from server ", + entryInfo?.Error?.Message, + " ", + entryInfo?.Data?.Error?.Message, + " ", + entryStats?.Error?.Message, + " ", + entryStats?.Data?.Error?.Message)); + } var entryData = entryInfo?.Data; if (entryData?.Type is null) @@ -249,80 +301,121 @@ public async Task FolderInfo(RemotePath path, int offset = 0, int limit if (entryData.Type == "file") return entryData.ToFile(); - Folder folder = folderInfo.Data.ToFolder(entryData, entryStats.Data, path.Path); + Folder folder = folderInfo.Data.ToFolder(entryData, entryStats.Data, path.Path, operInfo?.Data); + folder.IsChildrenLoaded = limit == int.MaxValue; + int alreadyCount = folder.Descendants.Count; // Если количество полученных элементов списка меньше максимального запрошенного числа элементов, // даже с учетом, что в число элементов сверх запрошенного входит информация // о папке-контейнере (папке, чей список элементов запросили), то считаем, // что получен полный список содержимого папки и возвращает данные. - if ((folderInfo.Data.Resources?.Count ?? int.MaxValue) < FirstReadEntriesCount) + if (alreadyCount < firstReadLimit) return folder; // В противном случае делаем несколько параллельных выборок для ускорения чтения списков с сервера. int entryCount = folderInfo?.Data?.Resources.FirstOrDefault()?.Meta?.TotalEntityCount ?? 1; - int maxParallel = 1; - int parallelCount = int.MaxValue; - if (entryCount < 300 + FirstReadEntriesCount) - { - maxParallel = 1; - parallelCount = int.MaxValue; - } - else - if (entryCount >= 3000 + FirstReadEntriesCount) - { - maxParallel = 10; - // Читать данные с сервера будем немного внахлест чтобы случайно не пропустить что-либо. - // Дубликаты с одинаковыми путями самоустранятся при сливании в один словарь. - parallelCount = entryCount / 10 + 5; - } - else + + // Обновление количества доступных подключений + maxParallel = Math.Max(_connectionLimiter.CurrentCount - 1, 1); + var info = new ParallelInfo[maxParallel]; + int restAmount = entryCount - alreadyCount; + + int amountParallel = 40 * maxParallel > restAmount + ? 40 + : (restAmount + maxParallel-1) / maxParallel; + + int startIndex = alreadyCount; + int lastIndex = 0; + for (int i = 0; startIndex < entryCount && i < info.Length; i++) { - maxParallel = entryCount / 300 + 1; - parallelCount = 304; + info[i] = new ParallelInfo + { + Offset = startIndex, + Amount = amountParallel, + }; + startIndex += amountParallel; + lastIndex = i; } + if (lastIndex < info.Length - 1) + Array.Resize(ref info, lastIndex + 1); + + // Хвостовой кусок читаем без ограничения длины, на случай неправильных подсчетов + // или добавленных в параллели файлов. + info[info.Length - 1].Amount = int.MaxValue; + Retry.Do( () => { - var doPreSleep = hasRemoveOp ? TimeSpan.FromMilliseconds(OperationStatusCheckIntervalMs) : TimeSpan.Zero; - if (doPreSleep > TimeSpan.Zero) - Logger.Debug("Has remove op, sleep before"); - return doPreSleep; + //var doPreSleep = hasRemoveOp ? TimeSpan.FromMilliseconds(OperationStatusCheckIntervalMs) : TimeSpan.Zero; + //if (doPreSleep > TimeSpan.Zero) + // Logger.Debug("Has remove op, sleep before"); + //return doPreSleep; + return TimeSpan.Zero; }, () => { - Parallel.For(0, maxParallel, (int index) => + string diskPath = WebDavPath.Combine("/disk", entryData.Path); + + Parallel.For(0, info.Length, (int index) => { - YadResponseResult noReturn = new YaDCommonRequest(HttpSettings, (YadWebAuth)Authent) + YadResponseResult noReturn = new YaDCommonRequest(HttpSettings, (YadWebAuth)Authenticator) .With(new YadFolderInfoPostModel(path.Path) { - Offset = FirstReadEntriesCount + parallelCount * index - 2 /* отступ для чтения внахлест */, - Amount = index == maxParallel - 1 ? int.MaxValue : parallelCount + Offset = info[index].Offset, + Amount = info[index].Amount, + WithParent = false }, out YadResponseModel folderPartInfo) - .MakeRequestAsync() + .MakeRequestAsync(_connectionLimiter) .Result; + if (folderPartInfo?.Error != null || + folderPartInfo?.Data?.Error != null) + throw new IOException(string.Concat("Error reading file or directory information from server ", + folderPartInfo?.Error?.Message, + " ", + folderPartInfo?.Data?.Error?.Message)); + if (folderPartInfo?.Data is not null && folderPartInfo.Error is null) - folder.MergeData(folderPartInfo.Data, entryData.Path); + info[index].Result = folderPartInfo.Data; }); - YadResponseResult retValue = null; - return retValue; + + return (YadResponseResult)null; }, - _ => - { + _ => false, + //{ //TODO: Здесь полностью неправильная проверка - bool doAgain = false; - if (hasRemoveOp && _lastRemoveOperation != null) - { - string cmpPath = WebDavPath.Combine("/disk", _lastRemoveOperation.Path); - doAgain = hasRemoveOp && - folderInfo.Data.Resources.Any(r => WebDavPath.PathEquals(r.Path, cmpPath)); - } - if (doAgain) - Logger.Debug("Remove op still not finished, let's try again"); - return doAgain; - }, - TimeSpan.FromMilliseconds(OperationStatusCheckIntervalMs), OperationStatusCheckRetryCount); + //bool doAgain = false; + //if (hasRemoveOp && _lastRemoveOperation != null) + //{ + // string cmpPath = WebDavPath.Combine("/disk", _lastRemoveOperation.Path); + // doAgain = hasRemoveOp && + // folderInfo.Data.Resources.Any(r => WebDavPath.PathEquals(r.Path, cmpPath)); + //} + //if (doAgain) + // Logger.Debug("Remove op still not finished, let's try again"); + //return doAgain; + //}, + TimeSpan.FromMilliseconds(OperationStatusCheckIntervalMs), OperationStatusCheckRetryTimeout); + + string diskPath = WebDavPath.Combine("/disk", path.Path); + var children = new List(folder.Descendants); + for (int i = 0; i < info.Length; i++) + { + var fi = info[i].Result.Resources; + children.AddRange( + fi.Where(it => it.Type == "file") + .Select(f => f.ToFile()) + .ToGroupedFiles()); + children.AddRange( + fi.Where(it => it.Type == "dir" && + // Пропуск элемента с информацией папки о родительской папке, + // этот элемент добавляется в выборки, если читается + // не всё содержимое папки, а делается только вырезка + it.Path != diskPath) + .Select(f => f.ToFolder())); + } + folder.Descendants = ImmutableList.Create(children.Distinct().ToArray()); return folder; } @@ -330,65 +423,78 @@ public async Task FolderInfo(RemotePath path, int offset = 0, int limit private async Task MediaFolderInfo(string path) { - if (await MediaFolderRootInfo() is not Folder root) + var entry = await MediaFolderRootInfo(); + + if (entry == null || entry is not Folder root) return null; if (WebDavPath.PathEquals(path, YadMediaPath)) return root; - //string albumName = WebDavPath.Name(path); - //var album = root.Folders.Values.FirstOrDefault(f => f.Name == albumName); - //if (null == album) - // return null; - // Вариант без перебора предпочтительнее - if (!root.Folders.TryGetValue(path, out var album)) + string albumName = WebDavPath.Name(path); + var child = entry.Descendants.FirstOrDefault(child => child.Name == albumName); + if (child is null) return null; + var album = child; + var key = album.PublicLinks.Values.FirstOrDefault()?.Key; if (key == null) return null; - _ = new YaDCommonRequest(HttpSettings, (YadWebAuth) Authent) + _ = new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) .With(new YadFolderInfoPostModel(key, "/album"), out YadResponseModel folderInfo) - .MakeRequestAsync() + .MakeRequestAsync(_connectionLimiter) .Result; - Folder folder = folderInfo.Data.ToFolder(null, null, path); - folder.MergeData(folderInfo.Data, path); + Folder folder = folderInfo.Data.ToFolder(null, null, path, null); + folder.IsChildrenLoaded = true; return folder; } private async Task MediaFolderRootInfo() { - var res = new Folder(YadMediaPath); + Folder res = new Folder(YadMediaPath); - _ = await new YaDCommonRequest(HttpSettings, (YadWebAuth)Authent) + _ = await new YaDCommonRequest(HttpSettings, (YadWebAuth)Authenticator) .With(new YadGetAlbumsSlicesPostModel(), out YadResponseModel slices) .With(new YadAlbumsPostModel(), out YadResponseModel albums) - .MakeRequestAsync(); + .MakeRequestAsync(_connectionLimiter); + + var children = new List(); if (slices.Data.Albums.Camera != null) { - Folder folder = new Folder($"{YadMediaPath}/.{slices.Data.Albums.Camera.Id}") - { ServerFilesCount = (int)slices.Data.Albums.Camera.Count }; - res.Folders.TryAdd(folder.FullPath, folder); + Folder folder = + new Folder($"{YadMediaPath}/.{slices.Data.Albums.Camera.Id}") + { + ServerFilesCount = (int)slices.Data.Albums.Camera.Count + }; + children.Add(folder); } if (slices.Data.Albums.Photounlim != null) { - Folder folder = new Folder($"{YadMediaPath}/.{slices.Data.Albums.Photounlim.Id}") - { ServerFilesCount = (int)slices.Data.Albums.Photounlim.Count }; - res.Folders.TryAdd(folder.FullPath, folder); + Folder folder = + new Folder($"{YadMediaPath}/.{slices.Data.Albums.Photounlim.Id}") + { + ServerFilesCount = (int)slices.Data.Albums.Photounlim.Count + }; + children.Add(folder); } if (slices.Data.Albums.Videos != null) { - Folder folder = new Folder($"{YadMediaPath}/.{slices.Data.Albums.Videos.Id}") - { ServerFilesCount = (int)slices.Data.Albums.Videos.Count }; - res.Folders.TryAdd(folder.FullPath, folder); + Folder folder = + new Folder($"{YadMediaPath}/.{slices.Data.Albums.Videos.Id}") + { + ServerFilesCount = (int)slices.Data.Albums.Videos.Count + }; + children.Add(folder); } + res.Descendants = res.Descendants.AddRange(children); foreach (var item in albums.Data) { @@ -410,12 +516,12 @@ public Task ItemInfo(RemotePath path, int offset = 0, int limi public async Task AccountInfo() { - //var req = await new YadAccountInfoRequest(HttpSettings, (YadWebAuth)Authent).MakeRequestAsync(); + //var req = await new YadAccountInfoRequest(HttpSettings, (YadWebAuth)Authenticator).MakeRequestAsync(_connectionLimiter); - await new YaDCommonRequest(HttpSettings, (YadWebAuth)Authent) + await new YaDCommonRequest(HttpSettings, (YadWebAuth)Authenticator) .With(new YadAccountInfoPostModel(), out YadResponseModel itemInfo) - .MakeRequestAsync(); + .MakeRequestAsync(_connectionLimiter); var res = itemInfo.ToAccountInfo(); return res; @@ -423,13 +529,13 @@ public async Task AccountInfo() public async Task CreateFolder(string path) { - //var req = await new YadCreateFolderRequest(HttpSettings, (YadWebAuth)Authent, path) - // .MakeRequestAsync(); + //var req = await new YadCreateFolderRequest(HttpSettings, (YadWebAuth)Authenticator, path) + // .MakeRequestAsync(_connectionLimiter); - await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authent) + await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) .With(new YadCreateFolderPostModel(path), out YadResponseModel itemInfo) - .MakeRequestAsync(); + .MakeRequestAsync(_connectionLimiter); var res = itemInfo.Params.ToCreateFolderResult(); return res; @@ -440,10 +546,10 @@ public async Task AddFile(string fileFullPath, IFileHash fileHash { var hash = (FileHashYad?)fileHash; - var _ = new YaDCommonRequest(HttpSettings, (YadWebAuth) Authent) + var _ = new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) .With(new YadGetResourceUploadUrlPostModel(fileFullPath, fileSize, hash?.HashSha256.Value, hash?.HashMd5.Value), out YadResponseModel itemInfo) - .MakeRequestAsync().Result; + .MakeRequestAsync(_connectionLimiter).Result; var res = new AddFileResult { @@ -463,15 +569,19 @@ public async Task Copy(string sourceFullPath, string destinationPath { string destFullPath = WebDavPath.Combine(destinationPath, WebDavPath.Name(sourceFullPath)); - //var req = await new YadCopyRequest(HttpSettings, (YadWebAuth)Authent, sourceFullPath, destFullPath) - // .MakeRequestAsync(); + //var req = await new YadCopyRequest(HttpSettings, (YadWebAuth)Authenticator, sourceFullPath, destFullPath) + // .MakeRequestAsync(_connectionLimiter); - await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authent) + await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) .With(new YadCopyPostModel(sourceFullPath, destFullPath), out YadResponseModel itemInfo) - .MakeRequestAsync(); + .MakeRequestAsync(_connectionLimiter); var res = itemInfo.ToCopyResult(); + + if (res.IsSuccess) + WaitForOperation(itemInfo.Data.Oid); + return res; } @@ -479,42 +589,108 @@ public async Task Move(string sourceFullPath, string destinationPath { string destFullPath = WebDavPath.Combine(destinationPath, WebDavPath.Name(sourceFullPath)); - await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authent) + await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) .With(new YadMovePostModel(sourceFullPath, destFullPath), out YadResponseModel itemInfo) - .MakeRequestAsync(); + .MakeRequestAsync(_connectionLimiter); var res = itemInfo.ToMoveResult(); - - WaitForOperation(itemInfo.Data.Oid); + if( res.IsSuccess) + WaitForOperation(itemInfo.Data.Oid); return res; } + public async Task ActiveOperationsAsync() + { + YadResponseModel, YadActiveOperationsParams> itemInfo = null; + + _ = await new YaDCommonRequest(HttpSettings, (YadWebAuth)Authenticator) + .With(new YadActiveOperationsPostModel(), out itemInfo) + .With(new YadAccountInfoPostModel(), + out YadResponseModel accountInfo) + .MakeRequestAsync(_connectionLimiter); + + var list = itemInfo?.Data? + .Select(x => new ActiveOperation + { + Oid = x.Oid, + Uid = x.Uid, + Type = x.Type, + SourcePath = GetOpPath(x.Data.Source), + TargetPath = GetOpPath(x.Data.Target), + })?.ToList(); + + var info = new CheckUpInfo + { + AccountInfo = new CheckUpInfo.CheckInfo + { + FilesCount = accountInfo?.Data?.FilesCount ?? 0, + Free = accountInfo?.Data?.Free ?? 0, + Trash = accountInfo?.Data?.Trash ?? 0, + }, + ActiveOperations = list, + }; + + return info; + } + + public static string GetOpPath(string path) + { + if (string.IsNullOrEmpty(path)) + return null; + int colon = path.IndexOf(':'); + return WebDavPath.Clean(path.Substring(1 + colon)).Remove(0, "/disk".Length); + } + private void WaitForOperation(string operationOid) { + if (string.IsNullOrWhiteSpace(operationOid)) + return; + + var flagWatch = Stopwatch.StartNew(); + YadResponseModel itemInfo = null; Retry.Do( () => TimeSpan.Zero, - () => new YaDCommonRequest(HttpSettings, (YadWebAuth) Authent) + () => new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) .With(new YadOperationStatusPostModel(operationOid), out itemInfo) - .MakeRequestAsync() + .MakeRequestAsync(_connectionLimiter) .Result, _ => { - var doAgain = null == itemInfo.Data.Error && itemInfo.Data.State != "COMPLETED"; - //if (doAgain) - // Logger.Debug("Move op still not finished, let's try again"); + /* + * Яндекс повторяет проверку при переносе папки каждый 9 секунд. + * Когда операция завершилась: "status": "DONE", "state": "COMPLETED", "type": "move" + * "params": { + * "source": "12-it's_uid-34:/disk/source-folder", + * "target": "12-it's_uid-34:/disk/destination-folder" + * }, + * Когда операция еще в процессе: "status": "EXECUTING", "state": "EXECUTING", "type": "move" + * "params": { + * "source": "12-it's_uid-34:/disk/source-folder", + * "target": "12-it's_uid-34:/disk/destination-folder" + * }, + */ + var doAgain = itemInfo.Data.Error is null && itemInfo.Data.State != "COMPLETED"; + if (doAgain) + { + if (flagWatch.Elapsed > TimeSpan.FromSeconds(30)) + { + Logger.Debug("Operation is still in progress, let's wait..."); + flagWatch.Restart(); + } + } return doAgain; }, - TimeSpan.FromMilliseconds(OperationStatusCheckIntervalMs), OperationStatusCheckRetryCount); + TimeSpan.FromMilliseconds(OperationStatusCheckIntervalMs), OperationStatusCheckRetryTimeout); } public async Task Publish(string fullPath) { - await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authent) + await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) .With(new YadPublishPostModel(fullPath, false), out YadResponseModel itemInfo) - .MakeRequestAsync(); + .MakeRequestAsync(_connectionLimiter); var res = itemInfo.ToPublishResult(); @@ -532,9 +708,9 @@ public async Task Unpublish(Uri publicLink, string fullPath) CachedSharedList.Value.Remove(item.Key); } - await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authent) + await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) .With(new YadPublishPostModel(fullPath, true), out YadResponseModel itemInfo) - .MakeRequestAsync(); + .MakeRequestAsync(_connectionLimiter); var res = itemInfo.ToUnpublishResult(); @@ -543,18 +719,21 @@ public async Task Unpublish(Uri publicLink, string fullPath) public async Task Remove(string fullPath) { - //var req = await new YadDeleteRequest(HttpSettings, (YadWebAuth)Authent, fullPath) - // .MakeRequestAsync(); + //var req = await new YadDeleteRequest(HttpSettings, (YadWebAuth)Authenticator, fullPath) + // .MakeRequestAsync(_connectionLimiter); - await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authent) + await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) .With(new YadDeletePostModel(fullPath), out YadResponseModel itemInfo) - .MakeRequestAsync(); + .MakeRequestAsync(_connectionLimiter); var res = itemInfo.ToRemoveResult(); - + if (res.IsSuccess) - _lastRemoveOperation = res.ToItemOperation(); + WaitForOperation(itemInfo.Data.Oid); + + //if (res.IsSuccess) + // _lastRemoveOperation = res.ToItemOperation(); return res; } @@ -564,12 +743,12 @@ public async Task Rename(string fullPath, string newName) string destPath = WebDavPath.Parent(fullPath); destPath = WebDavPath.Combine(destPath, newName); - //var req = await new YadMoveRequest(HttpSettings, (YadWebAuth)Authent, fullPath, destPath).MakeRequestAsync(); + //var req = await new YadMoveRequest(HttpSettings, (YadWebAuth)Authenticator, fullPath, destPath).MakeRequestAsync(_connectionLimiter); - await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authent) + await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) .With(new YadMovePostModel(fullPath, destPath), out YadResponseModel itemInfo) - .MakeRequestAsync(); + .MakeRequestAsync(_connectionLimiter); var res = itemInfo.ToRenameResult(); @@ -579,7 +758,6 @@ public async Task Rename(string fullPath, string newName) //if (res.IsSuccess) // _lastRemoveOperation = res.ToItemOperation(); - return res; } @@ -601,10 +779,10 @@ public IEnumerable GetShareLinks(string path) public async void CleanTrash() { - await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authent) + await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) .With(new YadCleanTrashPostModel(), out YadResponseModel _) - .MakeRequestAsync(); + .MakeRequestAsync(_connectionLimiter); } @@ -627,19 +805,4 @@ public string ConvertToVideoLink(Uri publicLink, SharedVideoResolution videoReso throw new NotImplementedException("Yad not implemented ConvertToVideoLink"); } } - - //public static class Zzz - //{ - // private static Stopwatch _sw = new Stopwatch(); - - // static Zzz() - // { - // _sw.Start(); - // } - - // public static long ElapsedMs() - // { - // return _sw.ElapsedMilliseconds; - // } - //} } diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs index e7ecabee..fa7ec082 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs @@ -74,8 +74,20 @@ protected virtual byte[] CreateHttpContent() private const int MaxRetryCount = 10; - public virtual async Task MakeRequestAsync() + public virtual async Task MakeRequestAsync(SemaphoreSlim serverMaxConnectionLimiter) { + /* + * С одной стороны есть желание максимально ускорить работу за счет параллельных + * вычислений и обращений к серверу. + * А с другой стороны, сервер может начать ругаться на слишком большое количество + * одновременных обращений. + * Можно экспериментальным путем подобрать близкое к максимальному количеству + * число допустимых одновременных обращений, а затем указать данное число + * в параметре maxconnections. На основе этого параметра создается ограничивающий + * максимальное количество обращений к серверу семафор, + * который передается в параметре метода. + */ + /* * По всей видимости, при нескольких последовательных обращениях к серверу * инфраструктура .NET повторно использует подключение после его закрытие по Close. @@ -92,169 +104,177 @@ public virtual async Task MakeRequestAsync() * что мы тут и сделаем. */ - Stopwatch totalWatch = Stopwatch.StartNew(); - int retry = MaxRetryCount; - while (true) + await serverMaxConnectionLimiter.WaitAsync(); + try { - retry--; - bool isRetryState = false; - Stopwatch watch = Stopwatch.StartNew(); - HttpWebRequest httpRequest = null; - - try + Stopwatch totalWatch = Stopwatch.StartNew(); + int retry = MaxRetryCount; + while (true) { - httpRequest = CreateRequest(); + retry--; + bool isRetryState = false; + Stopwatch watch = Stopwatch.StartNew(); + HttpWebRequest httpRequest = null; - var requestContent = CreateHttpContent(); - if (requestContent != null) + try { - httpRequest.Method = "POST"; - using Stream requestStream = await httpRequest.GetRequestStreamAsync().ConfigureAwait(false); + httpRequest = CreateRequest(); - /* - * The debug add the following to a watch list: - * System.Text.Encoding.UTF8.GetString(requestContent) - */ + var requestContent = CreateHttpContent(); + if (requestContent != null) + { + httpRequest.Method = "POST"; + using Stream requestStream = await httpRequest.GetRequestStreamAsync().ConfigureAwait(false); + + /* + * The debug add the following to a watch list: + * System.Text.Encoding.UTF8.GetString(requestContent) + */ #if NET48 await requestStream.WriteAsync(requestContent, 0, requestContent.Length).ConfigureAwait(false); #else - await requestStream.WriteAsync(requestContent).ConfigureAwait(false); + await requestStream.WriteAsync(requestContent).ConfigureAwait(false); #endif - await requestStream.FlushAsync().ConfigureAwait(false); - requestStream.Close(); - } + await requestStream.FlushAsync().ConfigureAwait(false); + requestStream.Close(); + } - /* - * Здесь в методе GetResponseAsync() иногда происходит исключение - * throw new HttpIOException(HttpRequestError.ResponseEnded, SR.net_http_invalid_response_premature_eof); - * Мы его отлавливаем и повторяем обращение к серверу. - */ - using var response = (HttpWebResponse)await httpRequest.GetResponseAsync().ConfigureAwait(false); + /* + * Здесь в методе GetResponseAsync() иногда происходит исключение + * throw new HttpIOException(HttpRequestError.ResponseEnded, SR.net_http_invalid_response_premature_eof); + * Мы его отлавливаем и повторяем обращение к серверу. + */ + using var response = (HttpWebResponse)await httpRequest.GetResponseAsync().ConfigureAwait(false); - if ((int)response.StatusCode >= 500) - { - throw new RequestException("Server fault") + if ((int)response.StatusCode >= 500) { - StatusCode = response.StatusCode - }; - } + throw new RequestException("Server fault") + { + StatusCode = response.StatusCode + }; + } - RequestResponse result; - using (var responseStream = response.GetResponseStream()) - { - result = DeserializeMessage(response.Headers, Transport(responseStream)); - responseStream.Close(); - } + RequestResponse result; + using (var responseStream = response.GetResponseStream()) + { + result = DeserializeMessage(response.Headers, Transport(responseStream)); + responseStream.Close(); + } - if (!result.Ok || response.StatusCode != HttpStatusCode.OK) - { - var exceptionMessage = - $"Request failed (status code {(int)response.StatusCode}): {result.Description}"; - throw new RequestException(exceptionMessage) + if (!result.Ok || response.StatusCode != HttpStatusCode.OK) { - StatusCode = response.StatusCode, - ResponseBody = string.Empty, - Description = result.Description, - ErrorCode = result.ErrorCode - }; - } + var exceptionMessage = + $"Request failed (status code {(int)response.StatusCode}): {result.Description}"; + throw new RequestException(exceptionMessage) + { + StatusCode = response.StatusCode, + ResponseBody = string.Empty, + Description = result.Description, + ErrorCode = result.ErrorCode + }; + } - response.Close(); + response.Close(); - return result.Result; - } - catch (WebException iex2) when (iex2?.InnerException is System.Net.Http.HttpRequestException iex1 && - iex1?.InnerException is IOException iex) - { - /* - * Здесь мы ловим ошибку - * throw new HttpIOException(HttpRequestError.ResponseEnded, SR.net_http_invalid_response_premature_eof), - * которая здесь выглядит следующим образом: - * System.AggregateException: One or more errors occurred. (An error occurred while sending the request.) - * ---> System.Net.WebException: An error occurred while sending the request. - * ---> System.Net.Http.HttpRequestException: An error occurred while sending the request. - * ---> System.IO.IOException: The response ended prematurely. - * at System.Net.Http.HttpConnection.SendAsyncCore(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken) - * --- End of inner exception stack trace --- - */ - if (retry <= 0) + return result.Result; + } + catch (WebException iex2) when (iex2?.InnerException is System.Net.Http.HttpRequestException iex1 && + iex1?.InnerException is IOException iex) { - string msg = "The response ended prematurely, retry count completed"; + /* + * Здесь мы ловим ошибку + * throw new HttpIOException(HttpRequestError.ResponseEnded, SR.net_http_invalid_response_premature_eof), + * которая здесь выглядит следующим образом: + * System.AggregateException: One or more errors occurred. (An error occurred while sending the request.) + * ---> System.Net.WebException: An error occurred while sending the request. + * ---> System.Net.Http.HttpRequestException: An error occurred while sending the request. + * ---> System.IO.IOException: The response ended prematurely. + * at System.Net.Http.HttpConnection.SendAsyncCore(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken) + * --- End of inner exception stack trace --- + */ + if (retry <= 0) + { + string msg = "The response ended prematurely, retry count completed"; #if DEBUG - Logger.Warn(msg); + Logger.Warn(msg); #else Logger.Debug(msg); #endif - throw; - } - else - { - isRetryState = true; - string msg = "The response ended prematurely, retrying..."; + throw; + } + else + { + isRetryState = true; + string msg = "The response ended prematurely, retrying..."; #if DEBUG - Logger.Warn(msg); + Logger.Warn(msg); #else Logger.Debug(msg); #endif + } } - } - catch (WebException iex2) when (iex2?.InnerException is System.Net.Http.HttpRequestException iex1 && - iex1?.InnerException is SocketException iex) - { - /* - * Здесь мы ловим ошибку - * SocketException: Попытка установить соединение была безуспешной, - * т.к. от другого компьютера за требуемое время не получен нужный отклик, - * или было разорвано уже установленное соединение из-за неверного отклика - * уже подключенного компьютера. - * - * Возможно превышено максимальное количество подключений к серверу. - * Просто повторяем запрос после небольшого ожидания. - */ - if (retry <= 0) + catch (WebException iex2) when (iex2?.InnerException is System.Net.Http.HttpRequestException iex1 && + iex1?.InnerException is SocketException iex) { - string msg = "Can not connect to server. Increase timeout in response-timeout-sec parameter."; + /* + * Здесь мы ловим ошибку + * SocketException: Попытка установить соединение была безуспешной, + * т.к. от другого компьютера за требуемое время не получен нужный отклик, + * или было разорвано уже установленное соединение из-за неверного отклика + * уже подключенного компьютера. + * + * Возможно превышено максимальное количество подключений к серверу. + * Просто повторяем запрос после небольшого ожидания. + */ + if (retry <= 0) + { + string msg = "Can not connect to server. Increase timeout in response-timeout-sec parameter."; #if DEBUG - Logger.Warn(msg); + Logger.Warn(msg); #else Logger.Debug(msg); #endif - throw; - } - else - { - isRetryState = true; - string msg = watch.ElapsedMilliseconds < 1000 - ? "Connection to server terminated immediately, retrying after couple of seconds..." - : $"Connection lost after {watch.ElapsedMilliseconds/1000} seconds, retrying after couple of seconds..."; + throw; + } + else + { + isRetryState = true; + string msg = watch.ElapsedMilliseconds < 1000 + ? "Connection to server terminated immediately, retrying after couple of seconds..." + : $"Connection lost after {watch.ElapsedMilliseconds / 1000} seconds, retrying after couple of seconds..."; #if DEBUG - Logger.Warn(msg); + Logger.Warn(msg); #else Logger.Debug(msg); #endif - Thread.Sleep(TimeSpan.FromSeconds(2)); + Thread.Sleep(TimeSpan.FromSeconds(2)); + } } - } - // ReSharper disable once RedundantCatchClause + // ReSharper disable once RedundantCatchClause #pragma warning disable 168 - catch (Exception ex) + catch (Exception ex) #pragma warning restore 168 - { - throw; - } - finally - { - watch.Stop(); - string totalText = null; - if (!isRetryState && retry < MaxRetryCount - 1) { - totalWatch.Stop(); - totalText = $"({totalWatch.Elapsed.Milliseconds} ms of {MaxRetryCount - retry} retry laps)"; + throw; + } + finally + { + watch.Stop(); + string totalText = null; + if (!isRetryState && retry < MaxRetryCount - 1) + { + totalWatch.Stop(); + totalText = $"({totalWatch.Elapsed.Milliseconds} ms of {MaxRetryCount - retry} retry laps)"; + } + Logger.Debug($"HTTP:{httpRequest.Method}:{httpRequest.RequestUri.AbsoluteUri} " + + $"({watch.Elapsed.Milliseconds} ms){(isRetryState ? ", retrying" : totalText)}"); } - Logger.Debug($"HTTP:{httpRequest.Method}:{httpRequest.RequestUri.AbsoluteUri} " + - $"({watch.Elapsed.Milliseconds} ms){(isRetryState ? ", retrying" : totalText)}"); } } + finally + { + serverMaxConnectionLimiter.Release(); + } } protected abstract TConvert Transport(Stream stream); diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/ActiveOperation.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/ActiveOperation.cs new file mode 100644 index 00000000..27b3fbc3 --- /dev/null +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/ActiveOperation.cs @@ -0,0 +1,29 @@ +namespace YaR.Clouds.Base.Requests.Types; + +public class ActiveOperation +{ + /// + /// Пользователь, запустивший операцию на сервере. + /// + public long Uid { get; set; } + + /// + /// Полный путь к файлу/папке - источнику данных операции. + /// + public string SourcePath { get; set; } + + /// + /// Полный путь к файлу/папке - месту назначения данных операции. + /// + public string TargetPath { get; set; } + + /// + /// Тип операции, например 'move'. + /// + public string Type { get; set; } + + /// + /// Идентификатор операции, который можно передавать параметром в метод . + /// + public string Oid { get; set; } +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/CheckUpInfo.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/CheckUpInfo.cs new file mode 100644 index 00000000..1204c82f --- /dev/null +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/CheckUpInfo.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; + +namespace YaR.Clouds.Base.Requests.Types; + +public class CheckUpInfo +{ + public struct CheckInfo + { + public long FilesCount; + public long Free; + public long Trash; + }; + + /// + /// Набор информации для проверки изменений на диске, минуя данный сервис. + /// + public CheckInfo AccountInfo { get; set; } + + /// + /// Список активных операций на сервере. + /// + public List ActiveOperations { get; set; } +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Streams/HttpClientFabric.cs b/MailRuCloud/MailRuCloudApi/Base/Streams/HttpClientFabric.cs index d28da6a5..e03c7156 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Streams/HttpClientFabric.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Streams/HttpClientFabric.cs @@ -17,7 +17,7 @@ public HttpClient this[Account account] { UseProxy = true, Proxy = account.RequestRepo.HttpSettings.Proxy, - CookieContainer = account.RequestRepo.Authent.Cookies, + CookieContainer = account.RequestRepo.Authenticator.Cookies, UseCookies = true, AllowAutoRedirect = true, MaxConnectionsPerServer = int.MaxValue diff --git a/MailRuCloud/MailRuCloudApi/Base/WebDavPath.cs b/MailRuCloud/MailRuCloudApi/Base/WebDavPath.cs index d663b374..1a9a9955 100644 --- a/MailRuCloud/MailRuCloudApi/Base/WebDavPath.cs +++ b/MailRuCloud/MailRuCloudApi/Base/WebDavPath.cs @@ -29,6 +29,8 @@ public static string Combine(string a, string b) right.Remove(right.Length - 1, 1); return right.ToString(); } + if (a == Root) + return right.ToString(); StringBuilder left = CleanSb(a, false); @@ -116,11 +118,60 @@ public static bool IsParentOrSame(string parent, string child) return IsParent(parent, child, true); } - public static bool IsParent(string parent, string child, bool selfTrue = false) + public static bool IsParent(string parent, string child, bool selfTrue = false, bool oneLevelDistanceOnly = false) { - parent = Clean(parent, true); - child = Clean(child, true); - return child.StartsWith(parent) && (selfTrue || parent.Length != child.Length); + if (child.Equals(parent, StringComparison.InvariantCultureIgnoreCase)) + return selfTrue; + + if (parent.Length > child.Length) + return false; + + if (parent == Root) + { + // Если родитель=root, то любой путь будет потомком, если не брать только ближайший уровень + if (!oneLevelDistanceOnly) + return true; + // Если уровень только ближайший нужен, + // то после части, совпадающей с родителем, не должно быть /, + // исключение - / в виде последнего символа + int slash = child.IndexOf(Separator, parent.Length, StringComparison.Ordinal); + return slash < 0 || slash == child.Length - 1; + } + + // Части потомка и родителя должны совпадать, и потомок должен быть длиннее + if (parent.Length < child.Length && + child.StartsWith(parent, StringComparison.InvariantCultureIgnoreCase)) + { + int start; + // Если у родителя последний символ был /, то у потомка следующий символ не должен быть / + if (child[parent.Length - 1] == '/' && child[parent.Length] != '/') + start = parent.Length; + else + // Если у родителя последний символ был не /, то у потомка следующий символ должен быть / + if (child[parent.Length - 1] != '/' && child[parent.Length] == '/') + start = parent.Length + 1; + else + { + // Тут какая-то ерунду с символами, это не родитель и потоком + return false; + } + int end = child[child.Length - 1] == '/' ? child.Length - 1 : child.Length; + + // Исключая возможный / в конце и исключая часть, совпадающую с родителем, + // исключая / после родителя, от start, включая, до end, исключая (как для substring), + // будет текст пути потомка. + if (start >= end) + return false; + + // Если уровень потомка не важен, то уже true. + if (!oneLevelDistanceOnly) + return true; + + // Если уровень потомка важен, то в интервале еще и символ / должен находиться. + int slash = child.IndexOf(Separator, start, end - start, StringComparison.Ordinal); + return slash < 0; + } + return false; } public static WebDavPathParts Parts(string path) diff --git a/MailRuCloud/MailRuCloudApi/Cloud.cs b/MailRuCloud/MailRuCloudApi/Cloud.cs index 78224b11..ed8e4bbc 100644 --- a/MailRuCloud/MailRuCloudApi/Cloud.cs +++ b/MailRuCloud/MailRuCloudApi/Cloud.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.IO; using System.Linq; @@ -27,7 +28,7 @@ namespace YaR.Clouds /// public class Cloud : IDisposable { - //private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(Account)); + private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(Cloud)); public LinkManager LinkManager { get; } @@ -45,10 +46,15 @@ public class Cloud : IDisposable public Account Account { get; } + ///// + ///// Caching files for multiple small reads + ///// + //private readonly ItemCache _itemCache; + /// - /// Caching files for multiple small reads + /// Кеш облачной файловой системы. /// - private readonly ItemCache _itemCache; + private readonly EntryCache _entryCache; /// /// Initializes a new instance of the class. @@ -62,12 +68,14 @@ public Cloud(CloudSettings settings, Credentials credentials) throw new AuthenticationException("Auth token hasn't been retrieved."); } - //TODO: wow very dummy linking, refact cache realization globally! - _itemCache = new ItemCache(TimeSpan.FromSeconds(settings.CacheListingSec)); - //{ - // Полагаемся на стандартно заданное время очистки - // CleanUpPeriod = TimeSpan.FromMinutes(5) - //}; + _entryCache = new EntryCache(TimeSpan.FromSeconds(settings.CacheListingSec), Account.RequestRepo.ActiveOperationsAsync); + + ////TODO: wow very dummy linking, refact cache realization globally! + //_itemCache = new ItemCache(TimeSpan.FromSeconds(settings.CacheListingSec)); + ////{ + //// Полагаемся на стандартно заданное время очистки + //// CleanUpPeriod = TimeSpan.FromMinutes(5) + ////}; LinkManager = settings.DisableLinkManager ? null : new LinkManager(this); } @@ -85,49 +93,131 @@ public virtual async Task GetPublicItemAsync(Uri url, ItemType itemType return entry; } - ///// - ///// Get list of files and folders from account. - ///// - ///// Path in the cloud to return the list of the items. - ///// Unknown, File/Folder if you know for sure - ///// True if you know for sure that's not a linked item - ///// List of the items. - public virtual async Task GetItemAsync(string path, ItemType itemType = ItemType.Unknown, bool resolveLinks = true) + + private readonly ConcurrentDictionary> _getItemDict = + new(StringComparer.InvariantCultureIgnoreCase); + + private readonly SemaphoreSlim _getItemDictLocker = new SemaphoreSlim(1); + + /// + /// Get list of files and folders from account. + /// + /// Path in the cloud to return the list of the items. + /// Unknown, File/Folder if you know for sure + /// True if you know for sure that's not a linked item + /// True to skip link manager, cache and so on, just get + /// the entry info from cloud as fast as possible. + /// List of the items. + public virtual Task GetItemAsync(string path, ItemType itemType = ItemType.Unknown, + bool resolveLinks = true, bool fastGetFromCloud = false) { - if (Settings.Protocol == Protocol.WebM1Bin || Settings.Protocol == Protocol.WebV2) + /* + * Параметр ItemType сохранен для совместимости со старыми вызовами, + * чтобы не потерять и сохранить информацию когда что надо получить, + * на случай, если понадобится, но сейчас по факту параметр не используется. + */ + + /* + * Клиенты очень часто читают одну и ту же директорию через короткий промежуток времени. + * Часто получается так, что первый запрос чтения папки с сервера еще не завершен, + * а тут уже второй пришел, а поскольку кеш еще не образован результатом первого обращения, + * то уже оба идут к серверу, не взирая на то, что выбирается одна и та же папка. + * Поэтому, составляем словарь, где ключ - FullPath, а значение - Task. + * Первый, кто лезет за содержимым папки, формирует Task, кладет его в словарь, + * по окончании очищает словарь от записи. + * Последующие, обнаружив запись в словаре, используют сохраненный Task, + * точнее его Result. До формирования значения в Result последующие обращения блокируются + * и ждут завершения, затем получают готовое значение и уходят довольные. + * Это снижает нагрузку на канал до сервера, нагрузку на сервер и позволяет для + * последующих обращений получать результат раньше, т.к. результат первого обращения + * точно будет получен раньше, т.к. раньше начался. + */ + + if (_getItemDict.TryGetValue(path, out var oldTask)) + return oldTask; + + _getItemDictLocker.Wait(); + try + { + if (_getItemDict.TryGetValue(path, out oldTask)) + return oldTask; + + _getItemDict[path] = Task.Run(() => GetItemInternalAsync(path, resolveLinks, fastGetFromCloud).Result); + } + finally + { + _getItemDictLocker.Release(); + } + + try + { + return Task.FromResult(_getItemDict[path].Result); + } + finally + { + _getItemDict.TryRemove(path, out _); + } + } + + private static readonly Regex _mailRegex = + new Regex(@"\A/(?https://cloud\.mail\.\w+/public/\S+/\S+(/.*)?)\Z", + RegexOptions.IgnoreCase | RegexOptions.Singleline); + + /// + /// Данный метод запускается для каждого path только в одном потоке. + /// Все остальные потоки ожидают результата от единственного выполняемого. + /// См. комментарий в методе GetItemAsync. + /// + /// + /// + /// + private async Task GetItemInternalAsync(string path, bool resolveLinks, bool fastGetFromCloud = false) + { + if (!fastGetFromCloud && + (Settings.Protocol == Protocol.WebM1Bin || Settings.Protocol == Protocol.WebV2)) { //TODO: вообще, всё плохо стало, всё запуталось, всё надо переписать - var uriMatch = Regex.Match(path, @"\A/(?https://cloud\.mail\.\w+/public/\S+/\S+(/.*)?)\Z"); + var uriMatch = _mailRegex.Match(path); if (uriMatch.Success) - return await GetPublicItemAsync(new Uri(uriMatch.Groups["uri"].Value, UriKind.Absolute), itemType); + return await GetPublicItemAsync(new Uri(uriMatch.Groups["uri"].Value, UriKind.Absolute)); } if (Account.IsAnonymous) return null; path = WebDavPath.Clean(path); + RemotePath remotePath; - if (Settings.CacheListingSec > 0) + if (fastGetFromCloud) { - var cached = CacheGetEntry(path); - if (cached is not null) - return cached; + remotePath = RemotePath.Get(path); + return await Account.RequestRepo.FolderInfo(remotePath, depth: 1, limit: 2); } + (var cached, var getState) = _entryCache.Get(path); + if (getState == EntryCache.GetState.Entry) + return cached; + if (getState == EntryCache.GetState.NotExists) + return null; + //TODO: subject to refact!!! Link ulink = resolveLinks && LinkManager is not null ? await LinkManager.GetItemLink(path) : null; - // bad link detected, just return stub - // cause client cannot, for example, delete it if we return NotFound for this item + /* + * Если LinkManager на затребованный path выдал ссылку, а ссылка бракованная и не рабочая, + * то нельзя выдать null, показывающий отсутствие файла/папки, иначе удалить такую + * бракованную ссылку будет невозможно. + * Потому формируем специальную заглушку с признаком Bad. + */ if (ulink is { IsBad: true }) { var res = ulink.ToBadEntry(); - _itemCache.Add(res.FullPath, res); + _entryCache.Add(res); return res; } - if (itemType == ItemType.Unknown && ulink is not null) - itemType = ulink.ItemType; + //if (itemType == ItemType.Unknown && ulink is not null) + // itemType = ulink.ItemType; // TODO: cache (parent) folder for file //if (itemType == ItemType.File) @@ -136,122 +226,234 @@ public virtual async Task GetItemAsync(string path, ItemType itemType = // _itemCache.Add(cachefolder.FullPath, cachefolder); // //_itemCache.Add(cachefolder.Files); //} + remotePath = ulink is null ? RemotePath.Get(path) : RemotePath.Get(ulink); + var cloudResult = await Account.RequestRepo.FolderInfo(remotePath, depth: Settings.ListDepth); + if (cloudResult is null) + { + // Если обратились к серверу, а в ответ пустота, + // надо прочистить кеш, на случай, если в кеше что-то есть, + // а в облаке параллельно удалили папку. + // Если с сервера получено состояние, что папки нет, + // а кеш ранее говорил, что папка в кеше есть, но без наполнения, + // то папку удалили и надо безотлагательно очистить кеш. + if (getState == EntryCache.GetState.EntryWithUnknownContent) + { + Logger.Debug("Папка была удалена, делается чистка кеша"); + } + _entryCache.OnRemoveTreeAsync(remotePath.Path, null); - var rp = ulink is null ? RemotePath.Get(path) : RemotePath.Get(ulink); - var entry = await Account.RequestRepo.FolderInfo(rp, depth:Settings.ListDepth); - if (entry is null) return null; + } - if (itemType == ItemType.Unknown) - itemType = entry is Folder - ? ItemType.Folder - : ItemType.File; - if (itemType == ItemType.Folder && entry is Folder folder) // fill folder with links if any - FillWithULinks(folder); + //if (itemType == ItemType.Unknown) + // itemType = cloudResult is Folder + // ? ItemType.Folder + // : ItemType.File; - if (Settings.CacheListingSec > 0) - CacheAddEntry(entry); + //if (itemType == ItemType.Folder && cloudResult is Folder folder) // fill folder with links if any + // FillWithULinks(folder); - return entry; + if (LinkManager is not null && cloudResult is Folder f) + { + FillWithULinks(f); + } + + //if (Settings.CacheListingSec > 0) + // CacheAddEntry(cloudResult); + + _entryCache.Add(cloudResult); + + + /* + * Если запрошен файл или папка, которого нет в кеше, + * при этом не известно, есть ли он на сервере, + * есть вероятность, что скоро будет запрошен соседний файл или папка + * и той же родительской папки, поэтому имеет смысл сделать упреждающее + * чтение содержимого родительской папки с сервера или убедиться, + * что она есть в кеше. + */ + string parentPath = WebDavPath.Parent(path); + if (parentPath != path && _entryCache.IsCacheEnabled) + { + // Здесь не ожидается результата работы, задача отработает в фоне и заполнит кеш. + // Обращение к серверу делается после чтения с сервера результата текущего обращения, + // то есть сначала затребованный результат, а потом фоновый, не наоборот, чтобы не ждать. + _ = GetItemAsync(parentPath).ConfigureAwait(false); + } + + return cloudResult; } private void FillWithULinks(Folder folder) { - if (!folder.IsChildrenLoaded) return; + if (folder == null || !folder.IsChildrenLoaded) + return; - if (LinkManager is not null) + string fullPath = folder.FullPath; + + var flinks = LinkManager.GetItems(fullPath); + if (flinks is not null) { - var flinks = LinkManager.GetItems(folder.FullPath); - if (flinks is not null && flinks.Any()) + var newChildren = new List(); + foreach (var flink in flinks) { - foreach (var flink in flinks) - { - string linkpath = WebDavPath.Combine(folder.FullPath, flink.Name); + string linkpath = WebDavPath.Combine(fullPath, flink.Name); - if (!flink.IsFile) - { - Folder item = new Folder(0, linkpath) { CreationTimeUtc = flink.CreationDate ?? DateTime.MinValue }; - folder.Folders.AddOrUpdate(item.FullPath, item, (_, _) => item); - } - else - { - if (folder.Files.ContainsKey(linkpath)) - continue; + if (flink.IsFile) + { + if (folder.Descendants.Any(entry => entry.FullPath.Equals(linkpath, StringComparison.InvariantCultureIgnoreCase))) + continue; - var newFile = new File(linkpath, flink.Size, new PublicLinkInfo(flink.Href)); - if (flink.CreationDate is not null) - newFile.LastWriteTimeUtc = flink.CreationDate.Value; - folder.Files.AddOrUpdate(newFile.FullPath, newFile, (_, _) => newFile); - } + var newFile = new File(linkpath, flink.Size, new PublicLinkInfo(flink.Href)); + if (flink.CreationDate is not null) + newFile.LastWriteTimeUtc = flink.CreationDate.Value; + newChildren.Add(newFile); + } + else + { + Folder newFolder = new Folder(0, linkpath) { CreationTimeUtc = flink.CreationDate ?? DateTime.MinValue }; + newChildren.Add(newFolder); } } + if (newChildren.Count > 0) + { + folder.Descendants = folder.Descendants.AddRange(newChildren); + } } - foreach (var childFolder in folder.Folders.Values) - FillWithULinks(childFolder); + foreach (var child in folder.Descendants) + { + if (child is Folder f) + FillWithULinks(f); + } } + //private void FillWithULinks(Folder folder) + //{ + // if (!folder.IsChildrenLoaded) return; + + // if (LinkManager is not null) + // { + // var flinks = LinkManager.GetItems(folder.FullPath); + // if (flinks is not null && flinks.Any()) + // { + // foreach (var flink in flinks) + // { + // string linkpath = WebDavPath.Combine(folder.FullPath, flink.Name); + + // if (!flink.IsFile) + // { + // Folder item = new Folder(0, linkpath) { CreationTimeUtc = flink.CreationDate ?? DateTime.MinValue }; + // folder.Folders.AddOrUpdate(item.FullPath, item, (_, _) => item); + // } + // else + // { + // if (folder.Files.ContainsKey(linkpath)) + // continue; + + // var newFile = new File(linkpath, flink.Size, new PublicLinkInfo(flink.Href)); + // if (flink.CreationDate is not null) + // newFile.LastWriteTimeUtc = flink.CreationDate.Value; + // folder.Files.AddOrUpdate(newFile.FullPath, newFile, (_, _) => newFile); + // } + // } + // } + // } + + // foreach (var childFolder in folder.Folders.Values) + // FillWithULinks(childFolder); + //} - private void CacheAddEntry(IEntry entry) - { - switch (entry) - { - case File cfile: - _itemCache.Add(cfile.FullPath, cfile); - break; - case Folder { IsChildrenLoaded: true } cfolder: - { - _itemCache.Add(cfolder.FullPath, cfolder); - _itemCache.Add(cfolder.Files.Select(f => new KeyValuePair(f.Value.FullPath, f.Value))); - foreach (var childFolder in cfolder.Entries) - CacheAddEntry(childFolder); - break; - } - } - } + //private void CacheAddEntry(IEntry entry) + //{ + // switch (entry) + // { + // case File cfile: + // _itemCache.Add(cfile.FullPath, cfile); + // break; + // case Folder { IsChildrenLoaded: true } cfolder: + // { + // _itemCache.Add(cfolder.FullPath, cfolder); + // _itemCache.Add(cfolder.Files.Select(f => new KeyValuePair(f.Value.FullPath, f.Value))); + + // foreach (var childFolder in cfolder.Entries) + // CacheAddEntry(childFolder); + // break; + // } + // } + //} - private IEntry CacheGetEntry(string path) => _itemCache.Get(path); + //private IEntry CacheGetEntry(string path) => _itemCache.Get(path); - public virtual IEntry GetItem(string path, ItemType itemType = ItemType.Unknown, bool resolveLinks = true) - => GetItemAsync(path, itemType, resolveLinks).Result; + //public virtual IEntry GetItem(string path, ItemType itemType = ItemType.Unknown, bool resolveLinks = true) + // => GetItemAsync(path, itemType, resolveLinks).Result; - public bool IsFileExists(string filename, List folderPaths) + /// + /// Поиск файла/папки по названию (без пути) в перечисленных папках. + /// Возвращает полный путь к файлу или папке. + /// + /// Название файла/папки без пути. + /// Список полных папок с полными путями. + /// + public string Find(string nameWithoutPathToFind, params string[] folderPaths) { - if (folderPaths is null) - return false; + if (folderPaths is null || folderPaths.Length == 0 || string.IsNullOrEmpty(nameWithoutPathToFind)) + return null; + + List paths = new List(); + // Сначала смотрим в кеше, без обращений к серверу + foreach (var folderPath in folderPaths) + { + var path = WebDavPath.Combine(folderPath, nameWithoutPathToFind); + (var cached, var getState) = _entryCache.Get(path); + // Если файл или папка найдены в кеше + if (getState == EntryCache.GetState.Entry) + return cached.FullPath; + // Если файл или папка точно отсутствуют в кеше и на сервере + if (getState == EntryCache.GetState.NotExists) + continue; + // В остальных случаях будем искать дальше + paths.Add(folderPath); + } + if (paths.Count == 0) + return null; + + // В кеше файла не оказалось, читаем все директории и смотрим а них - var folders = folderPaths + var tasks = paths .AsParallel() - .WithDegreeOfParallelism(Math.Min(MaxInnerParallelRequests, folderPaths.Count)) - .Select(async path => (Folder)await GetItemAsync(path, ItemType.Folder, false)); + .WithDegreeOfParallelism(paths.Count) + .Select(async path => await GetItemAsync(path, ItemType.Folder, false)); - if (folders is null) - return false; + if (tasks is null) + return null; - foreach (var item in folders) + foreach (var task in tasks) { - if (item.Result is null) + if (task.Result is null) continue; - Folder folder = item.Result; - // Вместо перебора и сравнения всех названий файлов в папке, - // формируем полный путь файла, как если бы он был в данной папке, - // а затем проверяем наличие в словаре Files по FullPath. - string fullPath = Path.Combine(folder.FullPath, filename); - if (folder.Files.TryGetValue(fullPath, out _)) - return true; + IEntry entry = task.Result; + if (entry.Name.Equals(nameWithoutPathToFind, StringComparison.InvariantCultureIgnoreCase)) + return entry.FullPath; + + foreach (var child in entry.Descendants) + { + if (child.Name.Equals(nameWithoutPathToFind, StringComparison.InvariantCultureIgnoreCase)) + return child.FullPath; + } } - return false; + return null; } #region == Publish ========================================================================================================================== private async Task Unpublish(Uri publicLink, string fullPath) { - //var res = (await new UnpublishRequest(CloudApi, publicLink).MakeRequestAsync()) - var res = (await Account.RequestRepo.Unpublish(publicLink, fullPath)) + //var res = (await new UnpublishRequest(CloudApi, publicLink).MakeRequestAsync(_connectionLimiter)) + var res = (await Account.RequestRepo.Unpublish(publicLink, fullPath)) .ThrowIf(r => !r.IsSuccess, _ => new Exception($"Unpublish error, link = {publicLink}")); return res.IsSuccess; @@ -264,7 +466,7 @@ public async Task Unpublish(File file) await Unpublish(innerFile.GetPublicLinks(this).First().Uri, innerFile.FullPath); innerFile.PublicLinks.Clear(); } - _itemCache.Invalidate(file.FullPath, file.Path); + _entryCache.OnRemoveTreeAsync(file.FullPath, GetItemAsync(file.FullPath, fastGetFromCloud: true)); } @@ -272,7 +474,7 @@ private async Task Publish(string fullPath) { var res = (await Account.RequestRepo.Publish(fullPath)) .ThrowIf(r => !r.IsSuccess, _ => new Exception($"Publish error, path = {fullPath}")); - + var uri = new Uri(res.Url, UriKind.RelativeOrAbsolute); if (!uri.IsAbsoluteUri) uri = new Uri($"{Account.RequestRepo.PublicBaseUrlDefault.TrimEnd('/')}/{res.Url.TrimStart('/')}", UriKind.Absolute); @@ -280,7 +482,7 @@ private async Task Publish(string fullPath) return uri; } - public async Task Publish(File file, bool makeShareFile = true, + public async Task Publish(File file, bool makeShareFile = true, bool generateDirectVideoLink = false, bool makeM3UFile = false, SharedVideoResolution videoResolution = SharedVideoResolution.All) { if (file.Files.Count > 1 && (generateDirectVideoLink || makeM3UFile)) @@ -296,7 +498,7 @@ public async Task Publish(File file, bool makeShareFile = true, if (makeShareFile) { - string path = $"{file.FullPath}{PublishInfo.SharedFilePostfix}"; + string path = string.Concat(file.FullPath, PublishInfo.SharedFilePostfix); UploadFileJson(path, info) .ThrowIf(r => !r, _ => new Exception($"Cannot upload JSON file, path = {path}")); } @@ -304,14 +506,14 @@ public async Task Publish(File file, bool makeShareFile = true, if (makeM3UFile) { - string path = $"{file.FullPath}{PublishInfo.PlaylistFilePostfix}"; + string path = string.Concat(file.FullPath, PublishInfo.PlayListFilePostfix); var content = new StringBuilder(); { content.Append("#EXTM3U\r\n"); foreach (var item in info.Items) { content.Append($"#EXTINF:-1,{WebDavPath.Name(item.Path)}\r\n"); - content.Append($"{item.PlaylistUrl}\r\n"); + content.Append($"{item.PlayListUrl}\r\n"); } } UploadFile(path, content.ToString()) @@ -328,7 +530,7 @@ public async Task Publish(Folder folder, bool makeShareFile = true) folder.PublicLinks.TryAdd(url.AbsolutePath, new PublicLinkInfo(url)); var info = folder.ToPublishInfo(); - if (!makeShareFile) + if (!makeShareFile) return info; string path = WebDavPath.Combine(folder.FullPath, PublishInfo.SharedFilePostfix); @@ -338,7 +540,7 @@ public async Task Publish(Folder folder, bool makeShareFile = true) return info; } - public async Task Publish(IEntry entry, bool makeShareFile = true, + public async Task Publish(IEntry entry, bool makeShareFile = true, bool generateDirectVideoLink = false, bool makeM3UFile = false, SharedVideoResolution videoResolution = SharedVideoResolution.All) { return entry switch @@ -377,9 +579,14 @@ public async Task Copy(Folder folder, string destinationPath) } } - //var copyRes = await new CopyRequest(CloudApi, folder.FullPath, destinationPath).MakeRequestAsync(); + //var copyRes = await new CopyRequest(CloudApi, folder.FullPath, destinationPath).MakeRequestAsync(_connectionLimiter); var copyRes = await Account.RequestRepo.Copy(folder.FullPath, destinationPath); - if (!copyRes.IsSuccess) return false; + if (!copyRes.IsSuccess) + return false; + + _entryCache.ResetCheck(); + _entryCache.OnCreateAsync(destinationPath, GetItemAsync(destinationPath, fastGetFromCloud: true)); + _entryCache.OnRemoveTreeAsync(folder.FullPath, GetItemAsync(folder.FullPath, fastGetFromCloud: true)); //clone all inner links if (LinkManager is not null) @@ -397,13 +604,11 @@ public async Task Copy(Folder folder, string destinationPath) if (await Rename(cloneres.Path, linka.Name)) continue; - _itemCache.Invalidate(destinationPath); return false; } } } - _itemCache.Invalidate(destinationPath); return true; } @@ -416,7 +621,8 @@ public async Task Copy(Folder folder, string destinationPath) public async Task Copy(string sourcePath, string destinationPath) { var entry = await GetItemAsync(sourcePath); - if (entry is null) return false; + if (entry is null) + return false; return await Copy(entry, destinationPath); } @@ -465,33 +671,44 @@ public async Task Copy(File file, string destinationPath, string newName) { string newFullPath = WebDavPath.Combine(destPath, WebDavPath.Name(cloneRes.Path)); var renameRes = await Rename(newFullPath, link.Name); - if (!renameRes) return false; + if (!renameRes) + return false; } - if (cloneRes.IsSuccess) _itemCache.Invalidate(destinationPath); - return cloneRes.IsSuccess; + + if (!cloneRes.IsSuccess) + return false; + + _entryCache.ResetCheck(); + _entryCache.OnCreateAsync(destPath, GetItemAsync(destPath, fastGetFromCloud: true)); + _entryCache.OnRemoveTreeAsync(link.Href.OriginalString, GetItemAsync(link.Href.OriginalString, fastGetFromCloud: true)); + + return true; } } var qry = file.Files .AsParallel() - .WithDegreeOfParallelism(Math.Min(MaxInnerParallelRequests, file.Files.Count)) + .WithDegreeOfParallelism(file.Files.Count) .Select(async pfile => { - //var copyRes = await new CopyRequest(CloudApi, pfile.FullPath, destPath, ConflictResolver.Rewrite).MakeRequestAsync(); + //var copyRes = await new CopyRequest(CloudApi, pfile.FullPath, destPath, ConflictResolver.Rewrite).MakeRequestAsync(_connectionLimiter); var copyRes = await Account.RequestRepo.Copy(pfile.FullPath, destPath, ConflictResolver.Rewrite); if (!copyRes.IsSuccess) return false; - if (!doRename && WebDavPath.Name(copyRes.NewName) == newName) + if (!doRename && WebDavPath.Name(copyRes.NewName) == newName) return true; string newFullPath = WebDavPath.Combine(destPath, WebDavPath.Name(copyRes.NewName)); return await Rename(newFullPath, pfile.Name.Replace(file.Name, newName)); }); - _itemCache.Invalidate(destinationPath); bool res = (await Task.WhenAll(qry)) .All(r => r); + _entryCache.ResetCheck(); + _entryCache.OnCreateAsync(destinationPath, GetItemAsync(destinationPath, fastGetFromCloud: true)); + _entryCache.OnRemoveTreeAsync(file.FullPath, GetItemAsync(file.FullPath, fastGetFromCloud: true)); + return res; } @@ -526,7 +743,7 @@ public async Task Rename(IEntry source, string newName) /// Source folder info. /// New folder name. /// True or false operation result. - public async Task Rename(Folder folder, string newFileName) + public async Task Rename(Folder folder, string newFileName) => await Rename(folder.FullPath, newFileName); /// @@ -539,7 +756,7 @@ public async Task Rename(File file, string newFileName) { var result = await Rename(file.FullPath, newFileName).ConfigureAwait(false); - if (file.Files.Count <= 1) + if (file.Files.Count <= 1) return result; foreach (var splitFile in file.Parts) @@ -559,18 +776,21 @@ public async Task Rename(File file, string newFileName) /// True or false result operation. private async Task Rename(string fullPath, string newName) { - var link = LinkManager is null? null : await LinkManager.GetItemLink(fullPath, false); + var link = LinkManager is null ? null : await LinkManager.GetItemLink(fullPath, false); //rename item if (link is null) { var data = await Account.RequestRepo.Rename(fullPath, newName); - if (!data.IsSuccess) + if (!data.IsSuccess) return data.IsSuccess; - LinkManager.ProcessRename(fullPath, newName); - _itemCache.Invalidate(fullPath, WebDavPath.Parent(fullPath)); + LinkManager?.ProcessRename(fullPath, newName); + string newNamePath = WebDavPath.Combine(WebDavPath.Parent(fullPath), newName); + _entryCache.ResetCheck(); + _entryCache.OnCreateAsync(newNamePath, GetItemAsync(newNamePath, fastGetFromCloud: true)); + _entryCache.OnRemoveTreeAsync(fullPath, GetItemAsync(fullPath, fastGetFromCloud: true)); return data.IsSuccess; } @@ -580,7 +800,11 @@ private async Task Rename(string fullPath, string newName) { bool res = LinkManager.RenameLink(link, newName); if (res) - _itemCache.Invalidate(fullPath, WebDavPath.Parent(fullPath)); + { + _entryCache.ResetCheck(); + _entryCache.OnCreateAsync(newName, GetItemAsync(newName, fastGetFromCloud: true)); + _entryCache.OnRemoveTreeAsync(fullPath, GetItemAsync(fullPath, fastGetFromCloud: true)); + } return res; } return false; @@ -625,9 +849,9 @@ public bool Move(string sourcePath, string destinationPath) /// - /// Move folder in another space on the server. + /// Move folder to another place on the server. /// - /// Folder info to move. + /// Folder to move. /// Destination path on the server. /// True or false operation result. public async Task MoveAsync(Folder folder, string destinationPath) @@ -637,13 +861,21 @@ public async Task MoveAsync(Folder folder, string destinationPath) { var remapped = await LinkManager.RemapLink(link, destinationPath); if (remapped) - _itemCache.Invalidate(WebDavPath.Parent(folder.FullPath), destinationPath); + { + _entryCache.ResetCheck(); + _entryCache.OnCreateAsync(destinationPath, GetItemAsync(destinationPath, fastGetFromCloud: true)); + _entryCache.OnRemoveTreeAsync(folder.FullPath, GetItemAsync(folder.FullPath, fastGetFromCloud: true)); + } return remapped; } var res = await Account.RequestRepo.Move(folder.FullPath, destinationPath); - _itemCache.Invalidate(WebDavPath.Parent(folder.FullPath), destinationPath); - if (!res.IsSuccess) return false; + _entryCache.ResetCheck(); + _entryCache.OnCreateAsync(destinationPath, GetItemAsync(destinationPath, fastGetFromCloud: true)); + _entryCache.OnRemoveTreeAsync(folder.FullPath, GetItemAsync(folder.FullPath, fastGetFromCloud: true)); + + if (!res.IsSuccess) + return false; //clone all inner links if (LinkManager is not null) @@ -661,7 +893,6 @@ public async Task MoveAsync(Folder folder, string destinationPath) if (!cloneres.IsSuccess) continue; - _itemCache.Invalidate(destinationPath); if (WebDavPath.Name(cloneres.Path) != linka.Name) { var renRes = await Rename(cloneres.Path, linka.Name); @@ -688,26 +919,30 @@ public async Task MoveAsync(File file, string destinationPath) { var remapped = await LinkManager.RemapLink(link, destinationPath); if (remapped) - _itemCache.Invalidate(file.Path, destinationPath); + { + _entryCache.ResetCheck(); + _entryCache.OnCreateAsync(destinationPath, GetItemAsync(destinationPath, fastGetFromCloud: true)); + _entryCache.OnRemoveTreeAsync(file.FullPath, GetItemAsync(file.FullPath, fastGetFromCloud: true)); + } return remapped; } var qry = file.Files .AsParallel() - .WithDegreeOfParallelism(Math.Min(MaxInnerParallelRequests, file.Files.Count)) + .WithDegreeOfParallelism(file.Files.Count) .Select(async pfile => { - var moveres = await Account.RequestRepo.Move(pfile.FullPath, destinationPath); - _itemCache.Forget(file.Path, pfile.FullPath); - return moveres; + return await Account.RequestRepo.Move(pfile.FullPath, destinationPath); }); - _itemCache.Forget(file.Path, file.FullPath); - _itemCache.Invalidate(destinationPath); bool res = (await Task.WhenAll(qry)) .All(r => r.IsSuccess); + _entryCache.ResetCheck(); + _entryCache.OnCreateAsync(destinationPath, GetItemAsync(destinationPath, fastGetFromCloud: true)); + _entryCache.OnRemoveTreeAsync(file.FullPath, GetItemAsync(file.FullPath, fastGetFromCloud: true)); + return res; } @@ -751,7 +986,7 @@ public virtual async Task Remove(File file, bool removeShareDescription = // remove all parts if file splitted var qry = file.Files .AsParallel() - .WithDegreeOfParallelism(Math.Min(MaxInnerParallelRequests, file.Files.Count)) + .WithDegreeOfParallelism(file.Files.Count) .Select(async pfile => { var removed = await Remove(pfile.FullPath); @@ -764,25 +999,32 @@ public virtual async Task Remove(File file, bool removeShareDescription = if (file.Name.EndsWith(PublishInfo.SharedFilePostfix)) //unshare master item { var mpath = WebDavPath.Clean(file.FullPath.Substring(0, file.FullPath.Length - PublishInfo.SharedFilePostfix.Length)); - var item = await GetItemAsync(mpath); - + var entry = await GetItemAsync(mpath); - switch (item) + switch (entry) { - case Folder folder: - await Unpublish(folder.GetPublicLinks(this).First().Uri, folder.FullPath); - break; - case File ifile: - await Unpublish(ifile); - break; + case Folder folder: + await Unpublish(folder.GetPublicLinks(this).First().Uri, folder.FullPath); + break; + case File ifile: + await Unpublish(ifile); + break; } } else { if (removeShareDescription) //remove share description (.wdmrc.share) { - if (await GetItemAsync(file.FullPath + PublishInfo.SharedFilePostfix) is File sharefile) + string fullName = string.Concat(file.FullPath, PublishInfo.SharedFilePostfix); + string path = WebDavPath.Parent(file.FullPath); + string name = WebDavPath.Name(file.FullPath); + string foundFullPath = Find(name, path); + + if (foundFullPath is not null && + await GetItemAsync(foundFullPath) is File sharefile) + { await Remove(sharefile, false); + } } } @@ -796,33 +1038,36 @@ public virtual async Task Remove(File file, bool removeShareDescription = /// /// Full file or folder name. /// True or false result operation. - private async Task Remove(string fullPath) + public async Task Remove(string fullPath) { - //TODO: refact - var link = LinkManager is null ? null : await LinkManager.GetItemLink(fullPath, false); - - if (link is not null) + if (LinkManager is not null) { - //if folder is linked - do not delete inner files/folders if client deleting recursively - //just try to unlink folder - LinkManager.RemoveLink(fullPath); - - _itemCache.Invalidate(WebDavPath.Parent(fullPath)); - return true; + var link = await LinkManager.GetItemLink(fullPath, false); + if (link is not null) + { + // if folder is linked - do not delete inner files/folders + // if client deleting recursively just try to unlink folder + LinkManager.RemoveLink(fullPath); + _entryCache.ResetCheck(); + _entryCache.OnRemoveTreeAsync(fullPath, GetItemAsync(fullPath, fastGetFromCloud: true)); + return true; + } } var res = await Account.RequestRepo.Remove(fullPath); - if (!res.IsSuccess) + if (!res.IsSuccess) return false; - //remove inner links + // remove inner links if (LinkManager is not null) { var innerLinks = LinkManager.GetChildren(fullPath); LinkManager.RemoveLinks(innerLinks); } - _itemCache.Forget(WebDavPath.Parent(fullPath), fullPath); //_itemCache.Invalidate(WebDavPath.Parent(fullPath)); + _entryCache.ResetCheck(); + _entryCache.OnRemoveTreeAsync(fullPath, GetItemAsync(fullPath, fastGetFromCloud: true)); + return res.IsSuccess; } @@ -856,16 +1101,8 @@ public void AbortAllAsyncThreads() CancelToken.Cancel(false); } - public byte MaxInnerParallelRequests - { - get => _maxInnerParallelRequests; - set => _maxInnerParallelRequests = value != 0 ? value : (byte)1; - } - public IRequestRepo Repo => Account.RequestRepo; - private byte _maxInnerParallelRequests = 5; - /// /// Create folder on the server. /// @@ -886,7 +1123,12 @@ public async Task CreateFolderAsync(string fullPath) { var res = await Account.RequestRepo.CreateFolder(fullPath); - if (res.IsSuccess) _itemCache.Invalidate(WebDavPath.Parent(fullPath)); + if (res.IsSuccess) + { + _entryCache.ResetCheck(); + _entryCache.OnCreateAsync(fullPath, GetItemAsync(fullPath, fastGetFromCloud: true)); + } + return res.IsSuccess; } @@ -896,18 +1138,22 @@ public async Task CreateFolderAsync(string fullPath) //} - public async Task CloneItem(string path, string url) + public async Task CloneItem(string toPath, string fromUrl) { - var res = await Account.RequestRepo.CloneItem(url, path); + var res = await Account.RequestRepo.CloneItem(fromUrl, toPath); - if (res.IsSuccess) _itemCache.Invalidate(path); + if (res.IsSuccess) + { + _entryCache.ResetCheck(); + _entryCache.OnCreateAsync(toPath, GetItemAsync(toPath, fastGetFromCloud: true)); + } return res; } public async Task GetFileDownloadStream(File file, long? start, long? end) { - var task = Task.FromResult(new DownloadStreamFabric(this).Create(file, start, end)) - .ConfigureAwait(false); + var result = new DownloadStreamFabric(this).Create(file, start, end); + var task = Task.FromResult(result).ConfigureAwait(false); Stream stream = await task; return stream; } @@ -917,10 +1163,9 @@ public async Task GetFileUploadStream(string fullFilePath, long size, Ac { var file = new File(fullFilePath, size); - var f = new UploadStreamFabric(this) { - FileStreamSent = fileStreamSent, + FileStreamSent = fileStreamSent, ServerFileProcessed = serverFileProcessed }; @@ -936,8 +1181,11 @@ public async Task GetFileUploadStream(string fullFilePath, long size, Ac private void OnFileUploaded(IEnumerable files) { var lst = files.ToList(); - _itemCache.Invalidate(lst.Select(f => f.FullPath)); - _itemCache.Invalidate(lst.Select(file => file.Path).Distinct()); + foreach (var file in lst) + { + _entryCache.OnCreateAsync(file.FullPath, GetItemAsync(file.FullPath, fastGetFromCloud: true)); + } + _entryCache.ResetCheck(); FileUploaded?.Invoke(lst); } @@ -951,15 +1199,6 @@ public T DownloadFileAsJson(File file) return ser.Deserialize(jsonReader); } - public string DownloadFileAsString(File file) - { - using var stream = Account.RequestRepo.GetDownloadStream(file); - using var reader = new StreamReader(stream); - - string res = reader.ReadToEnd(); - return res; - } - /// /// Download content of file /// @@ -969,8 +1208,16 @@ public async Task DownloadFileAsString(string path) { try { - var file = (File)await GetItemAsync(path); - return DownloadFileAsString(file); + var entry = await GetItemAsync(path); + if (entry is null || entry is not File file) + return null; + { + using var stream = Account.RequestRepo.GetDownloadStream(file); + using var reader = new StreamReader(stream); + + string res = await reader.ReadToEndAsync(); + return res; + } } catch (Exception e) when ( // let's check if there really no file or just other network error @@ -989,11 +1236,13 @@ e.InnerException is WebException we && public bool UploadFile(string path, byte[] content, bool discardEncryption = false) { - using (var stream = GetFileUploadStream(path, content.Length, null, null, discardEncryption).Result) + using (var stream = GetFileUploadStream(path, content.Length, null, null, discardEncryption).Result) { stream.Write(content, 0, content.Length); } - _itemCache.Invalidate(path, WebDavPath.Parent(path)); + + _entryCache.ResetCheck(); + _entryCache.OnCreateAsync(path, GetItemAsync(path, fastGetFromCloud: true)); return true; } @@ -1003,7 +1252,6 @@ public bool UploadFile(string path, string content, bool discardEncryption = fal { var data = Encoding.UTF8.GetBytes(content); return UploadFile(path, data, discardEncryption); - } public bool UploadFileJson(string fullFilePath, T data, bool discardEncryption = false) @@ -1041,7 +1289,7 @@ public async Task LinkItem(Uri url, string path, string name, bool isFile, if (res) { LinkManager.Save(); - _itemCache.Invalidate(path); + _entryCache.OnCreateAsync(path, GetItemAsync(path, fastGetFromCloud: true)); } return res; } @@ -1052,15 +1300,19 @@ public async void RemoveDeadLinks() return; var count = await LinkManager.RemoveDeadLinks(true); - if (count > 0) _itemCache.Invalidate(); + if (count > 0) + _entryCache.Clear(); } public async Task AddFile(IFileHash hash, string fullFilePath, long size, ConflictResolver? conflict = null) { var res = await Account.RequestRepo.AddFile(fullFilePath, hash, size, DateTime.Now, conflict); - + if (res.Success) - _itemCache.Invalidate(fullFilePath, WebDavPath.Parent(fullFilePath)); + { + _entryCache.ResetCheck(); + _entryCache.OnCreateAsync(fullFilePath, GetItemAsync(fullFilePath, fastGetFromCloud: true)); + } return res; } @@ -1082,7 +1334,7 @@ public async Task SetFileDateTime(File file, DateTime dateTime) if (res) { file.LastWriteTimeUtc = dateTime; - _itemCache.Invalidate(file.Path, file.FullPath); + _entryCache.OnCreateAsync(file.FullPath, GetItemAsync(file.FullPath, fastGetFromCloud: true)); } return res; diff --git a/MailRuCloud/MailRuCloudApi/CloudSettings.cs b/MailRuCloud/MailRuCloudApi/CloudSettings.cs index 19572153..4d358379 100644 --- a/MailRuCloud/MailRuCloudApi/CloudSettings.cs +++ b/MailRuCloud/MailRuCloudApi/CloudSettings.cs @@ -16,7 +16,9 @@ public class CloudSettings public int CacheListingSec { get; set; } = 30; - public int ListDepth + public int MaxConnectionCount { get; set; } = 10; + + public int ListDepth { get => CacheListingSec > 0 ? _listDepth : 1; set => _listDepth = value; diff --git a/MailRuCloud/MailRuCloudApi/Common/EntryCache.cs b/MailRuCloud/MailRuCloudApi/Common/EntryCache.cs new file mode 100644 index 00000000..868bf0f0 --- /dev/null +++ b/MailRuCloud/MailRuCloudApi/Common/EntryCache.cs @@ -0,0 +1,777 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Threading; +using System.Threading.Tasks; +using YaR.Clouds.Base; +using YaR.Clouds.Links; +using YaR.Clouds.Base.Requests.Types; +using System.Linq; + +namespace YaR.Clouds.Common; + +public class EntryCache +{ + public enum GetState + { + /// + /// Когда из менеджера кеша запрашивается файл, файла нет в кеше, + /// а менеджер не обладает информацией о наличии или отсутствии файла в облаке. + /// + Unknown, + /// + /// Когда менеджер кеша имеет в кеше всю папку, где должен быть файл, + /// но файла такого в папке в кеше нету, то его нет и в облаке. + /// + NotExists, + /// + /// Когда менеджер имеет в кеше файл и возвращает его. + /// + Entry, + /// + /// Когда менеджер имеет в кеше папку, но в кеше отсутствует ее содержимое, которое надо считать с сервера. + /// + EntryWithUnknownContent + }; + + private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(EntryCache)); + + private static readonly TimeSpan _minCleanUpInterval = new TimeSpan(0, 0, 10 /* секунды */ ); + private static readonly TimeSpan _maxCleanUpInterval = new TimeSpan(0, 10 /* минуты */, 0); + + // По умолчанию очистка кеша от устаревших записей производится каждые 30 секунд + private TimeSpan _cleanUpPeriod = TimeSpan.FromSeconds(30); + + private readonly TimeSpan _expirePeriod; + + public bool IsCacheEnabled { get; private set; } + + private readonly Timer _cleanTimer; + + private readonly ConcurrentDictionary _root = + new(StringComparer.InvariantCultureIgnoreCase); + + private readonly SemaphoreSlim _locker = new SemaphoreSlim(1); + + public delegate Task CheckOperations(); + private readonly CheckOperations _activeOperationsAsync; + private readonly Timer _checkActiveOperationsTimer; + // Проверка активных операций на сервере и внешних изменений в облаке не через сервис + // производится каждые 24 секунды, число не кратное очистке кеша, чтобы не пересекались + // алгоритмы. + private readonly TimeSpan _opCheckPeriod = TimeSpan.FromSeconds(24); + + private CheckUpInfo.CheckInfo? _lastComparedInfo; + + [DebuggerDisplay("{DebuggerDisplay,nq}")] + internal class CacheItem + { + /// + /// Отметка о времени размещения в кеше. + /// + public DateTime CreationTime { get; set; } + /// + /// Entry - файл, папка или link в кеше, если не null. + /// Или null, если сохраняется информация о том, + /// что на сервере нет такого файла или папки, а то есть некоторые клиенты, + /// которые многократно требуют то, что было уже ранее удалено. + /// + public IEntry Entry { get; set; } + + /// + /// Если часть файлов кеша папки устарело и отсутствует, + /// нельзя выдавать содержимое папки из кеша. + /// + public bool AllDescendantsInCache { get; set; } + + public string DebuggerDisplay => + $"{(Entry is null + ? "<>" + : Entry is Link + ? "<>" + : Entry is File + ? "<>" + : AllDescendantsInCache + ? "<>" + : "<>")}" + + $", Since {CreationTime:HH:mm:ss} {Entry.FullPath}"; + } + + public EntryCache(TimeSpan expirePeriod, CheckOperations activeOperationsAsync) + { + _expirePeriod = expirePeriod; + IsCacheEnabled = Math.Abs(_expirePeriod.TotalMilliseconds) > 0.01; + + _lastComparedInfo = null; + _activeOperationsAsync = activeOperationsAsync; + _checkActiveOperationsTimer = null; + _cleanTimer = null; + + if (IsCacheEnabled) + { + _cleanTimer = new Timer(_ => RemoveExpired(), null, _cleanUpPeriod, _cleanUpPeriod); + + if (_activeOperationsAsync is not null && expirePeriod.TotalMinutes >= 1) + { + // Если кеш достаточно длительный, делаются регулярные + // проверки на изменения в облаке + _checkActiveOperationsTimer = new Timer(_ => CheckActiveOps(), null, _opCheckPeriod, _opCheckPeriod); + } + } + } + + //public TimeSpan CleanUpPeriod + //{ + // get => _cleanUpPeriod; + // set + // { + // // Очистку кеша от устаревших записей не следует проводить часто чтобы не нагружать машину, + // // и не следует проводить редко, редко, чтобы не натыкаться постоянно на устаревшие записи. + // _cleanUpPeriod = value < _minCleanUpInterval + // ? _minCleanUpInterval + // : value > _maxCleanUpInterval + // ? _maxCleanUpInterval + // : value; + + // if (IsCacheEnabled) + // { + // long cleanPreiod = (long)_cleanUpPeriod.TotalMilliseconds; + // _cleanTimer.Change(cleanPreiod, cleanPreiod); + // } + // } + //} + + public int RemoveExpired() + { + if (_root.IsEmpty) return 0; + + var watch = Stopwatch.StartNew(); + + DateTime threshold = DateTime.Now - _expirePeriod; + int removedCount = 0; + int partiallyExpiredCount = 0; + foreach (var entry in _root) + { + _locker.Wait(); + try + { + if (entry.Value.CreationTime <= threshold && + _root.TryRemove(entry.Key, out var cacheEntry)) + { + /* + * Если элемент кеша устарел и удаляется, то + * у папки, в которой он содержится, если она в кеше, + * ставится признак AllChildrenInCache=false, + * чтобы при запросе на выборку содержимого папки + * не был возвращен неполный список из-за удаленных + * из кеша устаревших элементов. + */ + removedCount++; + if (entry.Key != WebDavPath.Root) + { + if (_root.TryGetValue(WebDavPath.Parent(entry.Key), out var parentEntry) && + parentEntry.Entry is Folder) + { + parentEntry.AllDescendantsInCache = false; + partiallyExpiredCount++; + } + } + } + } + finally + { + _locker.Release(); + } + } + + if (removedCount > 0) + Logger.Debug($"Items cache clean: removed {removedCount} expired " + + $"items, {partiallyExpiredCount} marked partially expired ({watch.ElapsedMilliseconds} ms)"); + + return removedCount; + } + + public void ResetCheck() + { + if (!IsCacheEnabled) + return; + + _locker.Wait(); + try + { + _lastComparedInfo = null; + } + finally + { + _locker.Release(); + } + } + + public async void CheckActiveOps() + { + CheckUpInfo info = await _activeOperationsAsync(); + if (info is null) + return; + + CheckUpInfo.CheckInfo? currentValue; + + _locker.Wait(); + try + { + currentValue = _lastComparedInfo; + _lastComparedInfo = info.AccountInfo; + } + finally + { + _locker.Release(); + } + + if (currentValue is not null) + { + if (info.AccountInfo.FilesCount != currentValue.Value.FilesCount || + info.AccountInfo.Trash != currentValue.Value.Trash || + info.AccountInfo.Free != currentValue.Value.Free) + { + // Если между проверками что-то изменились, делаем полный сброс кеша + Clear(); + return; + } + } + + List paths = new List(); + foreach (var op in info.ActiveOperations) + { + if (!string.IsNullOrEmpty(op.SourcePath)) + { + if (!paths.Contains(WebDavPath.Parent(op.SourcePath))) + { + paths.Add(WebDavPath.Parent(op.SourcePath)); + Logger.Debug($"Operation '{op.Type}' is progressing, clean up under {op.SourcePath}"); + } + } + if (!string.IsNullOrEmpty(op.TargetPath)) + { + if (!paths.Contains(WebDavPath.Parent(op.TargetPath))) + { + paths.Add(WebDavPath.Parent(op.TargetPath)); + Logger.Debug($"Operation '{op.Type}' is progressing, clean up under {op.TargetPath}"); + } + } + } + if (paths.Count == 0) + return; + + await _locker.WaitAsync(); + try + { + foreach (var cacheItem in _root) + { + if (paths.Any(x => WebDavPath.IsParent(x, cacheItem.Key, selfTrue: true, oneLevelDistanceOnly: false))) + { + _root.TryRemove(cacheItem.Key, out _); + } + } + } + finally + { + _locker.Release(); + } + } + + public (IEntry, GetState) Get(string fullPath) + { + if (!IsCacheEnabled) + return (default, GetState.Unknown); + + IEntry result = default; + + _locker.Wait(); + try + { + if (!_root.TryGetValue(fullPath, out var cachedEntry)) + { + if (_root.TryGetValue(WebDavPath.Parent(fullPath), out var parentEntry) && + parentEntry.Entry is Folder parentFolder && + parentEntry.AllDescendantsInCache) + { + // Когда в кеше нет элемента, но в родительской директории, + // где он должен быть, загружены все элементы в кеш, + // то можно точно сказать, что такого элемента нет + // не только в кеше, но и на сервере. + Logger.Debug($"Cache says: {fullPath} doesn't exist"); + return (default, GetState.NotExists); + } + + Logger.Debug($"Cache missed: {fullPath}"); + return (default, GetState.Unknown); + } + + if (cachedEntry.Entry is null) + { + Logger.Debug($"Cache says: {fullPath} doesn't exist"); + return (default, GetState.NotExists); + } + + DateTime threshold = DateTime.Now - _expirePeriod; + if (cachedEntry.CreationTime <= threshold) + { + Logger.Debug($"Cache expired: {fullPath}"); + return (default, GetState.Unknown); + } + + if (cachedEntry.Entry is File file) + { + result = file.New(file.FullPath); + } + + if (cachedEntry.Entry is Folder folder) + { + if (!cachedEntry.AllDescendantsInCache) + { + Logger.Debug($"Cache says: {fullPath} folder's content isn't cached"); + return (default, GetState.EntryWithUnknownContent); + } + + var children = new List(); + + foreach (var cacheItem in _root) + { + if (WebDavPath.IsParent(fullPath, cacheItem.Key, selfTrue: false, oneLevelDistanceOnly: true)) + { + // Если при формировании списка содержимого папки из кеша + // выясняется, что часть содержимого в кеше устарело, + // то список из кеша сформировать не можем. + if (cacheItem.Value.CreationTime <= threshold) + return (default, GetState.Unknown); + + if (cacheItem.Value.Entry is not null) + { + // В кеше может быть информация о том, что файла/папки нет, + // то есть null, такие пропускаем. + children.Add(cacheItem.Value.Entry); + } + } + } + result = folder.New(folder.FullPath, children); + } + } + finally + { + _locker.Release(); + } + + Logger.Debug($"Cache hit: {fullPath}"); + return (result, GetState.Entry); + } + + public void Add(IEntry entry) + { + if (!IsCacheEnabled) + return; + + AddInternal(entry); + } + + private void AddInternal(IEntry entry) + { + // Параметр со временем нужен для того, чтобы все + // добавляемые в кеш элементы ровно в одно и то же время + // становились устаревшими. + if (entry is Link link) + Add(link, DateTime.Now); + else + if (entry is File file) + AddInternal(file, DateTime.Now); + else + if (entry is Folder folder) + { + if (folder.IsChildrenLoaded) + { + _locker.Wait(); + try + { + AddWithChildren(folder, DateTime.Now); + } + finally + { + _locker.Release(); + } + } + else + { + AddOne(folder, DateTime.Now); + } + } + } + + private void AddInternal(File file, DateTime creationTime) + { + if (file.Attributes.HasFlag(System.IO.FileAttributes.Offline)) + { + // Файл затронут активной операцией на сервере + RemoveOne(file.FullPath); + } + else + { + string fullPath = file.FullPath; + var cachedItem = new CacheItem() + { + Entry = file.New(fullPath), + AllDescendantsInCache = true, + CreationTime = creationTime + }; + _root.AddOrUpdate(fullPath, cachedItem, (_, _) => cachedItem); + } + } + + private void Add(Link link, DateTime creationTime) + { + string fullPath = link.FullPath; + var cachedItem = new CacheItem() + { + Entry = link, + AllDescendantsInCache = true, + CreationTime = creationTime + }; + _root.AddOrUpdate(fullPath, cachedItem, (_, _) => cachedItem); + } + + private CacheItem AddOne(Folder folder, DateTime creationTime) + { + string fullPath = folder.FullPath; + + if (folder.Attributes.HasFlag(System.IO.FileAttributes.Offline)) + { + // Папка затронута активной операцией на сервере + RemoveTree(folder.FullPath); + return null; + } + else + { + var cachedItem = new CacheItem() + { + // При добавлении в кеш из folder всего содержимого, + // делается очистка его списка содержимого, чтобы не было + // соблазна использовать информацию где-то дальше, + // т.к. она однозначно перестанет быть актуальной, + // т.к. алгоритмы кеша не работают с этим списком, + // этот список только носитель данных при выгрузке с сервера. + Entry = folder.New(fullPath), + AllDescendantsInCache = false, + CreationTime = creationTime + }; + _root.AddOrUpdate(fullPath, cachedItem, + (key, value) => + { + /* + * Если папка в кеше имела признак, что все потомки загружены в кеш, + * а потом пришло обновление entry этой папки, но признака наличия + * в кеше всех потомков нету, то мы выставляем его принудительно, + * т.к. знаем, что в кеше все есть, даже если они устарели, + * т.к. это будет обработано при следующем чтении списка папки. + */ + if (value.AllDescendantsInCache && !cachedItem.AllDescendantsInCache) + cachedItem.AllDescendantsInCache = true; + return cachedItem; + }); + + return cachedItem; + } + } + + private void AddWithChildren(Folder folder, DateTime creationTime) + { + if (folder.Attributes.HasFlag(System.IO.FileAttributes.Offline)) + { + // Папка затронута активной операцией на сервере + RemoveTreeNoLock(folder.FullPath); + } + else + { + foreach (var child in folder.Descendants) + { + if (child is File file) + AddInternal(file, creationTime); + else + if (child is Folder fld) + AddOne(fld, creationTime); + } + CacheItem cachedItem = AddOne(folder, creationTime); + cachedItem.AllDescendantsInCache = true; + /* + * Внимание! У добавляемого в кеш folder список Descendants всегда пустой! + * Он специально очищается, чтобы не было соблазна им пользоваться! + * Содержимое папки берется не из этого списка, а собирается из кеша по path всех entry. + */ + } + } + + public async void OnCreateAsync(string fullPath, Task newEntryTask) + { + if (!IsCacheEnabled) + return; + + IEntry newEntry = newEntryTask is null + ? null + : await newEntryTask; + + if (newEntry is null) + { + await _locker.WaitAsync(); + try + { + if (_root.TryRemove(fullPath, out var cachedItem)) + { + // Нового нет, но был, удалить из кеша + if (fullPath != WebDavPath.Root) + { + if (_root.TryGetValue(WebDavPath.Parent(fullPath), out var parentEntry) && + parentEntry.Entry is Folder) + { + parentEntry.AllDescendantsInCache = false; + } + } + } + else + { + // Нового нет, и не было + return; + } + } + finally + { + _locker.Release(); + } + } + else + { + await _locker.WaitAsync(); + try + { + bool removed = _root.TryRemove(fullPath, out var cachedItem); + + // Добавить новый + if (newEntry is File file) + AddInternal(file, DateTime.Now); + else + if (newEntry is Folder folder) + { + /* Данный метод вызывается для созданных и переименованных файлов и папок. + * С сервера читали entry самой папки и одного вложенного элемента. + * Если Descendants.Count равен 0, то можно ставить + * IsChildrenLoaded = true, т.к. ничего вложенного нет. + * Но если Descendants.Count>0, тогда IsChildrenLoaded = false, + * т.к. что-то внутри есть, но мы не читали полный список содержимого. + * IsChildrenLoaded ставится в true, + * чтобы последующие чтения entry созданного элемента + * читались из кеша, а не находили только entry от директории + * без потомков, что автоматически приводит к игнорированию кеша. + */ + folder.IsChildrenLoaded = folder.Descendants.Count == 0; + AddWithChildren(folder, DateTime.Now); + } + // После добавления или обновления элемента надо обновить родителя, + // если у него AllDescendantsInCache=true, иначе нет смысла + string parent = WebDavPath.Parent(fullPath); + if (fullPath != WebDavPath.Root && + _root.TryGetValue(parent, out var parentEntry) && + parentEntry.Entry is Folder fld && + parentEntry.AllDescendantsInCache) + { + /* + * Внимание! У добавляемого в кеш folder список Descendants всегда пустой! + * Он специально очищается, чтобы не было соблазна им пользоваться! + * Содержимое папки берется не из этого списка, а собирается из кеша по path всех entry. + * В кеше у папок Descendants всегда = ImmutableList.Empty + */ + if (removed) + { + if (cachedItem.Entry.IsFile) + { + if (fld.ServerFilesCount.HasValue && fld.ServerFilesCount > 0) + fld.ServerFilesCount--; + } + else + { + if (fld.ServerFoldersCount.HasValue && fld.ServerFoldersCount > 0) + fld.ServerFoldersCount--; + } + } + if (newEntry.IsFile) + { + if (fld.ServerFilesCount.HasValue) + fld.ServerFilesCount++; + } + else + { + if (fld.ServerFoldersCount.HasValue) + fld.ServerFoldersCount++; + } + } + } + finally + { + _locker.Release(); + } + } + } + + public async void OnRemoveTreeAsync(string fullPath, Task newEntryTask) + { + if (!IsCacheEnabled) + return; + + IEntry newEntry = newEntryTask is null + ? null + : await newEntryTask; + + if (newEntry is not null) + { + // Если операция удалила элемент, но он снова получен с сервера, + // это не нормальная ситуация. + // Сбрасываем весь кеш, чтобы все перечитать заново на всякий случай. + await _locker.WaitAsync(); + try + { + _root.Clear(); + } + finally + { + _locker.Release(); + } + return; + } + else + { + // Нового элемента на сервере нет, + // очищаем кеш от элемента и всего, что под ним + await _locker.WaitAsync(); + try + { + // Если элемент был, а нового нет, надо удалить его у родителя, + // чтобы не перечитывать родителя целиком с сервера, + // но только, если у родителя AllDescendantsInCache=true, иначе нет смысла + if (fullPath != WebDavPath.Root && + _root.TryGetValue(WebDavPath.Parent(fullPath), out var parentEntry) && + parentEntry.Entry is Folder fld && + parentEntry.AllDescendantsInCache) + { + /* + * Внимание! У добавляемого в кеш folder список Descendants всегда пустой! + * Он специально очищается, чтобы не было соблазна им пользоваться! + * Содержимое папки берется не из этого списка, а собирается из кеша по path всех entry. + * В кеше у папок Descendants всегда = ImmutableList.Empty + */ + if (_root.TryRemove(fullPath, out var cachedItem)) + { + if (cachedItem.Entry.IsFile) + { + if (fld.ServerFilesCount.HasValue && fld.ServerFilesCount > 0) + fld.ServerFilesCount--; + } + else + { + if (fld.ServerFoldersCount.HasValue && fld.ServerFoldersCount > 0) + fld.ServerFoldersCount--; + } + } + } + + foreach (var cacheItem in _root) + { + if (WebDavPath.IsParent(fullPath, cacheItem.Key, selfTrue: true, oneLevelDistanceOnly: false)) + { + cacheItem.Value.CreationTime = DateTime.MinValue; + } + } + + // Добавляем информацию о том, что на сервере нет элемента с таким путем, + // т.к. он был удален, а нового (из newEntryTask) нет. + var deletedItem = new CacheItem() + { + Entry = null, + AllDescendantsInCache = true, + CreationTime = DateTime.Now + }; + _root.TryAdd(fullPath, deletedItem); + } + finally + { + _locker.Release(); + } + } + } + + public void RemoveOne(string fullPath) + { + if (!IsCacheEnabled) + return; + + _locker.Wait(); + try + { + _root.TryRemove(fullPath, out _); + + if (_root.TryGetValue(WebDavPath.Parent(fullPath), out var parentEntry) && + parentEntry.Entry is Folder) + { + parentEntry.AllDescendantsInCache = false; + } + } + finally + { + _locker.Release(); + } + } + + public void RemoveTree(string fullPath) + { + if (!IsCacheEnabled) + return; + + _locker.Wait(); + try + { + RemoveTreeNoLock(fullPath); + } + finally + { + _locker.Release(); + } + } + + private void RemoveTreeNoLock(string fullPath) + { + _root.TryRemove(fullPath, out _); + + if (_root.TryGetValue(WebDavPath.Parent(fullPath), out var parentEntry) && + parentEntry.Entry is Folder) + { + parentEntry.AllDescendantsInCache = false; + } + foreach (var cacheItem in _root) + { + if (WebDavPath.IsParent(fullPath, cacheItem.Key, selfTrue: true, oneLevelDistanceOnly: false)) + { + _root.TryRemove(cacheItem.Key, out _); + } + } + } + + public void Clear() + { + if (!IsCacheEnabled) + return; + + _locker.Wait(); + try + { + _root.Clear(); + } + finally + { + _locker.Release(); + } + } +} diff --git a/MailRuCloud/MailRuCloudApi/Common/ItemCache.cs b/MailRuCloud/MailRuCloudApi/Common/ItemCache.cs index 409cbe35..5a6b1ccd 100644 --- a/MailRuCloud/MailRuCloudApi/Common/ItemCache.cs +++ b/MailRuCloud/MailRuCloudApi/Common/ItemCache.cs @@ -4,6 +4,10 @@ using System.Linq; using System.Threading; +/* + * ВНИМАНИЕ! Файл выключен из компиляции! + */ + namespace YaR.Clouds.Common { public class ItemCache diff --git a/MailRuCloud/MailRuCloudApi/Common/Retry.cs b/MailRuCloud/MailRuCloudApi/Common/Retry.cs index 3942dbc3..b34c4b34 100644 --- a/MailRuCloud/MailRuCloudApi/Common/Retry.cs +++ b/MailRuCloud/MailRuCloudApi/Common/Retry.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Threading; namespace YaR.Clouds.Common @@ -59,5 +60,33 @@ public static T Do( return res; } + + public static T Do( + Func sleepBefore, + Func action, + Func retryIf, + TimeSpan retryInterval, + TimeSpan? retryTimeout = null) + { + retryTimeout ??= TimeSpan.FromSeconds(15); + + var sleep = sleepBefore(); + if (sleep > TimeSpan.Zero) + Thread.Sleep(sleep); + + var watch = Stopwatch.StartNew(); + T res = default; + for (int attempted = 0; watch.Elapsed < retryTimeout; attempted++) + { + if (attempted > 0) + Thread.Sleep(retryInterval); + + res = action(); + if (!retryIf(res)) + return res; + } + + return res; + } } } diff --git a/MailRuCloud/MailRuCloudApi/Extensions/Extensions.cs b/MailRuCloud/MailRuCloudApi/Extensions/Extensions.cs index f4affbbc..df9eb56b 100644 --- a/MailRuCloud/MailRuCloudApi/Extensions/Extensions.cs +++ b/MailRuCloud/MailRuCloudApi/Extensions/Extensions.cs @@ -3,7 +3,9 @@ using System.IO; using System.Net; using System.Threading; +using System.Threading.Tasks; using YaR.Clouds.Base; +using YaR.Clouds.Common; namespace YaR.Clouds.Extensions { @@ -64,18 +66,18 @@ public static string ToHexString(this byte[] ba) // } //} - public static void ReadAsByte(this WebResponse resp, CancellationToken token, Stream outputStream = null) - { - using Stream responseStream = resp.GetResponseStream(); - var buffer = new byte[65536]; - int bytesRead; + //public static void ReadAsByte(this WebResponse resp, CancellationToken token, Stream outputStream = null) + //{ + // using Stream responseStream = resp.GetResponseStream(); + // var buffer = new byte[65536]; + // int bytesRead; - while (responseStream != null && (bytesRead = responseStream.Read(buffer, 0, buffer.Length)) > 0) - { - token.ThrowIfCancellationRequested(); - outputStream?.Write(buffer, 0, bytesRead); - } - } + // while (responseStream != null && (bytesRead = responseStream.Read(buffer, 0, buffer.Length)) > 0) + // { + // token.ThrowIfCancellationRequested(); + // outputStream?.Write(buffer, 0, bytesRead); + // } + //} public static T ThrowIf(this T data, Func func, Func ex) { @@ -142,4 +144,4 @@ public static Exception InnerOf(this Exception ex, Type t) } } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Links/Link.cs b/MailRuCloud/MailRuCloudApi/Links/Link.cs index 46938c51..6ae03882 100644 --- a/MailRuCloud/MailRuCloudApi/Links/Link.cs +++ b/MailRuCloud/MailRuCloudApi/Links/Link.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Concurrent; +using System.Collections.Immutable; using System.IO; using YaR.Clouds.Base; using YaR.Clouds.Links.Dto; @@ -82,6 +83,7 @@ public ConcurrentDictionary PublicLinks } } + public ImmutableList Descendants => ImmutableList.Empty; public FileAttributes Attributes => FileAttributes.Normal; //TODO: dunno what to do diff --git a/MailRuCloud/MailRuCloudApi/Links/LinkManager.cs b/MailRuCloud/MailRuCloudApi/Links/LinkManager.cs index a6777cb1..134cab1e 100644 --- a/MailRuCloud/MailRuCloudApi/Links/LinkManager.cs +++ b/MailRuCloud/MailRuCloudApi/Links/LinkManager.cs @@ -25,7 +25,7 @@ public class LinkManager private const string HistoryContainerName = "item.links.history.wdmrc"; private readonly Cloud _cloud; private ItemList _itemList = new(); - private readonly ItemCache _itemCache; + private readonly EntryCache _linkCache; private readonly SemaphoreSlim _locker; @@ -34,7 +34,7 @@ public LinkManager(Cloud cloud) { _locker = new SemaphoreSlim(1); _cloud = cloud; - _itemCache = new ItemCache(TimeSpan.FromSeconds(60)); + _linkCache = new EntryCache(TimeSpan.FromSeconds(60), null); //{ // Полагаемся на стандартно заданное время очистки // CleanUpPeriod = TimeSpan.FromMinutes(5) @@ -100,22 +100,25 @@ public async void Load() try { string filepath = WebDavPath.Combine(WebDavPath.Root, LinkContainerName); - var file = (File)_cloud.GetItem(filepath, Cloud.ItemType.File, false); - - // If the file is not empty. - // An empty file in UTF-8 with BOM is exactly 3 bytes long. - if (file is not null && file.Size > 3) - { - _itemList = _cloud.DownloadFileAsJson(file); - } - - _itemList ??= new ItemList(); - - foreach (var f in _itemList.Items) + //var file = (File)_cloud.GetItem(filepath, Cloud.ItemType.File, false); + var entry = _cloud.GetItemAsync(filepath, Cloud.ItemType.File, false).Result; + if (entry is not null && entry is File file) { - f.MapTo = WebDavPath.Clean(f.MapTo); - if (!f.Href.IsAbsoluteUri) - f.Href = new Uri(_cloud.Repo.PublicBaseUrlDefault + f.Href); + // If the file is not empty. + // An empty file in UTF-8 with BOM is exactly 3 bytes long. + if (file is not null && file.Size > 3) + { + _itemList = _cloud.DownloadFileAsJson(file); + } + + _itemList ??= new ItemList(); + + foreach (var f in _itemList.Items) + { + f.MapTo = WebDavPath.Clean(f.MapTo); + if (!f.Href.IsAbsoluteUri) + f.Href = new Uri(_cloud.Repo.PublicBaseUrlDefault + f.Href); + } } } catch (Exception e) @@ -166,7 +169,7 @@ public bool RemoveLink(string path, bool doSave = true) return false; _itemList.Items.Remove(z); - _itemCache.Invalidate(path, parent); + _linkCache.OnRemoveTreeAsync(path, null); if (doSave) Save(); @@ -217,7 +220,7 @@ public async Task RemoveDeadLinks(bool doWriteHistory) { foreach (var link in removes) { - _itemCache.Invalidate(link.FullPath, link.MapPath); + _linkCache.RemoveTree(link.FullPath); } string path = WebDavPath.Combine(WebDavPath.Root, HistoryContainerName); @@ -264,7 +267,7 @@ public async Task RemoveDeadLinks(bool doWriteHistory) /// public async Task GetItemLink(string path, bool doResolveType = true) { - var cached = _itemCache.Get(path); + (var cached, var getState) = _linkCache.Get(path); if (cached is not null) return (Link)cached; @@ -294,7 +297,7 @@ public async Task GetItemLink(string path, bool doResolveType = true) if (doResolveType) await ResolveLink(link); - _itemCache.Add(link.FullPath, link); + _linkCache.Add(link); return link; } @@ -306,7 +309,7 @@ private async Task ResolveLink(Link link) // ? link.Href.OriginalString.Remove(0, _cloud.Repo.PublicBaseUrlDefault.Length + 1) // : link.Href.OriginalString; - //var infores = await new ItemInfoRequest(_cloud.CloudApi, link.Href, true).MakeRequestAsync(); + //var infores = await new ItemInfoRequest(_cloud.CloudApi, link.Href, true).MakeRequestAsync(_connectionLimiter); var infores = await _cloud.Account.RequestRepo.ItemInfo(RemotePath.Get(link)); link.ItemType = infores.Body.Kind == "file" ? Cloud.ItemType.File @@ -356,15 +359,11 @@ public async Task Add(Uri url, string path, string name, bool isFile, long { path = WebDavPath.Clean(path); - var folder = (Folder)await _cloud.GetItemAsync(path); - if (folder.Entries.Any(entry => entry.Name == name)) + var entry = await _cloud.GetItemAsync(path); + if (entry is not null && entry.Descendants.Any(entry => entry.Name == name)) return false; - //url = GetRelaLink(url); path = WebDavPath.Clean(path); - - if (folder.Entries.Any(entry => entry.Name == name)) - return false; if (_itemList.Items.Any(it => WebDavPath.PathEquals(it.MapTo, path) && it.Name == name)) return false; @@ -378,7 +377,7 @@ public async Task Add(Uri url, string path, string name, bool isFile, long CreationDate = creationDate }); - _itemCache.Invalidate(path); + _linkCache.RemoveOne(path); return true; } @@ -417,7 +416,8 @@ public void ProcessRename(string fullPath, string newName) if (!changed) return; - _itemCache.Invalidate(fullPath, newPath); + _linkCache.RemoveTree(fullPath); + _linkCache.RemoveOne(newPath); Save(); } @@ -432,7 +432,7 @@ public bool RenameLink(Link link, string newName) ilink.Name = newName; Save(); - _itemCache.Invalidate(link.MapPath); + _linkCache.RemoveOne(link.FullPath); return true; } @@ -471,7 +471,9 @@ public async Task RemapLink(Link link, string destinationPath, bool doSave string oldmap = rootlink.MapTo; rootlink.MapTo = destinationPath; Save(); - _itemCache.Invalidate(link.FullPath, oldmap, destinationPath); + _linkCache.RemoveTree(oldmap); + _linkCache.RemoveOne(destinationPath); + _linkCache.RemoveOne(link.FullPath); return true; } @@ -490,8 +492,10 @@ public async Task RemapLink(Link link, string destinationPath, bool doSave if (!res) return false; - if (doSave) Save(); - _itemCache.Invalidate(destinationPath); + if (doSave) + Save(); + + _linkCache.RemoveOne(destinationPath); return true; } diff --git a/MailRuCloud/MailRuCloudApi/PublishInfo.cs b/MailRuCloud/MailRuCloudApi/PublishInfo.cs index ce09e016..34dc66d4 100644 --- a/MailRuCloud/MailRuCloudApi/PublishInfo.cs +++ b/MailRuCloud/MailRuCloudApi/PublishInfo.cs @@ -6,7 +6,7 @@ namespace YaR.Clouds public class PublishInfo { public const string SharedFilePostfix = ".share.wdmrc"; - public const string PlaylistFilePostfix = ".m3u8"; + public const string PlayListFilePostfix = ".m3u8"; public List Items { get; } = new(); public DateTime DateTime { get; set; } = DateTime.Now; @@ -16,6 +16,6 @@ public class PublishInfoItem { public string Path { get; set; } public List Urls { get; set; } - public string PlaylistUrl { get; set; } + public string PlayListUrl { get; set; } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/CryptInitCommand.cs b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/CryptInitCommand.cs index 013c799b..d451b800 100644 --- a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/CryptInitCommand.cs +++ b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/CryptInitCommand.cs @@ -9,7 +9,8 @@ namespace YaR.Clouds.SpecialCommands.Commands /// public class CryptInitCommand : SpecialCommand { - public CryptInitCommand(Cloud cloud, string path, IList parames) : base(cloud, path, parames) + public CryptInitCommand(Cloud cloud, string path, IList parameters) + : base(cloud, path, parameters) { } @@ -28,11 +29,11 @@ public override async Task Execute() path = WebDavPath.Combine(Path, param); var entry = await Cloud.GetItemAsync(path); - if (null == entry || entry.IsFile) + if (entry is null || entry.IsFile) return SpecialCommandResult.Fail; var res = await Cloud.CryptInit((Folder)entry); return new SpecialCommandResult(res); } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/DeleteCommand.cs b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/DeleteCommand.cs index 72b06300..8e157923 100644 --- a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/DeleteCommand.cs +++ b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/DeleteCommand.cs @@ -6,7 +6,8 @@ namespace YaR.Clouds.SpecialCommands.Commands { public class DeleteCommand : SpecialCommand { - public DeleteCommand(Cloud cloud, string path, IList parames): base(cloud, path, parames) + public DeleteCommand(Cloud cloud, string path, IList parameters) + : base(cloud, path, parameters) { } @@ -25,13 +26,11 @@ public override async Task Execute() path = WebDavPath.Combine(Path, param); var entry = await Cloud.GetItemAsync(path); - if (null == entry) + if (entry is null) return SpecialCommandResult.Fail; var res = await Cloud.Remove(entry); return new SpecialCommandResult(res); } } - - -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/FishCommand.cs b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/FishCommand.cs index 9466f1d9..b86aee0f 100644 --- a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/FishCommand.cs +++ b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/FishCommand.cs @@ -36,7 +36,7 @@ public override async Task Execute() try { - //var res = await new CreateFileRequest(Cloud.CloudApi, target, strRandomHash, randomSize, ConflictResolver.Rename).MakeRequestAsync(); + //var res = await new CreateFileRequest(Cloud.CloudApi, target, strRandomHash, randomSize, ConflictResolver.Rename).MakeRequestAsync(_connectionLimiter); var hash = new FileHashMrc(randomHash); var res = await Cloud.Account.RequestRepo.AddFile(target, hash, randomSize, DateTime.Now, ConflictResolver.Rename); if (res.Success) diff --git a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/ListCommand.cs b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/ListCommand.cs index f642e6d3..9f1e92dd 100644 --- a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/ListCommand.cs +++ b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/ListCommand.cs @@ -6,11 +6,14 @@ using YaR.Clouds.Base; using YaR.Clouds.Base.Repos; using YaR.Clouds.Links; +using YaR.Clouds.Common; namespace YaR.Clouds.SpecialCommands.Commands { public class ListCommand : SpecialCommand { + private const string FileListExtention = ".wdmrc.list.lst"; + //private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(FishCommand)); public ListCommand(Cloud cloud, string path, IList parameters) : base(cloud, path, parameters) @@ -27,12 +30,12 @@ public override async Task Execute() var resolvedTarget = await RemotePath.Get(target, Cloud.LinkManager); - var data = await Cloud.Account.RequestRepo.FolderInfo(resolvedTarget); - string resFilepath = WebDavPath.Combine(Path, data.Name + ".wdmrc.list.lst"); + var entry = await Cloud.Account.RequestRepo.FolderInfo(resolvedTarget); + string resFilepath = WebDavPath.Combine(Path, string.Concat(entry.Name, FileListExtention)); var sb = new StringBuilder(); - foreach (var e in Flat(data, Cloud.LinkManager)) + foreach (var e in Flat(entry, Cloud.LinkManager)) { string hash = (e as File)?.Hash.ToString() ?? "-"; string link = e.PublicLinks.Values.FirstOrDefault()?.Uri.OriginalString ?? "-"; @@ -49,24 +52,21 @@ private IEnumerable Flat(IEntry entry, LinkManager lm) { yield return entry; - if (entry is Folder folder) - { - var ifolders = folder.Entries - .AsParallel() - .WithDegreeOfParallelism(5) - .Select(it => it switch - { - File => it, - Folder ifolder => ifolder.IsChildrenLoaded - ? ifolder - : Cloud.Account.RequestRepo.FolderInfo(RemotePath.Get(it.FullPath, lm).Result, depth: 3).Result, - _ => throw new NotImplementedException("Unknown item type") - }) - .OrderBy(it => it.Name); - - foreach (var item in ifolders.SelectMany(f => Flat(f, lm))) - yield return item; - } + var ifolders = entry.Descendants + .AsParallel() + .WithDegreeOfParallelism(5) + .Select(it => it switch + { + File => it, + Folder ifolder => ifolder.IsChildrenLoaded + ? ifolder + : Cloud.Account.RequestRepo.FolderInfo(RemotePath.Get(it.FullPath, lm).Result, depth: 3).Result, + _ => throw new NotImplementedException("Unknown item type") + }) + .OrderBy(it => it.Name); + + foreach (var item in ifolders.SelectMany(f => Flat(f, lm))) + yield return item; } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/MoveCommand.cs b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/MoveCommand.cs index 6cf53ae1..5fd35168 100644 --- a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/MoveCommand.cs +++ b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/MoveCommand.cs @@ -6,7 +6,8 @@ namespace YaR.Clouds.SpecialCommands.Commands { public class MoveCommand : SpecialCommand { - public MoveCommand(Cloud cloud, string path, IList parames) : base(cloud, path, parames) + public MoveCommand(Cloud cloud, string path, IList parameters) + : base(cloud, path, parameters) { } @@ -17,12 +18,12 @@ public override async Task Execute() string source = WebDavPath.Clean(Parames.Count == 1 ? Path : Parames[0]); string target = WebDavPath.Clean(Parames.Count == 1 ? Parames[0] : Parames[1]); - var sourceEntry = await Cloud.GetItemAsync(source); - if (null == sourceEntry) + var entry = await Cloud.GetItemAsync(source); + if (entry is null) return SpecialCommandResult.Fail; - var res = await Cloud.MoveAsync(sourceEntry, target); + var res = await Cloud.MoveAsync(entry, target); return new SpecialCommandResult(res); } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/ShareCommand.cs b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/ShareCommand.cs index bbda392a..5409e634 100644 --- a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/ShareCommand.cs +++ b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/ShareCommand.cs @@ -10,7 +10,8 @@ namespace YaR.Clouds.SpecialCommands.Commands public class ShareCommand : SpecialCommand { - public ShareCommand(Cloud cloud, string path, bool generateDirectVideoLink, bool makeM3UFile, IList parames) : base(cloud, path, parames) + public ShareCommand(Cloud cloud, string path, bool generateDirectVideoLink, bool makeM3UFile, IList parameters) + : base(cloud, path, parameters) { _generateDirectVideoLink = generateDirectVideoLink; _makeM3UFile = makeM3UFile; @@ -40,7 +41,7 @@ public override async Task Execute() path = WebDavPath.Combine(Path, param); var entry = await Cloud.GetItemAsync(path); - if (null == entry) + if (entry is null) return SpecialCommandResult.Fail; try @@ -55,4 +56,4 @@ public override async Task Execute() return SpecialCommandResult.Success; } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/SharedFolderLinkCommand.cs b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/SharedFolderLinkCommand.cs index cb709541..d7ca6a1f 100644 --- a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/SharedFolderLinkCommand.cs +++ b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/SharedFolderLinkCommand.cs @@ -10,7 +10,8 @@ namespace YaR.Clouds.SpecialCommands.Commands { public class SharedFolderLinkCommand : SpecialCommand { - public SharedFolderLinkCommand(Cloud cloud, string path, IList parames): base(cloud, path, parames) + public SharedFolderLinkCommand(Cloud cloud, string path, IList parameters) + : base(cloud, path, parameters) { } @@ -27,10 +28,10 @@ public override async Task Execute() url = new Uri(Cloud.Repo.PublicBaseUrlDefault + m.Groups["url"].Value, UriKind.Absolute); //TODO: make method in MailRuCloud to get entry by url - //var item = await new ItemInfoRequest(Cloud.CloudApi, m.Groups["url"].Value, true).MakeRequestAsync(); + //var item = await new ItemInfoRequest(Cloud.CloudApi, m.Groups["url"].Value, true).MakeRequestAsync(_connectionLimiter); var item = await Cloud.Account.RequestRepo.ItemInfo(RemotePath.Get(new Link(url)) ); var entry = item.ToEntry(Cloud.Repo.PublicBaseUrlDefault); - if (null == entry) + if (entry is null) return SpecialCommandResult.Fail; string name = Parames.Count > 1 && !string.IsNullOrWhiteSpace(Parames[1]) @@ -44,4 +45,4 @@ public override async Task Execute() return new SpecialCommandResult(res); } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/TestCommand.cs b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/TestCommand.cs index 52ce1f84..b3fba1a9 100644 --- a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/TestCommand.cs +++ b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/TestCommand.cs @@ -6,7 +6,8 @@ namespace YaR.Clouds.SpecialCommands.Commands { public class TestCommand : SpecialCommand { - public TestCommand(Cloud cloud, string path, IList parames) : base(cloud, path, parames) + public TestCommand(Cloud cloud, string path, IList parameters) + : base(cloud, path, parameters) { } @@ -19,17 +20,16 @@ public override async Task Execute() if (await Cloud.GetItemAsync(path) is not File entry) return SpecialCommandResult.Fail; - //var auth = await new OAuthRequest(Cloud.CloudApi).MakeRequestAsync(); + //var auth = await new OAuthRequest(Cloud.CloudApi).MakeRequestAsync(_connectionLimiter); bool removed = await Cloud.Remove(entry, false); if (removed) { //var addreq = await new MobAddFileRequest(Cloud.CloudApi, entry.FullPath, entry.Hash, entry.Size, new DateTime(2010, 1, 1), ConflictResolver.Rename) - // .MakeRequestAsync(); + // .MakeRequestAsync(_connectionLimiter); } return SpecialCommandResult.Success; } } - -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Streams/UploadStreamFabric.cs b/MailRuCloud/MailRuCloudApi/Streams/UploadStreamFabric.cs index 52d231e4..97e7548e 100644 --- a/MailRuCloud/MailRuCloudApi/Streams/UploadStreamFabric.cs +++ b/MailRuCloud/MailRuCloudApi/Streams/UploadStreamFabric.cs @@ -1,6 +1,5 @@ using System; using System.IO; -using System.Linq; using System.Threading.Tasks; using YaR.Clouds.Base; using YaR.Clouds.XTSSharp; @@ -22,22 +21,22 @@ public UploadStreamFabric(Cloud cloud) public async Task Create(File file, FileUploadedDelegate onUploaded = null, bool discardEncryption = false) { - if (await _cloud.GetItemAsync(file.Path, Cloud.ItemType.Folder) is not Folder folder) + string folderPath = file.Path; + IEntry entry = await _cloud.GetItemAsync(file.Path, Cloud.ItemType.Folder); + if(entry is null || entry is not Folder folder) throw new DirectoryNotFoundException(file.Path); Stream stream; - bool cryptRequired = _cloud.IsFileExists(CryptFileInfo.FileName, WebDavPath.GetParents(folder.FullPath)); + string fullPath = _cloud.Find(CryptFileInfo.FileName, WebDavPath.GetParents(folder.FullPath).ToArray()); + bool cryptRequired = !string.IsNullOrEmpty(fullPath); if (cryptRequired && !discardEncryption) { if (!_cloud.Account.Credentials.CanCrypt) throw new Exception($"Cannot upload {file.FullPath} to crypt folder without additional password!"); // #142 remove crypted file parts if size changed - if (folder.Files.TryGetValue(file.FullPath, out var remoteFile)) - { - await _cloud.Remove(remoteFile); - } + await _cloud.Remove(file.FullPath); stream = GetCryptoStream(file, onUploaded); } @@ -85,4 +84,4 @@ private Stream GetCryptoStream(File file, FileUploadedDelegate onUploaded) return encustream; } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/YaR.Clouds.csproj b/MailRuCloud/MailRuCloudApi/YaR.Clouds.csproj index 20fc9608..668037dc 100644 --- a/MailRuCloud/MailRuCloudApi/YaR.Clouds.csproj +++ b/MailRuCloud/MailRuCloudApi/YaR.Clouds.csproj @@ -24,12 +24,21 @@ $(DefineConstants);DEBUG;TRACE + + + + + + + + + diff --git a/NWebDav/NWebDav.Server/Handlers/PropFindHandler.cs b/NWebDav/NWebDav.Server/Handlers/PropFindHandler.cs index 442d1063..1cc1c441 100644 --- a/NWebDav/NWebDav.Server/Handlers/PropFindHandler.cs +++ b/NWebDav/NWebDav.Server/Handlers/PropFindHandler.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -117,11 +118,7 @@ public async Task HandleRequestAsync(IHttpContext httpContext, IStore stor } prepareWatch.Stop(); -#if DEBUG - s_log.Log(LogLevel.Warning, () => $"Prepare for XML generation {prepareWatch.ElapsedMilliseconds} ms"); -#else s_log.Log(LogLevel.Debug, () => $"Prepare for XML generation {prepareWatch.ElapsedMilliseconds} ms"); -#endif var sw = Stopwatch.StartNew(); @@ -210,12 +207,7 @@ public async Task HandleRequestAsync(IHttpContext httpContext, IStore stor }); var elapsed = sw.ElapsedMilliseconds; -#if DEBUG - s_log.Log(LogLevel.Warning, () => $"Response XML generation {elapsed} ms"); -#else s_log.Log(LogLevel.Debug, () => $"Response XML generation {elapsed} ms"); -#endif - // Stream the document await response.SendResponseAsync(DavStatusCode.MultiStatus, xDocument).ConfigureAwait(false); @@ -237,9 +229,11 @@ private static async Task AddPropertyAsync(IHttpContext httpContext, XElement xR // Check if the property is supported // YaR: optimize //if (propertyManager.Properties.Any(p => p.Name == propertyName)) - var value = await propertyManager.TryGetPropertyAsync(httpContext, item, propertyName).ConfigureAwait(false); + var (isExists, value) = await propertyManager + .TryGetPropertyAsync(httpContext, item, propertyName) + .ConfigureAwait(false); - if (value.IsExists) + if (isExists) { //YaR: can't catch what that mean //if (value is IEnumerable) @@ -256,7 +250,7 @@ private static async Task AddPropertyAsync(IHttpContext httpContext, XElement xR //xProp.Add (new XElement(propertyName, value.Value)); //xProp.Add(new XStreamingElement(propertyName, value.Value)); - xProp.Add(GetPropertyXElement(propertyName, value.Value)); + xProp.Add(GetPropertyXElement(propertyName, value)); } else { @@ -299,25 +293,25 @@ private static XStreamingElement GetPropertyXElement(XName name, object value) return xval; var xvalnew = new XStreamingElement(name, value); - vals.Dict.Add(value, xvalnew); + vals.Dict[value] = xvalnew; return xvalnew; } else { - vals = new() { Dict = new Dictionary() }; + vals = new() { Dict = new ConcurrentDictionary() }; var xval = new XStreamingElement(name, value); if (value == null) vals.NullElement = xval; else - vals.Dict.Add(value, xval); + vals.Dict[value] = xval; PropertyCache[name] = vals; return xval; } } - private static readonly Dictionary Dict, XStreamingElement NullElement)> PropertyCache = new(); + private static readonly ConcurrentDictionary Dict, XStreamingElement NullElement)> PropertyCache = new(); private static readonly HashSet Properties2Cache = new() { @@ -407,6 +401,3 @@ private static async Task AddEntriesAsync(IStoreCollection collection, int depth } } } - - - diff --git a/NWebDav/NWebDav.Server/WebDavDispatcher.cs b/NWebDav/NWebDav.Server/WebDavDispatcher.cs index cabecb28..56bd83ea 100644 --- a/NWebDav/NWebDav.Server/WebDavDispatcher.cs +++ b/NWebDav/NWebDav.Server/WebDavDispatcher.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics; +using System.IO; using System.Net; using System.Reflection; using System.Security.Authentication; @@ -87,7 +88,7 @@ public async Task DispatchRequestAsync(IHttpContext httpContext) throw new ArgumentException("The HTTP context doesn't have a response.", nameof(httpContext)); // Determine the request log-string - var logRequest = $"{request.HttpMethod}:{request.Url}:{request.RemoteEndPoint}"; + var logRequest = $"{request.HttpMethod}:{Uri.UnescapeDataString(request.Url.AbsoluteUri)}:{request.RemoteEndPoint}"; var range = request.GetRange(); if (null != range) logRequest += $" ({range.Start?.ToString() ?? string.Empty}-{range.End?.ToString() ?? string.Empty})"; diff --git a/WDMRC.Console/CommandLineOptions.cs b/WDMRC.Console/CommandLineOptions.cs index 3eb44e6f..1de3ead8 100644 --- a/WDMRC.Console/CommandLineOptions.cs +++ b/WDMRC.Console/CommandLineOptions.cs @@ -1,4 +1,6 @@ -using System; +// Ignore Spelling: Ua + +using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using CommandLine; @@ -26,9 +28,12 @@ class CommandLineOptions // ReSharper disable once UnusedMember.Global public string Password { get; set; } - [Option("maxthreads", Default = 5, HelpText = "Maximum concurrent connections to cloud")] + [Option("maxthreads", Default = 5, HelpText = "Maximum concurrent listening connections to the service")] public int MaxThreadCount { get; set; } + [Option("maxconnections", Default = 10, HelpText = "Maximum concurrent connections to cloud")] + public int MaxConnectionCount { get; set; } + [Option("user-agent", HelpText = "\"browser\" user-agent")] public string UserAgent { get; set; } @@ -50,7 +55,7 @@ class CommandLineOptions [Option("protocol", Default = Protocol.WebM1Bin, HelpText = "Cloud protocol")] public Protocol Protocol { get; set; } - [Option("cache-listing", Default = 30, HelpText = "Duration of in-memory cache of folder's listing, sec")] + [Option("cache-listing", Default = 30, HelpText = "Folder cache expiration timeout, sec")] public int CacheListingSec { get; set; } [Option("cache-listing-depth", Default = 1, HelpText = "List query folder depth, always equals 1 when cache-listing>0")] diff --git a/WDMRC.Console/Payload.cs b/WDMRC.Console/Payload.cs index 0de62abf..a0550232 100644 --- a/WDMRC.Console/Payload.cs +++ b/WDMRC.Console/Payload.cs @@ -49,7 +49,8 @@ public static void Run(CommandLineOptions options) UserAgent = ConstructUserAgent(options.UserAgent, Config.DefaultUserAgent), SecChUa = ConstructSecChUa( options.SecChUa, Config.DefaultSecChUa), CacheListingSec = options.CacheListingSec, - ListDepth = options.CacheListingDepth, + MaxConnectionCount = options.MaxConnectionCount, + ListDepth = options.CacheListingDepth, AdditionalSpecialCommandPrefix = Config.AdditionalSpecialCommandPrefix, DefaultSharedVideoResolution = Config.DefaultSharedVideoResolution, UseLocks = options.UseLocks, @@ -213,12 +214,13 @@ private static void ShowInfo(CommandLineOptions options) Logger.Info($"User interactive: {Environment.UserInteractive}"); Logger.Info($"Version: {version}"); Logger.Info($"Using proxy: {options.ProxyAddress}"); - Logger.Info($"Max threads count: {options.MaxThreadCount}"); + Logger.Info($"Max listening threads count: {options.MaxThreadCount}"); + Logger.Info($"Max cloud connections count: {options.MaxConnectionCount}"); Logger.Info($"Cloud server response timeout: {options.WaitResponseTimeoutSec} sec"); Logger.Info($"Cloud download/upload timeout: {options.ReadWriteTimeoutSec} sec"); Logger.Info($"Wait for 100-Continue timeout: {options.Wait100ContinueTimeoutSec} sec"); Logger.Info($"Cloud protocol: {options.Protocol}"); - Logger.Info($"Duration of in-memory cache of folder's listing: {options.CacheListingSec} sec"); + Logger.Info($"Folder cache expiration timeout: {options.CacheListingSec} sec"); Logger.Info($"List query folder depth: {options.CacheListingDepth}"); Logger.Info($"Use locks: {options.UseLocks}"); Logger.Info($"Support links in /item.links.wdmrc: {(!options.DisableLinkManager)}"); diff --git a/WDMRC.Console/WDMRC.Console.csproj b/WDMRC.Console/WDMRC.Console.csproj index bb51b97e..639d3333 100644 --- a/WDMRC.Console/WDMRC.Console.csproj +++ b/WDMRC.Console/WDMRC.Console.csproj @@ -60,6 +60,9 @@ + + Never + Always diff --git a/WebDAV.Uploader/UploadStub.cs b/WebDAV.Uploader/UploadStub.cs index d1e45a46..d527ad80 100644 --- a/WebDAV.Uploader/UploadStub.cs +++ b/WebDAV.Uploader/UploadStub.cs @@ -8,17 +8,19 @@ namespace YaR.CloudMailRu.Client.Console { static class UploadStub { - public static int Upload(UploadOptions cmdoptions) + public static int Upload(UploadOptions cmdOptions) { - string user = cmdoptions.Login; - string password = cmdoptions.Password; - string listname = cmdoptions.FileList; - string targetpath = cmdoptions.Target; + string user = cmdOptions.Login; + string password = cmdOptions.Password; + string listname = cmdOptions.FileList; + string targetpath = cmdOptions.Target; if (targetpath.StartsWith(@"\\\")) +#pragma warning disable SYSLIB1045 // Convert to 'GeneratedRegexAttribute'. targetpath = Regex.Replace(targetpath, @"\A\\\\\\.*?\\.*?\\", @"\"); +#pragma warning restore SYSLIB1045 // Convert to 'GeneratedRegexAttribute'. targetpath = WebDavPath.Clean(targetpath); diff --git a/WebDavMailRuCloudStore/CustomHandlers/CopyHandler.cs b/WebDavMailRuCloudStore/CustomHandlers/CopyHandler.cs index c7e720c7..89680ab1 100644 --- a/WebDavMailRuCloudStore/CustomHandlers/CopyHandler.cs +++ b/WebDavMailRuCloudStore/CustomHandlers/CopyHandler.cs @@ -120,4 +120,4 @@ private static async Task CopyAsync(IStoreItem source, IStoreCollection destinat //do not copy recursively } } -} \ No newline at end of file +} diff --git a/WebDavMailRuCloudStore/CustomHandlers/DeleteHandler.cs b/WebDavMailRuCloudStore/CustomHandlers/DeleteHandler.cs index 155c1bc8..5426699d 100644 --- a/WebDavMailRuCloudStore/CustomHandlers/DeleteHandler.cs +++ b/WebDavMailRuCloudStore/CustomHandlers/DeleteHandler.cs @@ -52,7 +52,7 @@ public async Task HandleRequestAsync(IHttpContext httpContext, IStore stor return true; } - // Obtain the item that actually is deleted + // Obtain the item that actually is being deleted var deleteItem = await parentCollection.GetItemAsync(splitUri.Name, httpContext).ConfigureAwait(false); if (deleteItem == null) { @@ -102,4 +102,4 @@ private static async Task DeleteItemAsync(IStoreCollection collec return await collection.DeleteItemAsync(name, httpContext).ConfigureAwait(false); } } -} \ No newline at end of file +} diff --git a/WebDavMailRuCloudStore/CustomHandlers/GetAndHeadHandler.cs b/WebDavMailRuCloudStore/CustomHandlers/GetAndHeadHandler.cs index 3f97baec..f74a11fb 100644 --- a/WebDavMailRuCloudStore/CustomHandlers/GetAndHeadHandler.cs +++ b/WebDavMailRuCloudStore/CustomHandlers/GetAndHeadHandler.cs @@ -216,4 +216,4 @@ private static async Task CopyToAsync(Stream src, Stream dest, long start, long? } -} \ No newline at end of file +} diff --git a/WebDavMailRuCloudStore/CustomHandlers/MkcolHandler.cs b/WebDavMailRuCloudStore/CustomHandlers/MkcolHandler.cs index df2fb289..4fd7eebf 100644 --- a/WebDavMailRuCloudStore/CustomHandlers/MkcolHandler.cs +++ b/WebDavMailRuCloudStore/CustomHandlers/MkcolHandler.cs @@ -69,4 +69,4 @@ public async Task HandleRequestAsync(IHttpContext httpContext, IStore stor return true; } } -} \ No newline at end of file +} diff --git a/WebDavMailRuCloudStore/CustomHandlers/MoveHandler.cs b/WebDavMailRuCloudStore/CustomHandlers/MoveHandler.cs index a6562131..01fb1cc4 100644 --- a/WebDavMailRuCloudStore/CustomHandlers/MoveHandler.cs +++ b/WebDavMailRuCloudStore/CustomHandlers/MoveHandler.cs @@ -34,17 +34,7 @@ public async Task HandleRequestAsync(IHttpContext httpContext, IStore stor // We should always move the item from a parent container var splitSourceUri = RequestHelper.SplitUri(request.Url); - // Obtain source collection - var sourceCollection = await store.GetCollectionAsync(splitSourceUri.CollectionUri, httpContext).ConfigureAwait(false); - if (sourceCollection == null) - { - // Source not found - response.SetStatus(DavStatusCode.NotFound); - return true; - } - // Obtain the destination - //var destinationUri = request.GetDestinationUri(); var destinationUri = GetDestinationUri(request); if (destinationUri == null) { @@ -64,8 +54,22 @@ public async Task HandleRequestAsync(IHttpContext httpContext, IStore stor // We should always move the item to a parent var splitDestinationUri = RequestHelper.SplitUri(destinationUri); + // Obtain source collection + var sourceCollection = await store.GetCollectionAsync(splitSourceUri.CollectionUri, httpContext).ConfigureAwait(false); + if (sourceCollection == null) + { + // Source not found + response.SetStatus(DavStatusCode.NotFound); + return true; + } + // Obtain destination collection - var destinationCollection = await store.GetCollectionAsync(splitDestinationUri.CollectionUri, httpContext).ConfigureAwait(false); + var destinationCollection = + splitDestinationUri.CollectionUri.AbsoluteUri.Equals( + splitSourceUri.CollectionUri.AbsoluteUri, StringComparison.InvariantCultureIgnoreCase) + // If source and destination collections are the same, there is no need to request a server twice + ? sourceCollection + : await store.GetCollectionAsync(splitDestinationUri.CollectionUri, httpContext).ConfigureAwait(false); if (destinationCollection == null) { // Source not found @@ -80,7 +84,8 @@ public async Task HandleRequestAsync(IHttpContext httpContext, IStore stor var errors = new UriResultCollection(); // Move collection - await MoveAsync(sourceCollection, splitSourceUri.Name, destinationCollection, splitDestinationUri.Name, overwrite, httpContext, splitDestinationUri.CollectionUri, errors).ConfigureAwait(false); + await MoveAsync(sourceCollection, splitSourceUri.Name, destinationCollection, + splitDestinationUri.Name, overwrite, httpContext, splitDestinationUri.CollectionUri, errors).ConfigureAwait(false); // Check if there are any errors if (errors.HasItems) diff --git a/WebDavMailRuCloudStore/Extensions.cs b/WebDavMailRuCloudStore/Extensions.cs index a68d12bb..415b3561 100644 --- a/WebDavMailRuCloudStore/Extensions.cs +++ b/WebDavMailRuCloudStore/Extensions.cs @@ -14,7 +14,7 @@ public static async Task Remove(this Cloud cloud, IStoreItem item) { null => await Task.FromResult(false), LocalStoreItem storeItem => await cloud.Remove(storeItem.FileInfo), - LocalStoreCollection storeCollection => await cloud.Remove(storeCollection.DirectoryInfo), + LocalStoreCollection storeCollection => await cloud.Remove(storeCollection.FolderWithDescendants), _ => throw new ArgumentException(string.Empty, nameof(item)) }; } diff --git a/WebDavMailRuCloudStore/StoreBase/LocalStore.cs b/WebDavMailRuCloudStore/StoreBase/LocalStore.cs index 4e3dd1c8..ddff200d 100644 --- a/WebDavMailRuCloudStore/StoreBase/LocalStore.cs +++ b/WebDavMailRuCloudStore/StoreBase/LocalStore.cs @@ -29,17 +29,18 @@ public LocalStore(bool isWritable = true, ILockingManager lockingManager = null, public async Task GetItemAsync(WebDavUri uri, IHttpContext httpContext) { - var identity = (HttpListenerBasicIdentity)httpContext.Session.Principal.Identity; var path = uri.Path; try { - var item = await CloudManager.Instance(identity).GetItemAsync(path); - if (item != null) + var entry = await CloudManager + .Instance(httpContext.Session.Principal.Identity) + .GetItemAsync(path); + if (entry is not null) { - return item.IsFile - ? new LocalStoreItem((File)item, IsWritable, this) - : new LocalStoreCollection(httpContext, (Folder)item, IsWritable, this); + return entry.IsFile + ? new LocalStoreItem((File)entry, IsWritable, this) + : new LocalStoreCollection(httpContext, (Folder)entry, IsWritable, this); } } // ReSharper disable once RedundantCatchClause @@ -57,11 +58,16 @@ public async Task GetCollectionAsync(WebDavUri uri, IHttpConte { var path = uri.Path; - var item = await CloudManager + var entry = await CloudManager .Instance(httpContext.Session.Principal.Identity) .GetItemAsync(path, Cloud.ItemType.Folder); - - return item == null ? null : new LocalStoreCollection(httpContext, (Folder)item, IsWritable, this); + if (entry != null) + { + if (entry.IsFile) + throw new Exception("File from cloud is processed as a folder"); + return new LocalStoreCollection(httpContext, (Folder)entry, IsWritable, this); + } + return null; } } } diff --git a/WebDavMailRuCloudStore/StoreBase/LocalStoreCollection.cs b/WebDavMailRuCloudStore/StoreBase/LocalStoreCollection.cs index 1a813e98..e030901a 100644 --- a/WebDavMailRuCloudStore/StoreBase/LocalStoreCollection.cs +++ b/WebDavMailRuCloudStore/StoreBase/LocalStoreCollection.cs @@ -17,30 +17,32 @@ namespace YaR.Clouds.WebDavStore.StoreBase { - [DebuggerDisplay("{DirectoryInfo.FullPath}")] + [DebuggerDisplay("{DebuggerDisplay,nq}")] public class LocalStoreCollection : ILocalStoreCollection { private static readonly ILogger Logger = LoggerFactory.Factory.CreateLogger(typeof(LocalStoreCollection)); private readonly IHttpContext _context; private readonly LocalStore _store; - public Folder DirectoryInfo { get; } - public IEntry EntryInfo => DirectoryInfo; - public long Length => DirectoryInfo.Size; + public Folder FolderWithDescendants { get; } + public IEntry EntryInfo => FolderWithDescendants; + public long Length => FolderWithDescendants.Size; public bool IsReadable => false; - public LocalStoreCollection(IHttpContext context, Folder directoryInfo, bool isWritable, + private string DebuggerDisplay => FolderWithDescendants.FullPath; + + public LocalStoreCollection(IHttpContext context, Folder folderWithChildren, bool isWritable, LocalStore store) { _context = context; - DirectoryInfo = directoryInfo ?? throw new ArgumentNullException(nameof(directoryInfo)); + FolderWithDescendants = folderWithChildren ?? throw new ArgumentNullException(nameof(folderWithChildren)); _store = store; IsWritable = isWritable; } - public string CalculateEtag() + public string CalculateETag() { - string h = DirectoryInfo.FullPath; + string h = FolderWithDescendants.FullPath; var hash = SHA256.Create().ComputeHash(GetBytes(h)); return BitConverter.ToString(hash).Replace("-", string.Empty); } @@ -55,9 +57,9 @@ private static byte[] GetBytes(string str) //public PropertyManager DefaultPropertyManager { get; } public bool IsWritable { get; } - public string Name => DirectoryInfo.Name; - public string UniqueKey => DirectoryInfo.FullPath; - public string FullPath => DirectoryInfo.FullPath; + public string Name => FolderWithDescendants.Name; + public string UniqueKey => FolderWithDescendants.FullPath; + public string FullPath => FolderWithDescendants.FullPath; public IPropertyManager PropertyManager => _store.CollectionPropertyManager; public ILockingManager LockingManager => _store.LockingManager; @@ -102,10 +104,10 @@ public Task GetItemAsync(string name, IHttpContext httpContext) public Task> GetItemsAsync(IHttpContext httpContext) { - var list = DirectoryInfo.Entries - .Select(entry => entry.IsFile - ? (IStoreItem)new LocalStoreItem((File)entry, IsWritable, _store) - : new LocalStoreCollection(httpContext, (Folder)entry, IsWritable, _store)) + var list = FolderWithDescendants.Descendants + .Select(child => child.IsFile + ? (IStoreItem)new LocalStoreItem((File)child, IsWritable, _store) + : new LocalStoreCollection(httpContext, (Folder)child, IsWritable, _store)) .ToList(); return Task.FromResult>(list); @@ -187,7 +189,7 @@ public Task UploadFromStreamAsync(IHttpContext httpContext, Strea public async Task CopyAsync(IStoreCollection destinationCollection, string name, bool overwrite, IHttpContext httpContext) { var instance = CloudManager.Instance(httpContext.Session.Principal.Identity); - var res = await instance.Copy(DirectoryInfo, destinationCollection.GetFullPath()); + var res = await instance.Copy(FolderWithDescendants, destinationCollection.GetFullPath()); return new StoreItemResult( res ? DavStatusCode.Created : DavStatusCode.InternalServerError); } @@ -275,7 +277,7 @@ public async Task DeleteItemAsync(string name, IHttpContext httpC return DavStatusCode.PreconditionFailed; // Determine the full path - var fullPath = WebDavPath.Combine(DirectoryInfo.FullPath, name); + var fullPath = WebDavPath.Combine(FolderWithDescendants.FullPath, name); try { var item = FindSubItem(name); @@ -296,18 +298,18 @@ public async Task DeleteItemAsync(string name, IHttpContext httpC } public InfiniteDepthMode InfiniteDepthMode => InfiniteDepthMode.Allowed; - public bool IsValid => !string.IsNullOrEmpty(DirectoryInfo?.FullPath); + public bool IsValid => !string.IsNullOrEmpty(FolderWithDescendants.FullPath); public override int GetHashCode() { - return DirectoryInfo.FullPath.GetHashCode(); + return FolderWithDescendants.FullPath.GetHashCode(); } public override bool Equals(object obj) { return obj is LocalStoreCollection storeCollection && - storeCollection.DirectoryInfo.FullPath.Equals(DirectoryInfo.FullPath, StringComparison.CurrentCultureIgnoreCase); + storeCollection.FolderWithDescendants.FullPath.Equals(FolderWithDescendants.FullPath, StringComparison.CurrentCultureIgnoreCase); } } -} \ No newline at end of file +} diff --git a/WebDavMailRuCloudStore/StoreBase/LocalStoreCollectionProps.cs b/WebDavMailRuCloudStore/StoreBase/LocalStoreCollectionProps.cs index 81557210..dfb11260 100644 --- a/WebDavMailRuCloudStore/StoreBase/LocalStoreCollectionProps.cs +++ b/WebDavMailRuCloudStore/StoreBase/LocalStoreCollectionProps.cs @@ -5,6 +5,7 @@ using NWebDav.Server; using NWebDav.Server.Props; using YaR.Clouds.WebDavStore.CustomProperties; +using YaR.Clouds.Base; namespace YaR.Clouds.WebDavStore.StoreBase { @@ -29,7 +30,7 @@ public LocalStoreCollectionProps(Func isEnabledPropFunc) new DavGetEtag { - Getter = (_, item) => item.CalculateEtag() + Getter = (_, item) => item.CalculateETag() }, //new DavBsiisreadonly @@ -62,41 +63,41 @@ public LocalStoreCollectionProps(Func isEnabledPropFunc) new DavQuotaUsedBytes { - Getter = (_, collection) => - collection.DirectoryInfo.Size + Getter = (_, collection) => + collection.FolderWithDescendants.Size //IsExpensive = true //folder listing performance }, // RFC-2518 properties new DavCreationDate { - Getter = (_, collection) => collection.DirectoryInfo.CreationTimeUtc, + Getter = (_, collection) => collection.FolderWithDescendants.CreationTimeUtc, Setter = (_, collection, value) => { - collection.DirectoryInfo.CreationTimeUtc = value; + (collection.FolderWithDescendants as Folder).CreationTimeUtc = value; return DavStatusCode.Ok; } }, new DavDisplayName { - Getter = (_, collection) => collection.DirectoryInfo.Name + Getter = (_, collection) => collection.FolderWithDescendants.Name }, new DavGetLastModified { - Getter = (_, collection) => collection.DirectoryInfo.LastWriteTimeUtc, + Getter = (_, collection) => (collection.FolderWithDescendants as Folder).LastWriteTimeUtc, Setter = (_, collection, value) => { - collection.DirectoryInfo.LastWriteTimeUtc = value; + (collection.FolderWithDescendants as Folder).LastWriteTimeUtc = value; return DavStatusCode.Ok; } }, new DavLastAccessed { - Getter = (_, collection) => collection.DirectoryInfo.LastWriteTimeUtc, + Getter = (_, collection) => (collection.FolderWithDescendants as Folder).LastWriteTimeUtc, Setter = (_, collection, value) => { - collection.DirectoryInfo.LastWriteTimeUtc = value; + (collection.FolderWithDescendants as Folder).LastWriteTimeUtc = value; return DavStatusCode.Ok; } }, @@ -121,14 +122,9 @@ public LocalStoreCollectionProps(Func isEnabledPropFunc) { Getter = (_, collection) => { - var info = collection.DirectoryInfo; - return (info.Folders.IsEmpty - ? (info.ServerFoldersCount ?? 0) - : info.Folders.Count) - + - (info.Files.IsEmpty - ? (info.ServerFilesCount ?? 0) - : info.Files.Count); + var folder = collection.FolderWithDescendants as Folder; + return Math.Max(collection.FolderWithDescendants.Descendants.Count, + (folder.ServerFoldersCount ?? 0) + (folder.ServerFilesCount ?? 0)); } }, new DavExtCollectionIsFolder @@ -146,7 +142,9 @@ public LocalStoreCollectionProps(Func isEnabledPropFunc) new DavExtCollectionHasSubs //Identifies whether this collection contains any collections which are folders (see "isfolder"). { - Getter = (_, collection) => !collection.DirectoryInfo.Folders.IsEmpty || collection.DirectoryInfo.ServerFoldersCount > 0 + Getter = (_, collection) + => ((collection.FolderWithDescendants as Folder)?.ServerFoldersCount ?? 0)> 0 + || collection.FolderWithDescendants.Descendants.Any(x=>x is Folder) }, new DavExtCollectionNoSubs //Identifies whether this collection allows child collections to be created. @@ -156,10 +154,9 @@ public LocalStoreCollectionProps(Func isEnabledPropFunc) new DavExtCollectionObjectCount //To count the number of non-folder resources in the collection. { - Getter = (_, collection) => - !collection.DirectoryInfo.Files.IsEmpty - ? collection.DirectoryInfo.Files.Count - : collection.DirectoryInfo.ServerFilesCount ?? 0 + Getter = (_, collection) => collection.FolderWithDescendants is Folder folder + ? Math.Max(folder.ServerFilesCount ?? 0, collection.FolderWithDescendants.Descendants.Count(x=>x.IsFile)) + : 0 }, new DavExtCollectionReserved @@ -169,52 +166,51 @@ public LocalStoreCollectionProps(Func isEnabledPropFunc) new DavExtCollectionVisibleCount //Counts the number of visible non-folder resources in the collection. { - Getter = (_, collection) => - !collection.DirectoryInfo.Files.IsEmpty - ? collection.DirectoryInfo.Files.Count - : collection.DirectoryInfo.ServerFilesCount ?? 0 + Getter = (_, collection) => collection.FolderWithDescendants is Folder folder + ? Math.Max(folder.ServerFilesCount ?? 0, collection.FolderWithDescendants.Descendants.Count(x=>x.IsFile)) + : 0 }, // Win32 extensions new Win32CreationTime { - Getter = (_, collection) => collection.DirectoryInfo.CreationTimeUtc, + Getter = (_, collection) => (collection.FolderWithDescendants as Folder).CreationTimeUtc, Setter = (_, collection, value) => { - collection.DirectoryInfo.CreationTimeUtc = value; + (collection.FolderWithDescendants as Folder).CreationTimeUtc = value; return DavStatusCode.Ok; } }, new Win32LastAccessTime { - Getter = (_, collection) => collection.DirectoryInfo.LastAccessTimeUtc, + Getter = (_, collection) => (collection.FolderWithDescendants as Folder).LastAccessTimeUtc, Setter = (_, collection, value) => { - collection.DirectoryInfo.LastAccessTimeUtc = value; + (collection.FolderWithDescendants as Folder).LastAccessTimeUtc = value; return DavStatusCode.Ok; } }, new Win32LastModifiedTime { - Getter = (_, collection) => collection.DirectoryInfo.LastWriteTimeUtc, + Getter = (_, collection) => (collection.FolderWithDescendants as Folder).LastWriteTimeUtc, Setter = (_, collection, value) => { - collection.DirectoryInfo.LastWriteTimeUtc = value; + (collection.FolderWithDescendants as Folder).LastWriteTimeUtc = value; return DavStatusCode.Ok; } }, new Win32FileAttributes { - Getter = (_, collection) => collection.DirectoryInfo.Attributes, + Getter = (_, collection) => (collection.FolderWithDescendants as Folder).Attributes, Setter = (_, collection, value) => { - collection.DirectoryInfo.Attributes = value; + (collection.FolderWithDescendants as Folder).Attributes = value; return DavStatusCode.Ok; } }, new DavGetContentLength { - Getter = (_, item) => item.DirectoryInfo.Size + Getter = (_, collection) => (collection.FolderWithDescendants as Folder).Size }, new DavGetContentType { @@ -222,7 +218,8 @@ public LocalStoreCollectionProps(Func isEnabledPropFunc) }, new DavSharedLink { - Getter = (_, item) => item.DirectoryInfo.PublicLinks.Values.FirstOrDefault()?.Uri.OriginalString ?? string.Empty, + Getter = (_, collection) => (collection.FolderWithDescendants as Folder) + .PublicLinks.Values.FirstOrDefault()?.Uri.OriginalString ?? string.Empty, Setter = (_, _, _) => DavStatusCode.Ok } }; @@ -231,6 +228,6 @@ public LocalStoreCollectionProps(Func isEnabledPropFunc) } public IEnumerable> Props => _props; - private readonly DavProperty[] _props; + private readonly DavProperty[] _props; } -} \ No newline at end of file +} diff --git a/WebDavMailRuCloudStore/StoreBase/LocalStoreItem.cs b/WebDavMailRuCloudStore/StoreBase/LocalStoreItem.cs index bd886090..a8aad79b 100644 --- a/WebDavMailRuCloudStore/StoreBase/LocalStoreItem.cs +++ b/WebDavMailRuCloudStore/StoreBase/LocalStoreItem.cs @@ -17,7 +17,7 @@ namespace YaR.Clouds.WebDavStore.StoreBase { - [DebuggerDisplay("{FileInfo.FullPath}")] + [DebuggerDisplay("{DebuggerDisplay,nq}")] public class LocalStoreItem : ILocalStoreItem { private static readonly ILogger Logger = LoggerFactory.Factory.CreateLogger(typeof(LocalStoreItem)); @@ -31,6 +31,8 @@ public class LocalStoreItem : ILocalStoreItem public long Length => FileInfo.Size; public bool IsReadable => true; + private string DebuggerDisplay => FileInfo.FullPath; + public LocalStoreItem(File fileInfo, bool isWritable, LocalStore store) { _store = store; From 6200afb2c7142785b83648ef2d9cb363510afcf6 Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Sun, 29 Oct 2023 20:59:35 +0300 Subject: [PATCH 40/77] =?UTF-8?q?=D0=98=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F:=201)=20=D0=9E=D1=82=D0=BF=D0=B0=D0=BB=D0=B0?= =?UTF-8?q?=20=D0=BD=D0=B5=D0=BE=D0=B1=D1=85=D0=BE=D0=B4=D0=B8=D0=BC=D0=BE?= =?UTF-8?q?=D1=81=D1=82=D1=8C=20=D0=B4=D0=B5=D1=80=D0=B6=D0=B0=D1=82=D1=8C?= =?UTF-8?q?=202=20=D1=8D=D0=BA=D0=B7=D0=B5=D0=BC=D0=BF=D0=BB=D1=8F=D1=80?= =?UTF-8?q?=D0=B0=20=D1=81=D0=B5=D1=80=D0=B2=D0=B8=D1=81=D0=B0=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=B4=20=D0=BA=D0=B0=D0=B6=D0=B4=D0=BE=D0=B5=20=D0=BE?= =?UTF-8?q?=D0=B1=D0=BB=D0=B0=D0=BA=D0=BE=20=E2=80=93=20=D1=86=D0=B5=D0=BB?= =?UTF-8?q?=D0=B5=D0=B2=D0=BE=D0=B5=20=D0=BE=D0=B1=D0=BB=D0=B0=D0=BA=D0=BE?= =?UTF-8?q?=20=D0=B8=20=D0=BF=D1=80=D0=BE=D1=82=D0=BE=D0=BA=D0=BE=D0=BB=20?= =?UTF-8?q?(=D0=B2=D0=BA=D0=BB=D1=8E=D1=87=D0=B0=D1=8F=20=D0=B0=D1=83?= =?UTF-8?q?=D1=82=D0=B5=D0=BD=D1=82=D0=B8=D1=84=D0=B8=D0=BA=D0=B0=D1=86?= =?UTF-8?q?=D0=B8=D1=8E=20=D1=87=D0=B5=D1=80=D0=B5=D0=B7=20=D0=B1=D1=80?= =?UTF-8?q?=D0=B0=D1=83=D0=B7=D0=B5=D1=80)=20=D0=B7=D0=B0=D0=B4=D0=B0?= =?UTF-8?q?=D1=8E=D1=82=D1=81=D1=8F=20=D0=B2=20=D1=81=D1=82=D1=80=D0=BE?= =?UTF-8?q?=D0=BA=D0=B5=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BD=D0=B0,=20=D0=B8?= =?UTF-8?q?=D0=B7-=D0=B7=D0=B0=20=D1=87=D0=B5=D0=B3=D0=BE=20=D1=81=D0=B8?= =?UTF-8?q?=D0=BD=D1=82=D0=B0=D0=BA=D1=81=D0=B8=D1=81=20=D1=81=D1=82=D1=80?= =?UTF-8?q?=D0=BE=D0=BA=D0=B8=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BD=D0=B0=20?= =?UTF-8?q?=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD=D0=B8=D0=BB=D1=81=D1=8F.=202)=20?= =?UTF-8?q?=D0=9F=D0=B0=D1=80=D0=B0=D0=BC=D0=B5=D1=82=D1=80=20=D0=BA=D0=BE?= =?UTF-8?q?=D0=BC=D0=B0=D0=BD=D0=B4=D0=BD=D0=BE=D0=B9=20=D1=81=D1=82=D1=80?= =?UTF-8?q?=D0=BE=D0=BA=D0=B8=20protocol=20=D1=81=D0=BE=D1=85=D1=80=D0=B0?= =?UTF-8?q?=D0=BD=D0=B5=D0=BD,=20=D0=BD=D0=BE=20=D0=BE=D0=BD=20=D0=BD?= =?UTF-8?q?=D0=B5=20=D1=8F=D0=B2=D0=BB=D1=8F=D0=B5=D1=82=D1=81=D1=8F=20?= =?UTF-8?q?=D0=BE=D0=BF=D1=80=D0=B5=D0=B4=D0=B5=D0=BB=D1=8F=D1=8E=D1=89?= =?UTF-8?q?=D0=B8=D0=BC,=20=D1=8F=D0=B2=D0=BB=D1=8F=D0=B5=D1=82=D1=81?= =?UTF-8?q?=D1=8F=20=D1=82=D0=BE=D0=BB=D1=8C=D0=BA=D0=BE=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=B4=D1=81=D0=BA=D0=B0=D0=B7=D0=BA=D0=BE=D0=B9,=20=D0=B5?= =?UTF-8?q?=D1=81=D0=BB=D0=B8=20=D0=BF=D0=BE=20=D0=B4=D1=80=D1=83=D0=B3?= =?UTF-8?q?=D0=B8=D0=BC=20=D0=BF=D1=80=D0=B8=D0=B7=D0=BD=D0=B0=D0=BA=D0=B0?= =?UTF-8?q?=D0=BC=20=D0=BE=D0=BF=D1=80=D0=B5=D0=B4=D0=B5=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=BD=D0=B5=20=D0=BF=D1=80=D0=BE=D0=B8=D0=B7?= =?UTF-8?q?=D0=BE=D1=88=D0=BB=D0=BE.=20=D0=9E=D0=B1=D0=BB=D0=B0=D0=BA?= =?UTF-8?q?=D0=BE=20=D0=BE=D0=BF=D1=80=D0=B5=D0=B4=D0=B5=D0=BB=D1=8F=D0=B5?= =?UTF-8?q?=D1=82=D1=81=D1=8F=20=D0=BF=D0=BE=20=D0=B4=D0=BE=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D1=83=20=D1=83=D1=87=D0=B5=D1=82=D0=BD=D0=BE=D0=B9=20?= =?UTF-8?q?=D0=B7=D0=B0=D0=BF=D0=B8=D1=81=D0=B8,=20=D0=BF=D1=80=D0=BE?= =?UTF-8?q?=D1=82=D0=BE=D0=BA=D0=BE=D0=BB=20=E2=80=93=20=D0=BB=D0=B8=D0=B1?= =?UTF-8?q?=D0=BE=20WebM1Bin,=20=D0=BA=D0=BE=D1=82=D0=BE=D1=80=D1=8B=D0=B9?= =?UTF-8?q?=20=D0=BD=D0=B5=20deprecated,=20=D0=BB=D0=B8=D0=B1=D0=BE=20?= =?UTF-8?q?=D0=BE=D0=B4=D0=B8=D0=BD=20=D0=B8=D0=B7=20YadWeb/YadWebV2=20?= =?UTF-8?q?=D0=BF=D0=BE=20=D0=B4=D1=80=D1=83=D0=B3=D0=B8=D0=BC=20=D0=BF?= =?UTF-8?q?=D1=80=D0=B8=D0=B7=D0=BD=D0=B0=D0=BA=D0=B0=D0=BC.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MailRuCloud/MailRuCloudApi/Base/Account.cs | 61 ----- MailRuCloud/MailRuCloudApi/Base/CloudType.cs | 8 + .../MailRuCloudApi/Base/Credentials.cs | 241 ++++++++++++++++-- MailRuCloud/MailRuCloudApi/Base/File.cs | 2 +- MailRuCloud/MailRuCloudApi/Base/Protocol.cs | 30 ++- .../Mobile/Requests/OAuthRequest.cs | 6 +- .../Base/Repos/MailRuCloud/OAuth.cs | 6 +- .../MailRuCloud/WebBin/WebBinRequestRepo.cs | 6 +- .../MailRuCloud/WebM1/WebM1RequestRepo.cs | 1 + .../Base/Repos/MailRuCloud/WebV2/WebAuth.cs | 10 +- .../MailRuCloud/WebV2/WebV2RequestRepo.cs | 1 + .../MailRuCloudApi/Base/Repos/RepoFabric.cs | 2 +- .../Repos/YandexDisk/YadWeb/YadWebAuth.cs | 4 +- .../Repos/YandexDisk/YadWebV2/YadWebAuth.cs | 39 ++- .../Base/Streams/HttpClientFabric.cs | 43 ++-- .../Base/Streams/UploadStreamHttpClient.cs | 6 +- .../Base/Streams/UploadStreamHttpClientV2.cs | 4 +- .../Streams/UploadStreamHttpWebRequest.cs | 4 +- .../Streams/UploadStreamHttpWebRequestV2.cs | 4 +- MailRuCloud/MailRuCloudApi/Cloud.cs | 70 ++--- MailRuCloud/MailRuCloudApi/CloudSettings.cs | 2 +- .../MailRuCloudApi/Links/LinkManager.cs | 6 +- .../Commands/CryptPasswdCommand.cs | 2 +- .../SpecialCommands/Commands/FishCommand.cs | 3 +- .../SpecialCommands/Commands/ListCommand.cs | 5 +- .../Commands/LocalToServerCopyCommand.cs | 4 +- .../Commands/SharedFolderLinkCommand.cs | 2 +- .../Streams/DownloadStreamFabric.cs | 6 +- .../Streams/SplittedUploadStream.cs | 4 +- .../Streams/UploadStreamFabric.cs | 4 +- WDMRC.Console/CommandLineOptions.cs | 2 +- WDMRC.Console/Payload.cs | 2 +- WDMRC.Console/Program.cs | 4 +- WDMRC.Console/WDMRC.Console.csproj | 8 +- WebDavMailRuCloudStore/CloudManager.cs | 51 +++- YandexAuthBrowser/ResidentForm.cs | 45 +++- readme.md | 2 +- 37 files changed, 471 insertions(+), 229 deletions(-) delete mode 100644 MailRuCloud/MailRuCloudApi/Base/Account.cs create mode 100644 MailRuCloud/MailRuCloudApi/Base/CloudType.cs diff --git a/MailRuCloud/MailRuCloudApi/Base/Account.cs b/MailRuCloud/MailRuCloudApi/Base/Account.cs deleted file mode 100644 index ef5fc47d..00000000 --- a/MailRuCloud/MailRuCloudApi/Base/Account.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System.Net; -using System.Threading.Tasks; -using YaR.Clouds.Base.Repos; -using YaR.Clouds.Base.Requests.Types; - -namespace YaR.Clouds.Base -{ - //TODO: refact, maybe we don't need this class - //TODO: refact, Requestrepo - wrong place? - public class Account - { - /// - /// Initializes a new instance of the class. - /// - public Account(CloudSettings settings, Credentials credentials) - { - Credentials = credentials; - - WebRequest.DefaultWebProxy.Credentials = CredentialCache.DefaultCredentials; - - RequestRepo = new RepoFabric(settings, credentials) - .Create(); - } - - internal IRequestRepo RequestRepo { get; } - - /// - /// Gets account cookies. - /// - /// Account cookies. - //public CookieContainer Cookies => _cookies ?? (_cookies = new CookieContainer()); - - internal Credentials Credentials { get; } - - public AccountInfoResult Info { get; private set; } - - public bool IsAnonymous => Credentials.IsAnonymous; - /// - /// Authorize on MAIL.RU server. - /// - /// True or false result operation. - public bool Login() - { - return LoginAsync().Result; - } - - /// - /// Async call to authorize on MAIL.RU server. - /// - /// True or false result operation. - public async Task LoginAsync() - { - if (!IsAnonymous) - Info = await RequestRepo.AccountInfo(); - return true; - } - - } - - public delegate string AuthCodeRequiredDelegate(string login, bool isAutoRelogin); -} diff --git a/MailRuCloud/MailRuCloudApi/Base/CloudType.cs b/MailRuCloud/MailRuCloudApi/Base/CloudType.cs new file mode 100644 index 00000000..5a00bf8f --- /dev/null +++ b/MailRuCloud/MailRuCloudApi/Base/CloudType.cs @@ -0,0 +1,8 @@ +namespace YaR.Clouds.Base; + +public enum CloudType +{ + Unkown = 0, + Mail, + Yandex, +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Credentials.cs b/MailRuCloud/MailRuCloudApi/Base/Credentials.cs index 2ee0348f..9e13da40 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Credentials.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Credentials.cs @@ -1,6 +1,8 @@ using System; using System.Linq; using System.Security.Authentication; +using System.Text.RegularExpressions; +using YaR.Clouds.Base.Repos.MailRuCloud; namespace YaR.Clouds.Base { @@ -8,60 +10,243 @@ public class Credentials : IBasicCredentials { private static readonly string[] AnonymousLogins = { "anonymous", "anon", "anonym", string.Empty }; + // протокол # логин # разделитель + private static Regex _loginRegex1 = new Regex("^([^#]*)#([^#]*)#([^#]*)$", RegexOptions.IgnoreCase | RegexOptions.Compiled); + // протокол # логин + private static Regex _loginRegex2 = new Regex( + "^(1|2|WebM1Bin|WebV2|YadWeb|YadWebV2)#([^#]*)$", + RegexOptions.IgnoreCase | RegexOptions.Compiled); + // логин # разделитель + private static Regex _loginRegex3 = new Regex("^([^#]*)#([^#]*)$", RegexOptions.IgnoreCase | RegexOptions.Compiled); + public Credentials(string login, string password) { if (string.IsNullOrWhiteSpace(login)) - login = string.Empty; // throw new InvalidCredentialException("Login is null or empty."); + login = string.Empty; if (AnonymousLogins.Contains(login)) { IsAnonymous = true; Login = login; Password = string.Empty; + PasswordCrypt = string.Empty; + CloudType = StringToCloud(Login); + Protocol = Protocol.Autodetect; return; } - if (string.IsNullOrEmpty(password)) - throw new ArgumentException("Password is null or empty."); + /* + * Login ожидается в форматах: + * John - здесь John - учетная запись в облаке без указания mail или yandex + * John#SEP - здесь SEP - это разделитель для строки пароля + * John@yandex.com - здесь John@yandex.com - учетная запись в облаке с указанием mail или yandex + * John@yandex.com#SEP - указано облако и разделитель для строки пароля + * Выше в форматах не задан протокол, поэтому принимается значение Autodetect + * 1#John - учетная запись без указания mail или yandex с указанием версии протокола - не допустимо + * WebM1Bin#John - учетная запись без указания mail или yandex с указанием версии протокола + * WebM1Bin#John#SEP - учетная запись без указания mail или yandex с указанием версии протокола и разделителя + * 1#John@mail.ru - учетная запись с указанием mail или yandex с указанием версии протокола + * 1#John@mail.ru#SEP - учетная запись с указанием mail или yandex с указанием версии протокола и разделителя строки пароля + * WebM1Bin#John@yandex.ru - учетная запись облака yandex с указанием несовместимой версии протокола - не допустимо + */ + + // протокол # логин # разделитель + Match m = _loginRegex1.Match(login); + if (m.Success) + { + if (string.IsNullOrEmpty(m.Groups[1].Value)) + { + throw new InvalidCredentialException("Invalid credential format: " + + "login doesn't have protocol part. See manuals."); + } + if (string.IsNullOrEmpty(m.Groups[2].Value)) + { + throw new InvalidCredentialException("Invalid credential format: " + + "login doesn't have email part. See manuals."); + } + if (string.IsNullOrEmpty(m.Groups[3].Value)) + { + throw new InvalidCredentialException("Invalid credential format: " + + "login doesn't have encryption part. See manuals."); + } + + (Login, Password, PasswordCrypt) = StringToLoginPassword(m.Groups[2].Value, m.Groups[3].Value, password); + CloudType = StringToCloud(Login); + Protocol = StringToProtocol(m.Groups[1].Value, CloudType); + } + else + { + // протокол # логин + m = _loginRegex2.Match(login); + if (m.Success) + { + if (string.IsNullOrEmpty(m.Groups[1].Value)) + { + throw new InvalidCredentialException("Invalid credential format: " + + "login doesn't have protocol part. See manuals."); + } + if (string.IsNullOrEmpty(m.Groups[2].Value)) + { + throw new InvalidCredentialException("Invalid credential format: " + + "login doesn't have email part. See manuals."); + } + + (Login, Password, PasswordCrypt) = StringToLoginPassword(m.Groups[2].Value, null, password); + CloudType = StringToCloud(Login); + Protocol = StringToProtocol(m.Groups[1].Value, CloudType); + } + else + { + // логин # разделитель + m = _loginRegex3.Match(login); + if (m.Success) + { + if (string.IsNullOrEmpty(m.Groups[1].Value)) + { + throw new InvalidCredentialException("Invalid credential format: " + + "login doesn't have email part. See manuals."); + } + if (string.IsNullOrEmpty(m.Groups[2].Value)) + { + throw new InvalidCredentialException("Invalid credential format: " + + "login doesn't have encryption part. See manuals."); + } + + (Login, Password, PasswordCrypt) = StringToLoginPassword(m.Groups[1].Value, m.Groups[2].Value, password); + CloudType = StringToCloud(Login); + Protocol = CloudType == CloudType.Mail + // Т.к. протокол WebV2 отмечен как deprecated, всегда автоматически выбирается WebM1Bin + ? Protocol = Protocol.WebM1Bin + : CloudType == CloudType.Yandex && string.IsNullOrWhiteSpace(Password) + ? Protocol.YadWebV2 + : Protocol.Autodetect; + } + else + { + (Login, Password, PasswordCrypt) = StringToLoginPassword(login, null, password); + CloudType = StringToCloud(Login); + Protocol = Protocol.Autodetect; + } + } + } + } + public bool IsAnonymous { get; set; } - int slashpos = login.IndexOf(@"#", StringComparison.InvariantCulture); - if (slashpos == login.Length - 1) - throw new InvalidCredentialException("Invalid credential format."); + public Protocol Protocol { get; set; } = Protocol.Autodetect; + public CloudType CloudType { get; set; } - if (slashpos < 0) - { - Login = login; - Password = password; - PasswordCrypt = string.Empty; - return; - } + public string Login { get; } + public string Password { get; } - Login = login.Substring(0, slashpos); + public string PasswordCrypt { get; set; } - string separator = login.Substring(slashpos + 1); + public bool CanCrypt => !string.IsNullOrEmpty(PasswordCrypt); - int seppos = password.IndexOf(separator, StringComparison.InvariantCulture); - if (seppos < 0) - throw new InvalidCredentialException("Invalid credential format."); + private CloudType StringToCloud(string login) + { + foreach (var domain in MailRuBaseRepo.AvailDomains) + { +#if NET48 + bool hasMail = System.Globalization.CultureInfo.CurrentCulture.CompareInfo + .IndexOf(login, string.Concat("@", domain, "."), System.Globalization.CompareOptions.OrdinalIgnoreCase) >= 0; +#else + bool hasMail = login.Contains(string.Concat("@", domain, "."), StringComparison.InvariantCultureIgnoreCase); +#endif + if (hasMail) + return CloudType.Mail; + } - Password = password.Substring(0, seppos); - if (seppos + separator.Length >= password.Length) - throw new InvalidCredentialException("Invalid credential format."); +#if NET48 + bool hasYandex = System.Globalization.CultureInfo.CurrentCulture.CompareInfo + .IndexOf(login, "@yandex.", System.Globalization.CompareOptions.OrdinalIgnoreCase) >= 0; +#else + bool hasYandex = login.Contains("@yandex.", StringComparison.InvariantCultureIgnoreCase); +#endif + if (hasYandex) + return CloudType.Yandex; - PasswordCrypt = password.Substring(seppos + separator.Length); + return CloudType.Unkown; } - public bool IsAnonymous { get; set; } + private Protocol StringToProtocol(string protocol, CloudType cloud) + { + switch (protocol) + { + case "1": + if (cloud == CloudType.Mail) + return Protocol.WebM1Bin; + if (cloud == CloudType.Yandex) + return Protocol.YadWeb; + break; + + case "2": + if (cloud == CloudType.Mail) + return Protocol.WebV2; + if (cloud == CloudType.Yandex) + return Protocol.YadWebV2; + break; + + case "WebM1Bin": + if (cloud == CloudType.Mail) + return Protocol.WebM1Bin; + if (cloud == CloudType.Yandex) + throw new InvalidCredentialException("Invalid credential format: " + + "protocol version isn't compatible with cloud specified by login. See manuals."); + break; + + case "WebV2": + if (cloud == CloudType.Mail) + return Protocol.WebV2; + if (cloud == CloudType.Yandex) + throw new InvalidCredentialException("Invalid credential format: " + + "protocol version isn't compatible with cloud specified by login. See manuals."); + break; + + case "YadWeb": + if (cloud == CloudType.Yandex) + return Protocol.YadWeb; + if (cloud == CloudType.Mail) + throw new InvalidCredentialException("Invalid credential format: " + + "protocol version isn't compatible with cloud specified by login. See manuals."); + break; + + case "YadWebV2": + if (cloud == CloudType.Yandex) + return Protocol.YadWebV2; + if (cloud == CloudType.Mail) + throw new InvalidCredentialException("Invalid credential format: " + + "protocol version isn't compatible with cloud specified by login. See manuals."); + break; + + default: + throw new InvalidCredentialException("Invalid credential format: " + + "unknown protocol. See manuals."); + }; + + throw new InvalidCredentialException("Invalid credential format: " + + "protocol version needs fully qualified login using email format. See manuals."); + } + private (string login, string password, string encPassword) StringToLoginPassword( + string loginPart, string separatorPart, string passwordPart) + { + if (string.IsNullOrEmpty(separatorPart)) + return (loginPart, passwordPart, string.Empty); - public string Login { get; } - public string Password { get; } + int sepPos = passwordPart.IndexOf(separatorPart, StringComparison.InvariantCulture /* case sensitive! */); + if (sepPos < 0) + throw new InvalidCredentialException("Invalid credential format: " + + "password doesn't contain encryption part. See manuals."); - public string PasswordCrypt { get; set; } + string password = passwordPart.Substring(0, sepPos); + if (sepPos + separatorPart.Length >= passwordPart.Length) + throw new InvalidCredentialException("Invalid credential format."); - public bool CanCrypt => !string.IsNullOrEmpty(PasswordCrypt); + string passwordCrypt = passwordPart.Substring(sepPos + separatorPart.Length); + return (loginPart, password, passwordCrypt); + } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/File.cs b/MailRuCloud/MailRuCloudApi/Base/File.cs index 2b10e546..86ff6284 100644 --- a/MailRuCloud/MailRuCloudApi/Base/File.cs +++ b/MailRuCloud/MailRuCloudApi/Base/File.cs @@ -238,7 +238,7 @@ public PublishInfo ToPublishInfo(Cloud cloud, bool generateDirectVideoLink, Shar private static string ConvertToVideoLink(Cloud cloud, Uri publicLink, SharedVideoResolution videoResolution) { - return cloud.Account.RequestRepo.ConvertToVideoLink(publicLink, videoResolution); + return cloud.RequestRepo.ConvertToVideoLink(publicLink, videoResolution); // GetShardInfo(ShardType.WeblinkVideo).Result.Url + diff --git a/MailRuCloud/MailRuCloudApi/Base/Protocol.cs b/MailRuCloud/MailRuCloudApi/Base/Protocol.cs index 656b238b..ece03ca5 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Protocol.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Protocol.cs @@ -1,10 +1,22 @@ -namespace YaR.Clouds.Base +namespace YaR.Clouds.Base; + +public enum Protocol { - public enum Protocol - { - WebM1Bin, - WebV2, - YadWeb, - YadWebV2 - } -} \ No newline at end of file + Autodetect = 0, + /// + /// (Cloud.Mail.Ru) mix of mobile and DiskO protocols + /// + WebM1Bin, + /// + /// (Cloud.Mail.Ru) [deprecated] desktop browser protocol + /// + WebV2, + /// + /// (Yandex.Disk) desktop browser protocol + /// + YadWeb, + /// + /// (Yandex.Disk) desktop browser protocol with browser authentication + /// + YadWebV2 +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/OAuthRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/OAuthRequest.cs index 200457fb..e2b49a8f 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/OAuthRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/OAuthRequest.cs @@ -10,10 +10,10 @@ class OAuthRequest: BaseRequestJson private readonly string _login; private readonly string _password; - public OAuthRequest(HttpCommonSettings settings, IBasicCredentials creds) : base(settings, null) + public OAuthRequest(HttpCommonSettings settings, IBasicCredentials credentials) : base(settings, null) { - _login = creds.Login; - _password = creds.Password; + _login = credentials.Login; + _password = credentials.Password; } protected override string RelationalUri => "https://o2.mail.ru/token"; diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/OAuth.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/OAuth.cs index 8eae3307..58cbcaa5 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/OAuth.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/OAuth.cs @@ -6,6 +6,8 @@ using YaR.Clouds.Base.Requests; using YaR.Clouds.Base.Requests.Types; using YaR.Clouds.Common; +using static YaR.Clouds.Cloud; + namespace YaR.Clouds.Base.Repos.MailRuCloud { @@ -20,11 +22,11 @@ internal class OAuth : IAuth private readonly AuthCodeRequiredDelegate _onAuthCodeRequired; public OAuth(SemaphoreSlim connectionLimiter, - HttpCommonSettings settings, IBasicCredentials creds, AuthCodeRequiredDelegate onAuthCodeRequired) + HttpCommonSettings settings, IBasicCredentials credentials, AuthCodeRequiredDelegate onAuthCodeRequired) { _settings = settings; _connectionLimiter = connectionLimiter; - _creds = creds; + _creds = credentials; _onAuthCodeRequired = onAuthCodeRequired; Cookies = new CookieContainer(); diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebBin/WebBinRequestRepo.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebBin/WebBinRequestRepo.cs index 8483ce1b..19c406ff 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebBin/WebBinRequestRepo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebBin/WebBinRequestRepo.cs @@ -4,6 +4,7 @@ using System.IO; using System.Linq; using System.Net; +using System.Threading; using System.Threading.Tasks; using YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests; @@ -19,8 +20,7 @@ using AccountInfoRequest = YaR.Clouds.Base.Repos.MailRuCloud.WebM1.Requests.AccountInfoRequest; using CreateFolderRequest = YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests.CreateFolderRequest; using MoveRequest = YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests.MoveRequest; -using System.Threading; -using YaR.Clouds.Extensions; +using static YaR.Clouds.Cloud; namespace YaR.Clouds.Base.Repos.MailRuCloud.WebBin { @@ -56,6 +56,7 @@ public WebBinRequestRepo(CloudSettings settings, IBasicCredentials credentials, HttpSettings.Proxy = settings.Proxy; _onAuthCodeRequired = onAuthCodeRequired; + Authenticator = new OAuth(_connectionLimiter, HttpSettings, credentials, onAuthCodeRequired); ShardManager = new ShardManager(_connectionLimiter, this); @@ -64,7 +65,6 @@ public WebBinRequestRepo(CloudSettings settings, IBasicCredentials credentials, // required for Windows 7 breaking connection ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13 | SecurityProtocolType.Tls12; - Authenticator = new OAuth(_connectionLimiter, HttpSettings, credentials, onAuthCodeRequired); } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/WebM1RequestRepo.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/WebM1RequestRepo.cs index 8a20d14f..51a355ad 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/WebM1RequestRepo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/WebM1RequestRepo.cs @@ -14,6 +14,7 @@ using YaR.Clouds.Common; using CreateFolderRequest = YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests.CreateFolderRequest; using MoveRequest = YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests.MoveRequest; +using static YaR.Clouds.Cloud; namespace YaR.Clouds.Base.Repos.MailRuCloud.WebM1 { diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/WebAuth.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/WebAuth.cs index 8b0f97a7..b8f28f0e 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/WebAuth.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/WebAuth.cs @@ -7,6 +7,7 @@ using YaR.Clouds.Base.Requests; using YaR.Clouds.Base.Requests.Types; using YaR.Clouds.Common; +using static YaR.Clouds.Cloud; namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2 { @@ -20,22 +21,23 @@ class WebAuth : IAuth private readonly HttpCommonSettings _settings; private readonly IBasicCredentials _creds; - public WebAuth(SemaphoreSlim connectionLimiter, HttpCommonSettings settings, IBasicCredentials creds, AuthCodeRequiredDelegate onAuthCodeRequired) + public WebAuth(SemaphoreSlim connectionLimiter, HttpCommonSettings settings, + IBasicCredentials credentials, AuthCodeRequiredDelegate onAuthCodeRequired) { _connectionLimiter = connectionLimiter; _settings = settings; - _creds = creds; + _creds = credentials; Cookies = new CookieContainer(); var logged = MakeLogin(connectionLimiter, onAuthCodeRequired).Result; if (!logged) - throw new AuthenticationException($"Cannot log in {creds.Login}"); + throw new AuthenticationException($"Cannot log in {credentials.Login}"); _authToken = new Cached(_ => { Logger.Debug("AuthToken expired, refreshing."); - if (creds.IsAnonymous) + if (credentials.IsAnonymous) return null; var token = Auth(connectionLimiter).Result; diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/WebV2RequestRepo.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/WebV2RequestRepo.cs index 92b5b927..725bcc44 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/WebV2RequestRepo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/WebV2RequestRepo.cs @@ -10,6 +10,7 @@ using YaR.Clouds.Base.Requests.Types; using YaR.Clouds.Base.Streams; using YaR.Clouds.Common; +using static YaR.Clouds.Cloud; namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2 { diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/RepoFabric.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/RepoFabric.cs index 2f4364f2..b7f06fe3 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/RepoFabric.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/RepoFabric.cs @@ -27,7 +27,7 @@ string TwoFaHandler(string login, bool isAutoRelogin) return code; } - IRequestRepo repo = _settings.Protocol switch + IRequestRepo repo = _credentials.Protocol switch { Protocol.YadWebV2 => new YandexDisk.YadWebV2.YadWebRequestRepo(_settings, _settings.Proxy, _credentials), Protocol.YadWeb => new YandexDisk.YadWeb.YadWebRequestRepo(_settings, _settings.Proxy, _credentials), diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebAuth.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebAuth.cs index 92f82955..e35c05e7 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebAuth.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebAuth.cs @@ -11,10 +11,10 @@ namespace YaR.Clouds.Base.Repos.YandexDisk.YadWeb { class YadWebAuth : IAuth { - public YadWebAuth(SemaphoreSlim connectionLimiter, HttpCommonSettings settings, IBasicCredentials creds) + public YadWebAuth(SemaphoreSlim connectionLimiter, HttpCommonSettings settings, IBasicCredentials credentials) { _settings = settings; - _creds = creds; + _creds = credentials; Cookies = new CookieContainer(); var _ = MakeLogin(connectionLimiter).Result; diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/YadWebAuth.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/YadWebAuth.cs index 6ccc0ca7..5322ff2d 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/YadWebAuth.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/YadWebAuth.cs @@ -54,7 +54,7 @@ public YadWebAuth(SemaphoreSlim connectionLimiter, HttpCommonSettings settings, DiskSk = testAuthenticator.DiskSk; Uuid = testAuthenticator.Uuid; doRegularLogin = false; - Logger.Info($"Authentication refreshed using cached cookie"); + Logger.Info($"Browser authentication refreshed using cached cookie"); } } catch (Exception) @@ -74,8 +74,32 @@ public YadWebAuth(SemaphoreSlim connectionLimiter, HttpCommonSettings settings, if (doRegularLogin) { - MakeLogin().Wait(); - Logger.Info($"Authentication successful"); + try + { + MakeLogin().Wait(); + } + catch (AggregateException aex) when (aex.InnerException is HttpRequestException ex) + { + Logger.Error("Browser authentication failed! " + + "Please check browser authentication component is running!"); + + throw new InvalidCredentialException("Browser authentication failed! Browser component is not running!"); + } + catch (AggregateException aex) when (aex.InnerException is AuthenticationException ex) + { + string txt = string.Concat("Browser authentication failed! ", ex.Message); + Logger.Error(txt); + + throw new InvalidCredentialException(txt); + } + catch (Exception ex) + { + Logger.Error("Browser authentication failed! " + + "Check the URL and the password for browser authentication component!"); + + throw new InvalidCredentialException("Browser authentication failed!"); + } + Logger.Info($"Browser authentication successful"); } } @@ -167,6 +191,9 @@ public async Task MakeLogin() public string Login => _creds.Login; public string Password => _creds.Password; public string DiskSk { get; set; } + /// + /// yandexuid + /// public string Uuid { get; set; } public bool IsAnonymous => false; @@ -186,6 +213,9 @@ public class BrowserAppResponse [JsonProperty("Login")] public string Login { get; set; } + /// + /// yandexuid + /// [JsonProperty("Uuid")] public string Uuid { get; set; } @@ -224,9 +254,6 @@ private static string GetNameOnly(string value) private async Task<(BrowserAppResponse, string)> ConnectToBrowserApp() { - // Login для подключения содержит название логина Диска, не email, затем | и номер порта программы с браузером. - // Password - пароль в программе с браузером. - string url = _settings.CloudSettings.BrowserAuthenticatorUrl; string password = string.IsNullOrWhiteSpace(Password) ? _settings.CloudSettings.BrowserAuthenticatorPassword diff --git a/MailRuCloud/MailRuCloudApi/Base/Streams/HttpClientFabric.cs b/MailRuCloud/MailRuCloudApi/Base/Streams/HttpClientFabric.cs index e03c7156..e1bd81d6 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Streams/HttpClientFabric.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Streams/HttpClientFabric.cs @@ -2,32 +2,31 @@ using System.Net.Http; using System.Threading; -namespace YaR.Clouds.Base.Streams +namespace YaR.Clouds.Base.Streams; + +class HttpClientFabric { - class HttpClientFabric - { - public static HttpClientFabric Instance => _instance ??= new HttpClientFabric(); - private static HttpClientFabric _instance; + public static HttpClientFabric Instance => _instance ??= new HttpClientFabric(); + private static HttpClientFabric _instance; - public HttpClient this[Account account] + public HttpClient this[Cloud cloud] + { + get { - get + var cli = _lockDict.GetOrAdd(cloud.Credentials, new HttpClient(new HttpClientHandler { - var cli = _lockDict.GetOrAdd(account, new HttpClient(new HttpClientHandler - { - UseProxy = true, - Proxy = account.RequestRepo.HttpSettings.Proxy, - CookieContainer = account.RequestRepo.Authenticator.Cookies, - UseCookies = true, - AllowAutoRedirect = true, - MaxConnectionsPerServer = int.MaxValue - }) - {Timeout = Timeout.InfiniteTimeSpan}); + UseProxy = true, + Proxy = cloud.RequestRepo.HttpSettings.Proxy, + CookieContainer = cloud.RequestRepo.Authenticator.Cookies, + UseCookies = true, + AllowAutoRedirect = true, + MaxConnectionsPerServer = int.MaxValue + }) + { Timeout = Timeout.InfiniteTimeSpan }); - return cli; - } + return cli; } - - private readonly ConcurrentDictionary _lockDict = new(); } -} \ No newline at end of file + + private readonly ConcurrentDictionary _lockDict = new(); +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Streams/UploadStreamHttpClient.cs b/MailRuCloud/MailRuCloudApi/Base/Streams/UploadStreamHttpClient.cs index 5fbd2def..c19c3500 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Streams/UploadStreamHttpClient.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Streams/UploadStreamHttpClient.cs @@ -114,7 +114,7 @@ private void UploadFull(Stream sourceStream, bool doInvokeFileStreamSent = true) } }); - var client = HttpClientFabric.Instance[_cloud.Account]; + var client = HttpClientFabric.Instance[_cloud]; var uploadFileResult = Repo.DoUpload(client, pushContent, _file).Result; @@ -122,7 +122,7 @@ private void UploadFull(Stream sourceStream, bool doInvokeFileStreamSent = true) uploadFileResult.HttpStatusCode != HttpStatusCode.OK) throw new Exception("Cannot upload file, status " + uploadFileResult.HttpStatusCode); - // 2020-10-26 mairu does not return file size now + // 2020-10-26 mail.ru does not return file size now //if (uploadFileResult.HasReturnedData && _file.OriginalSize != uploadFileResult.Size) // throw new Exception("Local and remote file size does not match"); @@ -176,7 +176,7 @@ protected override void Dispose(bool disposing) private readonly Cloud _cloud; private readonly File _file; - private IRequestRepo Repo => _cloud.Account.RequestRepo; + private IRequestRepo Repo => _cloud.RequestRepo; private readonly ICloudHasher _cloudFileHasher; private Task _uploadTask; private readonly RingBufferedStream _ringBuffer = new(65536); diff --git a/MailRuCloud/MailRuCloudApi/Base/Streams/UploadStreamHttpClientV2.cs b/MailRuCloud/MailRuCloudApi/Base/Streams/UploadStreamHttpClientV2.cs index 4780a91b..767d699b 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Streams/UploadStreamHttpClientV2.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Streams/UploadStreamHttpClientV2.cs @@ -29,7 +29,7 @@ // // { // // try // // { -// // var shard = _cloud.Account.RequestRepo.GetShardInfo(ShardType.Upload).Result; +// // var shard = _cloud.RequestRepo.GetShardInfo(ShardType.Upload).Result; // // var url = new Uri($"{shard.Url}?cloud_domain=2&{_cloud.Account.Credentials.Login}"); // // _client = HttpClientFabric.Instance[_cloud.Account]; @@ -45,7 +45,7 @@ // // _request.Headers.Add("Host", url.Host); // // _request.Headers.Add("Accept", "*/*"); -// // _request.Headers.TryAddWithoutValidation("User-Agent", _cloud.Account.RequestRepo.HttpSettings.UserAgent); +// // _request.Headers.TryAddWithoutValidation("User-Agent", _cloud.RequestRepo.HttpSettings.UserAgent); // // var guid = Guid.NewGuid(); // // var content = new MultipartFormDataContent($"----{guid}"); diff --git a/MailRuCloud/MailRuCloudApi/Base/Streams/UploadStreamHttpWebRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Streams/UploadStreamHttpWebRequest.cs index 12275270..2e2c6e55 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Streams/UploadStreamHttpWebRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Streams/UploadStreamHttpWebRequest.cs @@ -40,8 +40,8 @@ // return null; // } -// //var shard = _cloud.Account.RequestRepo.GetShardInfo(ShardType.Upload).Result; -// _request = _cloud.Account.RequestRepo.UploadRequest(_file, null); +// //var shard = _cloud.RequestRepo.GetShardInfo(ShardType.Upload).Result; +// _request = _cloud.RequestRepo.UploadRequest(_file, null); // Logger.Debug($"HTTP:{_request.Method}:{_request.RequestUri.AbsoluteUri}"); diff --git a/MailRuCloud/MailRuCloudApi/Base/Streams/UploadStreamHttpWebRequestV2.cs b/MailRuCloud/MailRuCloudApi/Base/Streams/UploadStreamHttpWebRequestV2.cs index 8f90b75b..a313de77 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Streams/UploadStreamHttpWebRequestV2.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Streams/UploadStreamHttpWebRequestV2.cs @@ -28,8 +28,8 @@ // try // { // var boundary = new UploadMultipartBoundary(_file); -// //var shard = _cloud.Account.RequestRepo.GetShardInfo(ShardType.Upload).Result; -// _request = _cloud.Account.RequestRepo.UploadRequest(_file, boundary); +// //var shard = _cloud.RequestRepo.GetShardInfo(ShardType.Upload).Result; +// _request = _cloud.RequestRepo.UploadRequest(_file, boundary); // Logger.Debug($"HTTP:{_request.Method}:{_request.RequestUri.AbsoluteUri}"); diff --git a/MailRuCloud/MailRuCloudApi/Cloud.cs b/MailRuCloud/MailRuCloudApi/Cloud.cs index ed8e4bbc..df470ee4 100644 --- a/MailRuCloud/MailRuCloudApi/Cloud.cs +++ b/MailRuCloud/MailRuCloudApi/Cloud.cs @@ -30,6 +30,8 @@ public class Cloud : IDisposable { private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(Cloud)); + public delegate string AuthCodeRequiredDelegate(string login, bool isAutoRelogin); + public LinkManager LinkManager { get; } /// @@ -39,13 +41,6 @@ public class Cloud : IDisposable public CloudSettings Settings { get; } - /// - /// Gets or sets account to connect with cloud. - /// - /// Account info. - public Account Account { get; } - - ///// ///// Caching files for multiple small reads ///// @@ -56,19 +51,28 @@ public class Cloud : IDisposable /// private readonly EntryCache _entryCache; + internal IRequestRepo RequestRepo { get; } + internal Credentials Credentials { get; } + public AccountInfoResult AccountInfo { get; private set; } = null; + + /// /// Initializes a new instance of the class. /// public Cloud(CloudSettings settings, Credentials credentials) { Settings = settings; - Account = new Account(settings, credentials); - if (!Account.Login()) + WebRequest.DefaultWebProxy.Credentials = CredentialCache.DefaultCredentials; + Credentials = credentials; + RequestRepo = new RepoFabric(settings, credentials).Create(); + + if (!Credentials.IsAnonymous) { - throw new AuthenticationException("Auth token hasn't been retrieved."); + AccountInfo = RequestRepo.AccountInfo().Result + ?? throw new AuthenticationException("Auth token hasn't been retrieved."); } - _entryCache = new EntryCache(TimeSpan.FromSeconds(settings.CacheListingSec), Account.RequestRepo.ActiveOperationsAsync); + _entryCache = new EntryCache(TimeSpan.FromSeconds(settings.CacheListingSec), RequestRepo.ActiveOperationsAsync); ////TODO: wow very dummy linking, refact cache realization globally! //_itemCache = new ItemCache(TimeSpan.FromSeconds(settings.CacheListingSec)); @@ -88,7 +92,7 @@ public enum ItemType public virtual async Task GetPublicItemAsync(Uri url, ItemType itemType = ItemType.Unknown) { - var entry = await Account.RequestRepo.FolderInfo(RemotePath.Get(new Link(url))); + var entry = await RequestRepo.FolderInfo(RemotePath.Get(new Link(url))); return entry; } @@ -182,7 +186,7 @@ private async Task GetItemInternalAsync(string path, bool resolveLinks, return await GetPublicItemAsync(new Uri(uriMatch.Groups["uri"].Value, UriKind.Absolute)); } - if (Account.IsAnonymous) + if (Credentials.IsAnonymous) return null; path = WebDavPath.Clean(path); @@ -191,7 +195,7 @@ private async Task GetItemInternalAsync(string path, bool resolveLinks, if (fastGetFromCloud) { remotePath = RemotePath.Get(path); - return await Account.RequestRepo.FolderInfo(remotePath, depth: 1, limit: 2); + return await RequestRepo.FolderInfo(remotePath, depth: 1, limit: 2); } (var cached, var getState) = _entryCache.Get(path); @@ -227,7 +231,7 @@ private async Task GetItemInternalAsync(string path, bool resolveLinks, // //_itemCache.Add(cachefolder.Files); //} remotePath = ulink is null ? RemotePath.Get(path) : RemotePath.Get(ulink); - var cloudResult = await Account.RequestRepo.FolderInfo(remotePath, depth: Settings.ListDepth); + var cloudResult = await RequestRepo.FolderInfo(remotePath, depth: Settings.ListDepth); if (cloudResult is null) { // Если обратились к серверу, а в ответ пустота, @@ -453,7 +457,7 @@ public string Find(string nameWithoutPathToFind, params string[] folderPaths) private async Task Unpublish(Uri publicLink, string fullPath) { //var res = (await new UnpublishRequest(CloudApi, publicLink).MakeRequestAsync(_connectionLimiter)) - var res = (await Account.RequestRepo.Unpublish(publicLink, fullPath)) + var res = (await RequestRepo.Unpublish(publicLink, fullPath)) .ThrowIf(r => !r.IsSuccess, _ => new Exception($"Unpublish error, link = {publicLink}")); return res.IsSuccess; @@ -472,12 +476,12 @@ public async Task Unpublish(File file) private async Task Publish(string fullPath) { - var res = (await Account.RequestRepo.Publish(fullPath)) + var res = (await RequestRepo.Publish(fullPath)) .ThrowIf(r => !r.IsSuccess, _ => new Exception($"Publish error, path = {fullPath}")); var uri = new Uri(res.Url, UriKind.RelativeOrAbsolute); if (!uri.IsAbsoluteUri) - uri = new Uri($"{Account.RequestRepo.PublicBaseUrlDefault.TrimEnd('/')}/{res.Url.TrimStart('/')}", UriKind.Absolute); + uri = new Uri($"{RequestRepo.PublicBaseUrlDefault.TrimEnd('/')}/{res.Url.TrimStart('/')}", UriKind.Absolute); return uri; } @@ -580,7 +584,7 @@ public async Task Copy(Folder folder, string destinationPath) } //var copyRes = await new CopyRequest(CloudApi, folder.FullPath, destinationPath).MakeRequestAsync(_connectionLimiter); - var copyRes = await Account.RequestRepo.Copy(folder.FullPath, destinationPath); + var copyRes = await RequestRepo.Copy(folder.FullPath, destinationPath); if (!copyRes.IsSuccess) return false; @@ -692,7 +696,7 @@ public async Task Copy(File file, string destinationPath, string newName) .Select(async pfile => { //var copyRes = await new CopyRequest(CloudApi, pfile.FullPath, destPath, ConflictResolver.Rewrite).MakeRequestAsync(_connectionLimiter); - var copyRes = await Account.RequestRepo.Copy(pfile.FullPath, destPath, ConflictResolver.Rewrite); + var copyRes = await RequestRepo.Copy(pfile.FullPath, destPath, ConflictResolver.Rewrite); if (!copyRes.IsSuccess) return false; if (!doRename && WebDavPath.Name(copyRes.NewName) == newName) @@ -781,7 +785,7 @@ private async Task Rename(string fullPath, string newName) //rename item if (link is null) { - var data = await Account.RequestRepo.Rename(fullPath, newName); + var data = await RequestRepo.Rename(fullPath, newName); if (!data.IsSuccess) return data.IsSuccess; @@ -869,7 +873,7 @@ public async Task MoveAsync(Folder folder, string destinationPath) return remapped; } - var res = await Account.RequestRepo.Move(folder.FullPath, destinationPath); + var res = await RequestRepo.Move(folder.FullPath, destinationPath); _entryCache.ResetCheck(); _entryCache.OnCreateAsync(destinationPath, GetItemAsync(destinationPath, fastGetFromCloud: true)); _entryCache.OnRemoveTreeAsync(folder.FullPath, GetItemAsync(folder.FullPath, fastGetFromCloud: true)); @@ -932,7 +936,7 @@ public async Task MoveAsync(File file, string destinationPath) .WithDegreeOfParallelism(file.Files.Count) .Select(async pfile => { - return await Account.RequestRepo.Move(pfile.FullPath, destinationPath); + return await RequestRepo.Move(pfile.FullPath, destinationPath); }); @@ -1054,7 +1058,7 @@ public async Task Remove(string fullPath) } } - var res = await Account.RequestRepo.Remove(fullPath); + var res = await RequestRepo.Remove(fullPath); if (!res.IsSuccess) return false; @@ -1075,7 +1079,7 @@ public async Task Remove(string fullPath) public IEnumerable GetSharedLinks(string fullPath) { - return Account.RequestRepo.GetShareLinks(fullPath); + return RequestRepo.GetShareLinks(fullPath); } /// @@ -1084,7 +1088,7 @@ public IEnumerable GetSharedLinks(string fullPath) /// Returns Total/Free/Used size. public async Task GetDiskUsageAsync() { - var data = await Account.RequestRepo.AccountInfo(); + var data = await RequestRepo.AccountInfo(); return data.DiskUsage; } public DiskUsage GetDiskUsage() @@ -1101,7 +1105,7 @@ public void AbortAllAsyncThreads() CancelToken.Cancel(false); } - public IRequestRepo Repo => Account.RequestRepo; + public IRequestRepo Repo => RequestRepo; /// /// Create folder on the server. @@ -1121,7 +1125,7 @@ public bool CreateFolder(string name, string basePath) public async Task CreateFolderAsync(string fullPath) { - var res = await Account.RequestRepo.CreateFolder(fullPath); + var res = await RequestRepo.CreateFolder(fullPath); if (res.IsSuccess) { @@ -1140,7 +1144,7 @@ public async Task CreateFolderAsync(string fullPath) public async Task CloneItem(string toPath, string fromUrl) { - var res = await Account.RequestRepo.CloneItem(fromUrl, toPath); + var res = await RequestRepo.CloneItem(fromUrl, toPath); if (res.IsSuccess) { @@ -1191,7 +1195,7 @@ private void OnFileUploaded(IEnumerable files) public T DownloadFileAsJson(File file) { - using var stream = Account.RequestRepo.GetDownloadStream(file); + using var stream = RequestRepo.GetDownloadStream(file); using var reader = new StreamReader(stream); using var jsonReader = new JsonTextReader(reader); @@ -1212,7 +1216,7 @@ public async Task DownloadFileAsString(string path) if (entry is null || entry is not File file) return null; { - using var stream = Account.RequestRepo.GetDownloadStream(file); + using var stream = RequestRepo.GetDownloadStream(file); using var reader = new StreamReader(stream); string res = await reader.ReadToEndAsync(); @@ -1306,7 +1310,7 @@ public async void RemoveDeadLinks() public async Task AddFile(IFileHash hash, string fullFilePath, long size, ConflictResolver? conflict = null) { - var res = await Account.RequestRepo.AddFile(fullFilePath, hash, size, DateTime.Now, conflict); + var res = await RequestRepo.AddFile(fullFilePath, hash, size, DateTime.Now, conflict); if (res.Success) { @@ -1329,7 +1333,7 @@ public async Task SetFileDateTime(File file, DateTime dateTime) if (file.LastWriteTimeUtc == dateTime) return true; - var added = await Account.RequestRepo.AddFile(file.FullPath, file.Hash, file.Size, dateTime, ConflictResolver.Rename); + var added = await RequestRepo.AddFile(file.FullPath, file.Hash, file.Size, dateTime, ConflictResolver.Rename); bool res = added.Success; if (res) { diff --git a/MailRuCloud/MailRuCloudApi/CloudSettings.cs b/MailRuCloud/MailRuCloudApi/CloudSettings.cs index 4d358379..62aa9fae 100644 --- a/MailRuCloud/MailRuCloudApi/CloudSettings.cs +++ b/MailRuCloud/MailRuCloudApi/CloudSettings.cs @@ -12,7 +12,7 @@ public class CloudSettings public string UserAgent { get; set; } public string SecChUa { get; set; } - public Protocol Protocol { get; set; } + public Protocol Protocol { get; set; } = Protocol.Autodetect; public int CacheListingSec { get; set; } = 30; diff --git a/MailRuCloud/MailRuCloudApi/Links/LinkManager.cs b/MailRuCloud/MailRuCloudApi/Links/LinkManager.cs index 134cab1e..827f9aeb 100644 --- a/MailRuCloud/MailRuCloudApi/Links/LinkManager.cs +++ b/MailRuCloud/MailRuCloudApi/Links/LinkManager.cs @@ -91,7 +91,7 @@ public async void Save() /// public async void Load() { - if (!_cloud.Account.IsAnonymous) + if (!_cloud.Credentials.IsAnonymous) { Logger.Info($"Loading links from {LinkContainerName}"); @@ -310,7 +310,7 @@ private async Task ResolveLink(Link link) // : link.Href.OriginalString; //var infores = await new ItemInfoRequest(_cloud.CloudApi, link.Href, true).MakeRequestAsync(_connectionLimiter); - var infores = await _cloud.Account.RequestRepo.ItemInfo(RemotePath.Get(link)); + var infores = await _cloud.RequestRepo.ItemInfo(RemotePath.Get(link)); link.ItemType = infores.Body.Kind == "file" ? Cloud.ItemType.File : Cloud.ItemType.Folder; @@ -390,7 +390,7 @@ public async Task Add(Uri url, string path, string name, bool isFile, long //private string GetRelaLink(Uri url) //{ - // foreach (string pbu in _cloud.Account.RequestRepo.PublicBaseUrls) + // foreach (string pbu in _cloud.RequestRepo.PublicBaseUrls) // { // if (!string.IsNullOrEmpty(pbu)) // if (url.StartsWith(pbu)) diff --git a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/CryptPasswdCommand.cs b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/CryptPasswdCommand.cs index 2f72b103..9778bfcb 100644 --- a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/CryptPasswdCommand.cs +++ b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/CryptPasswdCommand.cs @@ -20,7 +20,7 @@ public override async Task Execute() if (string.IsNullOrEmpty(newPasswd)) return await Task.FromResult(new SpecialCommandResult(false, "Crypt password is empty")); - Cloud.Account.Credentials.PasswordCrypt = newPasswd; + Cloud.Credentials.PasswordCrypt = newPasswd; return await Task.FromResult(SpecialCommandResult.Success); } diff --git a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/FishCommand.cs b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/FishCommand.cs index b86aee0f..fe195a40 100644 --- a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/FishCommand.cs +++ b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/FishCommand.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Net; using System.Net.Http; using System.Text.RegularExpressions; using System.Threading.Tasks; @@ -38,7 +37,7 @@ public override async Task Execute() { //var res = await new CreateFileRequest(Cloud.CloudApi, target, strRandomHash, randomSize, ConflictResolver.Rename).MakeRequestAsync(_connectionLimiter); var hash = new FileHashMrc(randomHash); - var res = await Cloud.Account.RequestRepo.AddFile(target, hash, randomSize, DateTime.Now, ConflictResolver.Rename); + var res = await Cloud.RequestRepo.AddFile(target, hash, randomSize, DateTime.Now, ConflictResolver.Rename); if (res.Success) { Logger.Warn("╔╗╔╗╔╦══╦╗╔╗╔╗╔╦╦╗"); diff --git a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/ListCommand.cs b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/ListCommand.cs index 9f1e92dd..7d0c1229 100644 --- a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/ListCommand.cs +++ b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/ListCommand.cs @@ -6,7 +6,6 @@ using YaR.Clouds.Base; using YaR.Clouds.Base.Repos; using YaR.Clouds.Links; -using YaR.Clouds.Common; namespace YaR.Clouds.SpecialCommands.Commands { @@ -30,7 +29,7 @@ public override async Task Execute() var resolvedTarget = await RemotePath.Get(target, Cloud.LinkManager); - var entry = await Cloud.Account.RequestRepo.FolderInfo(resolvedTarget); + var entry = await Cloud.RequestRepo.FolderInfo(resolvedTarget); string resFilepath = WebDavPath.Combine(Path, string.Concat(entry.Name, FileListExtention)); var sb = new StringBuilder(); @@ -60,7 +59,7 @@ private IEnumerable Flat(IEntry entry, LinkManager lm) File => it, Folder ifolder => ifolder.IsChildrenLoaded ? ifolder - : Cloud.Account.RequestRepo.FolderInfo(RemotePath.Get(it.FullPath, lm).Result, depth: 3).Result, + : Cloud.RequestRepo.FolderInfo(RemotePath.Get(it.FullPath, lm).Result, depth: 3).Result, _ => throw new NotImplementedException("Unknown item type") }) .OrderBy(it => it.Name); diff --git a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/LocalToServerCopyCommand.cs b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/LocalToServerCopyCommand.cs index 5c0e1a96..68ba5cbe 100644 --- a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/LocalToServerCopyCommand.cs +++ b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/LocalToServerCopyCommand.cs @@ -7,9 +7,9 @@ namespace YaR.Clouds.SpecialCommands.Commands { public class LocalToServerCopyCommand : SpecialCommand { - private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(Account)); + private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(LocalToServerCopyCommand)); - public LocalToServerCopyCommand(Cloud cloud, string path, IList parames) : base(cloud, path, parames) + public LocalToServerCopyCommand(Cloud cloud, string path, IList parameters) : base(cloud, path, parameters) { } diff --git a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/SharedFolderLinkCommand.cs b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/SharedFolderLinkCommand.cs index d7ca6a1f..420c598e 100644 --- a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/SharedFolderLinkCommand.cs +++ b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/SharedFolderLinkCommand.cs @@ -29,7 +29,7 @@ public override async Task Execute() //TODO: make method in MailRuCloud to get entry by url //var item = await new ItemInfoRequest(Cloud.CloudApi, m.Groups["url"].Value, true).MakeRequestAsync(_connectionLimiter); - var item = await Cloud.Account.RequestRepo.ItemInfo(RemotePath.Get(new Link(url)) ); + var item = await Cloud.RequestRepo.ItemInfo(RemotePath.Get(new Link(url)) ); var entry = item.ToEntry(Cloud.Repo.PublicBaseUrlDefault); if (entry is null) return SpecialCommandResult.Fail; diff --git a/MailRuCloud/MailRuCloudApi/Streams/DownloadStreamFabric.cs b/MailRuCloud/MailRuCloudApi/Streams/DownloadStreamFabric.cs index 8f4342dd..9c305f46 100644 --- a/MailRuCloud/MailRuCloudApi/Streams/DownloadStreamFabric.cs +++ b/MailRuCloud/MailRuCloudApi/Streams/DownloadStreamFabric.cs @@ -20,14 +20,14 @@ public Stream Create(File file, long? start = null, long? end = null) return CreateXTSStream(file, start, end); //return new DownloadStream(file, _cloud.CloudApi, start, end); - var stream = _cloud.Account.RequestRepo.GetDownloadStream(file, start, end); + var stream = _cloud.RequestRepo.GetDownloadStream(file, start, end); return stream; } private Stream CreateXTSStream(File file, long? start = null, long? end = null) { var pub = CryptoUtil.GetCryptoPublicInfo(_cloud, file); - var key = CryptoUtil.GetCryptoKey(_cloud.Account.Credentials.PasswordCrypt, pub.Salt); + var key = CryptoUtil.GetCryptoKey(_cloud.Credentials.PasswordCrypt, pub.Salt); var xts = XtsAes256.Create(key, pub.IV); long fileLength = file.OriginalSize; @@ -40,7 +40,7 @@ private Stream CreateXTSStream(File file, long? start = null, long? end = null) : (requestedEnd / XTSBlockSize + 1) * XTSBlockSize; if (alignedEnd == 0) alignedEnd = 16; - var downStream = _cloud.Account.RequestRepo.GetDownloadStream(file, alignedOffset, alignedEnd); + var downStream = _cloud.RequestRepo.GetDownloadStream(file, alignedOffset, alignedEnd); ulong startSector = (ulong)alignedOffset / XTSSectorSize; int trimStart = (int)(requestedOffset - alignedOffset); diff --git a/MailRuCloud/MailRuCloudApi/Streams/SplittedUploadStream.cs b/MailRuCloud/MailRuCloudApi/Streams/SplittedUploadStream.cs index fd6ce967..273118a7 100644 --- a/MailRuCloud/MailRuCloudApi/Streams/SplittedUploadStream.cs +++ b/MailRuCloud/MailRuCloudApi/Streams/SplittedUploadStream.cs @@ -37,8 +37,8 @@ public SplittedUploadStream(string destinationPath, Cloud cloud, long size, Acti FileStreamSent = fileStreamSent; ServerFileProcessed = serverFileProcessed; - _maxFileSize = _cloud.Account.Info.FileSizeLimit > 0 - ? _cloud.Account.Info.FileSizeLimit - 1024 + _maxFileSize = _cloud.AccountInfo.FileSizeLimit > 0 + ? _cloud.AccountInfo.FileSizeLimit - 1024 : long.MaxValue - 1024; Initialize(); diff --git a/MailRuCloud/MailRuCloudApi/Streams/UploadStreamFabric.cs b/MailRuCloud/MailRuCloudApi/Streams/UploadStreamFabric.cs index 97e7548e..8bcd0144 100644 --- a/MailRuCloud/MailRuCloudApi/Streams/UploadStreamFabric.cs +++ b/MailRuCloud/MailRuCloudApi/Streams/UploadStreamFabric.cs @@ -32,7 +32,7 @@ public async Task Create(File file, FileUploadedDelegate onUploaded = nu bool cryptRequired = !string.IsNullOrEmpty(fullPath); if (cryptRequired && !discardEncryption) { - if (!_cloud.Account.Credentials.CanCrypt) + if (!_cloud.Credentials.CanCrypt) throw new Exception($"Cannot upload {file.FullPath} to crypt folder without additional password!"); // #142 remove crypted file parts if size changed @@ -57,7 +57,7 @@ private Stream GetPlainStream(File file, FileUploadedDelegate onUploaded) private Stream GetCryptoStream(File file, FileUploadedDelegate onUploaded) { - var info = CryptoUtil.GetCryptoKeyAndSalt(_cloud.Account.Credentials.PasswordCrypt); + var info = CryptoUtil.GetCryptoKeyAndSalt(_cloud.Credentials.PasswordCrypt); var xts = XtsAes256.Create(info.Key, info.IV); file.ServiceInfo.CryptInfo = new CryptInfo diff --git a/WDMRC.Console/CommandLineOptions.cs b/WDMRC.Console/CommandLineOptions.cs index 1de3ead8..c73dc19e 100644 --- a/WDMRC.Console/CommandLineOptions.cs +++ b/WDMRC.Console/CommandLineOptions.cs @@ -52,7 +52,7 @@ class CommandLineOptions [Option("service", Required = false, Default = false, HelpText = "Started as a service")] public bool ServiceRun { get; set; } - [Option("protocol", Default = Protocol.WebM1Bin, HelpText = "Cloud protocol")] + [Option("protocol", Default = Protocol.Autodetect, HelpText = "Cloud protocol")] public Protocol Protocol { get; set; } [Option("cache-listing", Default = 30, HelpText = "Folder cache expiration timeout, sec")] diff --git a/WDMRC.Console/Payload.cs b/WDMRC.Console/Payload.cs index a0550232..27c2d978 100644 --- a/WDMRC.Console/Payload.cs +++ b/WDMRC.Console/Payload.cs @@ -219,7 +219,7 @@ private static void ShowInfo(CommandLineOptions options) Logger.Info($"Cloud server response timeout: {options.WaitResponseTimeoutSec} sec"); Logger.Info($"Cloud download/upload timeout: {options.ReadWriteTimeoutSec} sec"); Logger.Info($"Wait for 100-Continue timeout: {options.Wait100ContinueTimeoutSec} sec"); - Logger.Info($"Cloud protocol: {options.Protocol}"); + Logger.Info($"Cloud & protocol: defined by login and the rest parameters"); Logger.Info($"Folder cache expiration timeout: {options.CacheListingSec} sec"); Logger.Info($"List query folder depth: {options.CacheListingDepth}"); Logger.Info($"Use locks: {options.UseLocks}"); diff --git a/WDMRC.Console/Program.cs b/WDMRC.Console/Program.cs index 2f04a710..4d8adc81 100644 --- a/WDMRC.Console/Program.cs +++ b/WDMRC.Console/Program.cs @@ -22,9 +22,9 @@ private static void Main(string[] args) Assembly = Assembly.GetExecutingAssembly(), Name = options.ServiceInstall ?? options.ServiceUninstall ?? "wdmrc", DisplayName = string.IsNullOrEmpty(options.ServiceInstallDisplayName) - ? $"WebDavCloud [{options.Protocol}]" + ? $"WebDavCloud [{options.Port}]" : options.ServiceInstallDisplayName, - Description = "WebDAV gate2cloud", + Description = "WebDAV emulator for cloud.Mail.ru / disk.Yandex.ru", FireStart = () => Payload.Run(options), FireStop = Payload.Stop diff --git a/WDMRC.Console/WDMRC.Console.csproj b/WDMRC.Console/WDMRC.Console.csproj index 639d3333..085f5c5a 100644 --- a/WDMRC.Console/WDMRC.Console.csproj +++ b/WDMRC.Console/WDMRC.Console.csproj @@ -13,8 +13,8 @@ YaR WebDAVCloudMailRu - yar229@yandex.ru;ZZZConsulting@internet.ru - WebDAV emulator for Cloud.mail.ru + MIT License, Copyright (c) 2023 YaR + WebDAV emulator for cloud.Mail.ru / disk.Yandex.ru WebDAVCloudMailRu $(ReleaseVersion) $(ReleaseVersion) @@ -26,7 +26,7 @@ https://github.com/yar229/WebDavMailRuCloud readme.md False - WebDAV emulator for Cloud.mail.ru / Yandex.Disk + WebDAV emulator for cloud.Mail.ru / disk.Yandex.ru @@ -64,7 +64,7 @@ Never - Always + PreserveNewest diff --git a/WebDavMailRuCloudStore/CloudManager.cs b/WebDavMailRuCloudStore/CloudManager.cs index d89ad63e..03472dab 100644 --- a/WebDavMailRuCloudStore/CloudManager.cs +++ b/WebDavMailRuCloudStore/CloudManager.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Concurrent; using System.Net; +using System.Security.Authentication; using System.Security.Principal; using System.Threading; using YaR.Clouds.Base; @@ -45,11 +46,57 @@ public static Cloud Instance(IIdentity identity) private static Cloud CreateCloud(HttpListenerBasicIdentity identity) { - Logger.Info($"Cloud instance created for {identity.Name}"); - var credentials = new Credentials(identity.Name, identity.Password); + if (credentials.Protocol == Protocol.Autodetect && + Settings.Protocol != Protocol.Autodetect) + { + // Если протокол не определился из строки логина, + // то пользуемся подсказкой в виде параметра командной строки + credentials.Protocol = Settings.Protocol; + } + + if (credentials.Protocol == Protocol.Autodetect && + credentials.CloudType == CloudType.Yandex) + { + if (string.IsNullOrEmpty(Settings.BrowserAuthenticatorUrl) || + string.IsNullOrEmpty(Settings.BrowserAuthenticatorPassword)) + { + credentials.Protocol = Protocol.YadWeb; + } + else + if (!string.IsNullOrEmpty(Settings.BrowserAuthenticatorUrl) && + !string.IsNullOrEmpty(Settings.BrowserAuthenticatorPassword) && + identity.Password.Equals(Settings.BrowserAuthenticatorPassword, StringComparison.InvariantCultureIgnoreCase)) + { + credentials.Protocol = Protocol.YadWebV2; + } + else + { + Logger.Info("Protocol auto detect is ON. Can not choose between YadWeb and YadWebV2"); + throw new InvalidCredentialException( + "Protocol auto detect is ON. Can not choose between YadWeb and YadWebV2. " + + "Please specify protocol version in login string, see manuals."); + } + } + + if (credentials.CloudType == CloudType.Unkown) + { + Logger.Info("Cloud type is not detected by user login string"); + throw new InvalidCredentialException("Cloud type is not detected. " + + "Please specify protocol and email in login string, see manuals."); + } + + if (credentials.Protocol == Protocol.Autodetect) + { + Logger.Info("Protocol is undefined by user credentials"); + throw new InvalidCredentialException("Protocol type is not detected. " + + "Please specify protocol and email in login string, see manuals."); + } + var cloud = new Cloud(Settings, credentials); + Logger.Info($"Cloud instance created for {credentials.Login}"); + return cloud; } } diff --git a/YandexAuthBrowser/ResidentForm.cs b/YandexAuthBrowser/ResidentForm.cs index 74f5397a..cf2f7f7e 100644 --- a/YandexAuthBrowser/ResidentForm.cs +++ b/YandexAuthBrowser/ResidentForm.cs @@ -23,7 +23,8 @@ public partial class ResidentForm : Form public Execute AuthExecuteDelegate; private readonly int? SavedTop = null; private readonly int? SavedLeft = null; - private SemaphoreSlim Sema = new SemaphoreSlim(1, 1); + private SemaphoreSlim _showBrowserLocker; + private bool _doNotSave = false; private int AuthenticationOkCounter = 0; private int AuthenticationFailCounter = 0; @@ -33,17 +34,25 @@ public ResidentForm() { InitializeComponent(); + _showBrowserLocker = new SemaphoreSlim(1, 1); + var screen = Screen.GetWorkingArea(this); Top = screen.Height + 100; ShowInTaskbar = false; NotifyIcon.Visible = true; +#if DEBUG + _doNotSave = true; + Port.Text = "54322"; + Password.Text = "adb4bcd5-b4b6-45b7-bb7d-b38470917448"; + _doNotSave = false; +#endif + // Get the current configuration file. - System.Configuration.Configuration config = - ConfigurationManager.OpenExeConfiguration( - ConfigurationUserLevel.None); + Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None); + _doNotSave = true; string? value = config.AppSettings?.Settings?["port"]?.Value; if (!string.IsNullOrWhiteSpace(value) && int.TryParse(value, out _)) Port.Text = value; @@ -57,6 +66,7 @@ public ResidentForm() value = config.AppSettings?.Settings?["Left"]?.Value; if (!string.IsNullOrWhiteSpace(value) && int.TryParse(value, out int left)) SavedLeft = left; + _doNotSave = false; PreviousPort = Port.Text; @@ -69,7 +79,6 @@ public ResidentForm() Counter.Text = ""; - RunServer = true; StartServer(); } @@ -128,9 +137,12 @@ private void ResidentForm_Move(object sender, EventArgs e) } private void SaveConfigTimer_Tick(object sender, EventArgs e) { + if (_doNotSave) + return; + SaveConfigTimer.Enabled = false; - System.Configuration.Configuration config = + Configuration config = ConfigurationManager.OpenExeConfiguration( ConfigurationUserLevel.None); @@ -164,6 +176,11 @@ private void NotifyIcon_ExitClick(object? sender, EventArgs e) { NotifyIcon.Visible = false; StopServer(); + if (SaveConfigTimer.Enabled) + { + SaveConfigTimer.Enabled = false; + SaveConfig(); + } // При вызове Close дальше будет обработка в HiddenContext, см. там. Close(); @@ -178,8 +195,11 @@ private void HideButton_Click(object sender, EventArgs e) } private void SaveConfig() { + if (_doNotSave) + return; + // Get the current configuration file. - System.Configuration.Configuration config = + Configuration config = ConfigurationManager.OpenExeConfiguration( ConfigurationUserLevel.None); @@ -253,10 +273,6 @@ private void Lock_CheckedChanged(object sender, EventArgs e) private void StartServer() { -#if DEBUG - Port.Text = "54322"; - Password.Text = "adb4bcd5-b4b6-45b7-bb7d-b38470917448"; -#endif if (!int.TryParse(Port.Text, out int port)) { Port.Text = "54321"; @@ -269,6 +285,7 @@ private void StartServer() // Create a http server and start listening for incoming connections Listener?.Prefixes.Add($"http://localhost:{port}/"); Listener?.Start(); + RunServer = true; // Handle requests _ = Task.Run(HandleIncomingConnections); @@ -319,7 +336,7 @@ public async Task HandleIncomingConnections() try { if (Listener == null) - throw new NullReferenceException("Listener is null"); + break; // Will wait here until we hear from a connection HttpListenerContext ctx = await Listener.GetContextAsync(); @@ -345,13 +362,13 @@ public async Task HandleIncomingConnections() response.ErrorMessage = "Password is wrong"; else { - Sema.Wait(); + _showBrowserLocker.Wait(); // Окно с браузером нужно открыть в потоке, обрабатывающем UI if (AuthButton.InvokeRequired) AuthButton.Invoke(AuthExecuteDelegate, login, response); else AuthExecuteDelegate(login, response); - Sema.Release(); + _showBrowserLocker.Release(); } string text = response.Serialize(); diff --git a/readme.md b/readme.md index a9094b29..ab697c7d 100644 --- a/readme.md +++ b/readme.md @@ -1,4 +1,4 @@ -## **WebDAV emulator for Cloud.mail.ru / Yandex.Disk**
+## **WebDAV emulator for cloud.Mail.ru / disk.Yandex.ru**
From 623dd42666d0bd2c8dd8d68928a053c5b392e937 Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Sun, 29 Oct 2023 21:44:55 +0300 Subject: [PATCH 41/77] =?UTF-8?q?=D0=A3=D1=81=D1=82=D0=B0=D0=BD=D0=B0?= =?UTF-8?q?=D0=B2=D0=BB=D0=B8=D0=B2=D0=B0=D1=8E=D1=82=D1=81=D1=8F=20Dispay?= =?UTF-8?q?Name=20=D0=B8=20Description=20=D0=B4=D0=BB=D1=8F=20=D1=81=D0=B5?= =?UTF-8?q?=D1=80=D0=B2=D0=B8=D1=81=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- WDMRC.Console/Program.cs | 4 ++-- WinServiceInstaller/ServiceConfigurator.cs | 17 ++++++++++++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/WDMRC.Console/Program.cs b/WDMRC.Console/Program.cs index 4d8adc81..75054987 100644 --- a/WDMRC.Console/Program.cs +++ b/WDMRC.Console/Program.cs @@ -21,8 +21,8 @@ private static void Main(string[] args) { Assembly = Assembly.GetExecutingAssembly(), Name = options.ServiceInstall ?? options.ServiceUninstall ?? "wdmrc", - DisplayName = string.IsNullOrEmpty(options.ServiceInstallDisplayName) - ? $"WebDavCloud [{options.Port}]" + DisplayName = string.IsNullOrEmpty(options.ServiceInstallDisplayName) + ? $"WebDavCloud [port {string.Join(", port ", options.Port)}]" : options.ServiceInstallDisplayName, Description = "WebDAV emulator for cloud.Mail.ru / disk.Yandex.ru", diff --git a/WinServiceInstaller/ServiceConfigurator.cs b/WinServiceInstaller/ServiceConfigurator.cs index e14117e9..14346230 100644 --- a/WinServiceInstaller/ServiceConfigurator.cs +++ b/WinServiceInstaller/ServiceConfigurator.cs @@ -72,7 +72,10 @@ public void Install() // Если ключа реестра нет, значит сначала надо программу прописать сервисом, а только потом править параметры запуска if (serviceKey == null) { - string consoleText = RunSc("create", _name, "start=", "auto", "binPath=", exePath); + string consoleText = RunSc("create", _name, + "start=", "auto", + "binPath=", exePath, + "DisplayName=", _displayName); if (!(consoleText.Contains("успех", StringComparison.OrdinalIgnoreCase) || consoleText.Contains("success", StringComparison.OrdinalIgnoreCase) || consoleText.Contains("PENDING", StringComparison.OrdinalIgnoreCase) @@ -95,6 +98,14 @@ public void Install() )) throw new Exception("Error while stopping the service\r\n" + consoleText); } + { + string consoleText = RunSc("Description", _name, _description); + if (!(consoleText.Contains("успех", StringComparison.OrdinalIgnoreCase) + || consoleText.Contains("success", StringComparison.OrdinalIgnoreCase) + || consoleText.Contains("PENDING", StringComparison.OrdinalIgnoreCase) + )) + throw new Exception("Error while installing the service\r\n" + consoleText); + } string cmd = string.IsNullOrWhiteSpace(CommandLine) ? exePath @@ -226,6 +237,10 @@ private string RunSc(params string[] args) { proc.Kill(); } + //if (proc.ExitCode != 0) + //{ + // Console.WriteLine(sb.ToString()); + //} return standardOutput + standardError; } From 5e0bb7fb2e2e4cb193550c8e837077edc9766e56 Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Sun, 5 Nov 2023 00:46:26 +0300 Subject: [PATCH 42/77] =?UTF-8?q?=D0=9D=D0=B0=D1=81=D1=82=D1=80=D0=BE?= =?UTF-8?q?=D0=B9=D0=BA=D0=B0=20=D1=81=D0=B5=D1=80=D0=B2=D0=B8=D1=81=D0=B0?= =?UTF-8?q?,=20=D1=87=D1=82=D0=BE=D0=B1=D1=8B=20=D0=B2=20=D1=81=D0=BB?= =?UTF-8?q?=D1=83=D1=87=D0=B0=D0=B5=20=D1=81=D0=B1=D0=BE=D1=8F,=20=D1=81?= =?UTF-8?q?=D0=B8=D1=81=D1=82=D0=B5=D0=BC=D0=B0=20=D0=B5=D0=B3=D0=BE=20?= =?UTF-8?q?=D0=B0=D0=B2=D1=82=D0=BE=D0=BC=D0=B0=D1=82=D0=B8=D1=87=D0=B5?= =?UTF-8?q?=D1=81=D0=BA=D0=B8=20=D0=BF=D0=B5=D1=80=D0=B5=D0=B7=D0=B0=D0=BF?= =?UTF-8?q?=D1=83=D1=81=D0=BA=D0=B0=D0=BB=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- WinServiceInstaller/ServiceConfigurator.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/WinServiceInstaller/ServiceConfigurator.cs b/WinServiceInstaller/ServiceConfigurator.cs index 14346230..94561d4b 100644 --- a/WinServiceInstaller/ServiceConfigurator.cs +++ b/WinServiceInstaller/ServiceConfigurator.cs @@ -106,6 +106,15 @@ public void Install() )) throw new Exception("Error while installing the service\r\n" + consoleText); } + { + // В случае сбоя сервиса, система его сразу автоматически перезапустит + string consoleText = RunSc("failure", _name, "reset=", "0", "actions=", "restart/0"); + if (!(consoleText.Contains("успех", StringComparison.OrdinalIgnoreCase) + || consoleText.Contains("success", StringComparison.OrdinalIgnoreCase) + || consoleText.Contains("PENDING", StringComparison.OrdinalIgnoreCase) + )) + throw new Exception("Error while installing the service\r\n" + consoleText); + } string cmd = string.IsNullOrWhiteSpace(CommandLine) ? exePath @@ -198,12 +207,12 @@ private void SetCommandLine(string cmd) #if NET7_0_WINDOWS - private bool NeedWaitSc(string serviceName) + private static bool NeedWaitSc(string serviceName) { return RunSc("query", serviceName).Contains("PENDING", StringComparison.OrdinalIgnoreCase); } - private string RunSc(params string[] args) + private static string RunSc(params string[] args) { StringBuilder sb = new StringBuilder(); foreach (var item in args) From 3fd76186e616eefd7f121bdc9887b522f8771113 Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Sun, 5 Nov 2023 13:33:36 +0300 Subject: [PATCH 43/77] =?UTF-8?q?=D0=9D=D0=B5=D0=BC=D0=BD=D0=BE=D0=B3?= =?UTF-8?q?=D0=BE=20=D1=83=D1=81=D0=BA=D0=BE=D1=80=D0=B5=D0=BD=D0=B8=D1=8F?= =?UTF-8?q?=20Regex=20=D0=BF=D0=BE=D0=B4=20NET=207+?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .editorconfig | 2 +- .../MailRuCloudApi/Base/Credentials.cs | 41 ++++++++++++++----- .../WebV2/Requests/DownloadTokenRequest.cs | 6 +-- .../YadWeb/Requests/YadAuthDiskSkRequest.cs | 14 +++++-- .../Requests/YadAuthPreAuthRequestResult.cs | 29 ++++++++++--- .../Repos/YandexDisk/YadWeb/YadWebAuth.cs | 6 +-- .../Repos/YandexDisk/YadWebV2/YadWebAuth.cs | 2 +- .../Base/Streams/Cache/CacheStream.cs | 2 +- MailRuCloud/MailRuCloudApi/Cloud.cs | 21 ++++++---- .../SpecialCommands/Commands/FishCommand.cs | 4 +- .../SpecialCommands/Commands/JoinCommand.cs | 35 ++++++++++++---- .../Commands/SharedFolderLinkCommand.cs | 15 ++++++- .../SpecialCommands/SpecialCommandFabric.cs | 16 ++++++-- .../NWebDav.Server/Helpers/RequestHelper.cs | 30 ++++++++------ .../Props/DavTypedProperties.cs | 28 +++++++++---- NWebDav/NWebDav.Server/WebDavUri.cs | 14 +++---- WebDAV.Uploader/UploadStub.cs | 15 +++++-- YandexAuthBrowser/AuthForm.cs | 15 +++++-- YandexAuthBrowser/ResidentForm.cs | 5 ++- 19 files changed, 210 insertions(+), 90 deletions(-) diff --git a/.editorconfig b/.editorconfig index 28baba0e..d2a34489 100644 --- a/.editorconfig +++ b/.editorconfig @@ -3,7 +3,7 @@ root = true [*] vsspell_section_id = b0252c83892c418e88e4674d7eb14041 -vsspell_ignored_words_b0252c83892c418e88e4674d7eb14041 = deduplicate|repo|repos|uninstall|Yad|maxthreads|maxconnections|Auth|Versioning|App|codec|Subentries|Utc|refact|nq|unshare +vsspell_ignored_words_b0252c83892c418e88e4674d7eb14041 = deduplicate|repo|repos|uninstall|Yad|maxthreads|maxconnections|Auth|Versioning|App|codec|Subentries|Utc|refact|nq|unshare|Deserialize|Configurator|csrf|uuid # C# files [*.cs] diff --git a/MailRuCloud/MailRuCloudApi/Base/Credentials.cs b/MailRuCloud/MailRuCloudApi/Base/Credentials.cs index 9e13da40..816c26ea 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Credentials.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Credentials.cs @@ -6,18 +6,33 @@ namespace YaR.Clouds.Base { - public class Credentials : IBasicCredentials + public partial class Credentials : IBasicCredentials { private static readonly string[] AnonymousLogins = { "anonymous", "anon", "anonym", string.Empty }; // протокол # логин # разделитель - private static Regex _loginRegex1 = new Regex("^([^#]*)#([^#]*)#([^#]*)$", RegexOptions.IgnoreCase | RegexOptions.Compiled); + private const string RegexMask1 = "^([^#]*)#([^#]*)#([^#]*)$"; // протокол # логин - private static Regex _loginRegex2 = new Regex( - "^(1|2|WebM1Bin|WebV2|YadWeb|YadWebV2)#([^#]*)$", - RegexOptions.IgnoreCase | RegexOptions.Compiled); + private const string RegexMask2 = "^(1|2|WebM1Bin|WebV2|YadWeb|YadWebV2)#([^#]*)$"; // логин # разделитель - private static Regex _loginRegex3 = new Regex("^([^#]*)#([^#]*)$", RegexOptions.IgnoreCase | RegexOptions.Compiled); + private const string RegexMask3 = "^([^#]*)#([^#]*)$"; + +#if NET7_0_OR_GREATER + private static readonly Regex _loginRegex1 = LoginRegexMask1(); + private static readonly Regex _loginRegex2 = LoginRegexMask2(); + private static readonly Regex _loginRegex3 = LoginRegexMask3(); + + [GeneratedRegex(RegexMask1, RegexOptions.IgnoreCase | RegexOptions.Compiled)] + private static partial Regex LoginRegexMask1(); + [GeneratedRegex(RegexMask2, RegexOptions.IgnoreCase | RegexOptions.Compiled)] + private static partial Regex LoginRegexMask2(); + [GeneratedRegex(RegexMask3, RegexOptions.IgnoreCase | RegexOptions.Compiled)] + private static partial Regex LoginRegexMask3(); +#else + private static readonly Regex _loginRegex1 = new Regex(RegexMask1, RegexOptions.IgnoreCase | RegexOptions.Compiled); + private static readonly Regex _loginRegex2 = new Regex(RegexMask2, RegexOptions.IgnoreCase | RegexOptions.Compiled); + private static readonly Regex _loginRegex3 = new Regex(RegexMask3, RegexOptions.IgnoreCase | RegexOptions.Compiled); +#endif public Credentials(string login, string password) { @@ -144,7 +159,7 @@ public Credentials(string login, string password) public bool CanCrypt => !string.IsNullOrEmpty(PasswordCrypt); - private CloudType StringToCloud(string login) + private static CloudType StringToCloud(string login) { foreach (var domain in MailRuBaseRepo.AvailDomains) { @@ -160,9 +175,13 @@ private CloudType StringToCloud(string login) #if NET48 bool hasYandex = System.Globalization.CultureInfo.CurrentCulture.CompareInfo - .IndexOf(login, "@yandex.", System.Globalization.CompareOptions.OrdinalIgnoreCase) >= 0; + .IndexOf(login, "@yandex.", System.Globalization.CompareOptions.OrdinalIgnoreCase) >= 0 || + System.Globalization.CultureInfo.CurrentCulture.CompareInfo + .IndexOf(login, "@ya.", System.Globalization.CompareOptions.OrdinalIgnoreCase) >= 0; #else - bool hasYandex = login.Contains("@yandex.", StringComparison.InvariantCultureIgnoreCase); + bool hasYandex = + login.Contains("@yandex.", StringComparison.InvariantCultureIgnoreCase) || + login.Contains("@ya.", StringComparison.InvariantCultureIgnoreCase); #endif if (hasYandex) return CloudType.Yandex; @@ -170,7 +189,7 @@ private CloudType StringToCloud(string login) return CloudType.Unkown; } - private Protocol StringToProtocol(string protocol, CloudType cloud) + private static Protocol StringToProtocol(string protocol, CloudType cloud) { switch (protocol) { @@ -229,7 +248,7 @@ private Protocol StringToProtocol(string protocol, CloudType cloud) "protocol version needs fully qualified login using email format. See manuals."); } - private (string login, string password, string encPassword) StringToLoginPassword( + private static (string login, string password, string encPassword) StringToLoginPassword( string loginPart, string separatorPart, string passwordPart) { if (string.IsNullOrEmpty(separatorPart)) diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/DownloadTokenRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/DownloadTokenRequest.cs index 3a4e757e..d9bd2167 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/DownloadTokenRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/DownloadTokenRequest.cs @@ -47,12 +47,10 @@ protected override string RelationalUri // Ok = m.Success, // Result = new DownloadTokenResult // { - // Body = new DownloadTokenBody{Token = m.Groups["token"].Value } + // Body = new DownloadTokenBody{Token = m.Groups["token"].Value } // } // }; // return msg; // } //} - - -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadAuthDiskSkRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadAuthDiskSkRequest.cs index 639b15fb..47c48d85 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadAuthDiskSkRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadAuthDiskSkRequest.cs @@ -5,7 +5,7 @@ namespace YaR.Clouds.Base.Repos.YandexDisk.YadWeb.Requests { - class YadAuthDiskSkRequest : BaseRequestString + internal partial class YadAuthDiskSkRequest : BaseRequestString { public YadAuthDiskSkRequest(HttpCommonSettings settings, YadWebAuth auth) : base(settings, auth) { @@ -33,7 +33,7 @@ protected override HttpWebRequest CreateRequest(string baseDomain = null) protected override RequestResponse DeserializeMessage(NameValueCollection responseHeaders, string responseText) { - var matchSk = Regex.Match(responseText, Regex1); + var matchSk = s_skRegex.Match(responseText); var msg = new RequestResponse { @@ -48,8 +48,14 @@ protected override RequestResponse DeserializeMessag return msg; } - private const string Regex1 = @"""sk"":""(?.+?)"""; - //private const string _regex2 = @"sk=(?.*?)&"; // не надо, значит, уже все плохо + private const string SkRegexMask = @"""sk"":""(?.+?)"""; +#if NET7_0_OR_GREATER + [GeneratedRegex(SkRegexMask)] + private static partial Regex SkRegex(); + private static readonly Regex s_skRegex = SkRegex(); +#else + private static readonly Regex s_skRegex = new(SkRegexMask, RegexOptions.Compiled); +#endif } class YadAuthDiskSkRequestResult diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadAuthPreAuthRequestResult.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadAuthPreAuthRequestResult.cs index 2a0a6b6e..5d580bb9 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadAuthPreAuthRequestResult.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadAuthPreAuthRequestResult.cs @@ -5,9 +5,9 @@ namespace YaR.Clouds.Base.Repos.YandexDisk.YadWeb.Requests { - class YadPreAuthRequest : BaseRequestString + internal partial class YadPreAuthRequest : BaseRequestString { - public YadPreAuthRequest(HttpCommonSettings settings, IAuth auth) + public YadPreAuthRequest(HttpCommonSettings settings, IAuth auth) : base(settings, auth) { } @@ -27,10 +27,27 @@ protected override HttpWebRequest CreateRequest(string baseDomain = null) // return Encoding.UTF8.GetBytes(data); //} + private const string UuidRegexMask = @"""process_uuid"":""(?.*?)"""; + private const string CsrfRegexMask = @"""csrf"":""(?.*?)"""; +#if NET7_0_OR_GREATER + [GeneratedRegex(UuidRegexMask)] + private static partial Regex UuidRegex(); + private static readonly Regex s_uuidRegex = UuidRegex(); + + [GeneratedRegex(CsrfRegexMask)] + private static partial Regex CsrfRegex(); + private static readonly Regex s_csrfRegex = CsrfRegex(); +#else + private static readonly Regex s_uuidRegex = new(UuidRegexMask, RegexOptions.Compiled); + private static readonly Regex s_csrfRegex = new(CsrfRegexMask, RegexOptions.Compiled); +#endif + + protected override RequestResponse DeserializeMessage(NameValueCollection responseHeaders, string responseText) { - var matchCsrf = Regex.Match(responseText, @"""csrf"":""(?.*?)"""); - var matchUuid = Regex.Match(responseText, @"""process_uuid"":""(?.*?)"""); //var matchUuid = Regex.Match(responseText, @"process_uuid(?\S+?)""); + var matchCsrf = s_csrfRegex.Match(responseText); + var matchUuid = s_uuidRegex.Match(responseText); + //var matchUuid = Regex.Match(responseText, @"process_uuid(?\S+?)""); var msg = new RequestResponse { @@ -38,7 +55,7 @@ protected override RequestResponse DeserializeMessa Result = new YadAuthPreAuthRequestResult { Csrf = matchCsrf.Success ? matchCsrf.Groups["csrf"].Value : string.Empty, - ProcessUUID = matchUuid.Success ? matchUuid.Groups["uuid"].Value : string.Empty + ProcessUuid = matchUuid.Success ? matchUuid.Groups["uuid"].Value : string.Empty } }; @@ -49,6 +66,6 @@ protected override RequestResponse DeserializeMessa class YadAuthPreAuthRequestResult { public string Csrf { get; set; } - public string ProcessUUID { get; set; } + public string ProcessUuid { get; set; } } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebAuth.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebAuth.cs index e35c05e7..dc24ff72 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebAuth.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebAuth.cs @@ -29,12 +29,12 @@ public async Task MakeLogin(SemaphoreSlim connectionLimiter) .MakeRequestAsync(connectionLimiter); if (string.IsNullOrWhiteSpace(preAuthResult.Csrf)) throw new AuthenticationException($"{nameof(YadPreAuthRequest)} error parsing csrf"); - if (string.IsNullOrWhiteSpace(preAuthResult.ProcessUUID)) + if (string.IsNullOrWhiteSpace(preAuthResult.ProcessUuid)) throw new AuthenticationException($"{nameof(YadPreAuthRequest)} error parsing ProcessUUID"); - Uuid = preAuthResult.ProcessUUID; + Uuid = preAuthResult.ProcessUuid; - var loginAuth = await new YadAuthLoginRequest(_settings, this, preAuthResult.Csrf, preAuthResult.ProcessUUID) + var loginAuth = await new YadAuthLoginRequest(_settings, this, preAuthResult.Csrf, preAuthResult.ProcessUuid) .MakeRequestAsync(connectionLimiter); if (loginAuth.HasError) throw new AuthenticationException($"{nameof(YadAuthLoginRequest)} error"); diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/YadWebAuth.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/YadWebAuth.cs index 5322ff2d..6d80c7f6 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/YadWebAuth.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/YadWebAuth.cs @@ -92,7 +92,7 @@ public YadWebAuth(SemaphoreSlim connectionLimiter, HttpCommonSettings settings, throw new InvalidCredentialException(txt); } - catch (Exception ex) + catch (Exception) { Logger.Error("Browser authentication failed! " + "Check the URL and the password for browser authentication component!"); diff --git a/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/CacheStream.cs b/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/CacheStream.cs index 920d7c78..ad03557a 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/CacheStream.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/CacheStream.cs @@ -38,7 +38,7 @@ private DataCache GetCache() foreach (var rule in _deduplicateRules.Rules) { if ( - (rule.MaxSize == 0 || rule.MaxSize > _file.Size) && + (rule.MaxSize == 0 || rule.MaxSize > _file.Size) && _file.Size >= rule.MinSize && (string.IsNullOrEmpty(rule.Target) || Regex.Match(_file.FullPath, rule.Target).Success) ) { diff --git a/MailRuCloud/MailRuCloudApi/Cloud.cs b/MailRuCloud/MailRuCloudApi/Cloud.cs index df470ee4..49c40119 100644 --- a/MailRuCloud/MailRuCloudApi/Cloud.cs +++ b/MailRuCloud/MailRuCloudApi/Cloud.cs @@ -26,7 +26,7 @@ namespace YaR.Clouds /// /// Cloud client. /// - public class Cloud : IDisposable + public partial class Cloud : IDisposable { private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(Cloud)); @@ -163,9 +163,16 @@ public virtual Task GetItemAsync(string path, ItemType itemType = ItemTy } } + private const string MailRuPublicRegexMask = @"\A/(?https://cloud\.mail\.\w+/public/\S+/\S+(/.*)?)\Z"; + +#if NET7_0_OR_GREATER + [GeneratedRegex(MailRuPublicRegexMask, RegexOptions.IgnoreCase | RegexOptions.Singleline)] + private static partial Regex MailRuPublicRegex(); + private static readonly Regex _mailRegex = MailRuPublicRegex(); +#else private static readonly Regex _mailRegex = - new Regex(@"\A/(?https://cloud\.mail\.\w+/public/\S+/\S+(/.*)?)\Z", - RegexOptions.IgnoreCase | RegexOptions.Singleline); + new Regex(MailRuPublicRegexMask, RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.Compiled); +#endif /// /// Данный метод запускается для каждого path только в одном потоке. @@ -1225,12 +1232,12 @@ public async Task DownloadFileAsString(string path) } catch (Exception e) when ( // let's check if there really no file or just other network error - (e is AggregateException && + e is AggregateException && e.InnerException is WebException we && - we.Response is HttpWebResponse { StatusCode: HttpStatusCode.NotFound }) + we.Response is HttpWebResponse { StatusCode: HttpStatusCode.NotFound } || - (e is WebException wee && - wee.Response is HttpWebResponse { StatusCode: HttpStatusCode.NotFound }) + e is WebException wee && + wee.Response is HttpWebResponse { StatusCode: HttpStatusCode.NotFound } ) { return null; diff --git a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/FishCommand.cs b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/FishCommand.cs index fe195a40..f4fdfc40 100644 --- a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/FishCommand.cs +++ b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/FishCommand.cs @@ -15,7 +15,7 @@ public class FishCommand : SpecialCommand { private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(FishCommand)); - public FishCommand(Cloud cloud, string path, IList parames) : base(cloud, path, parames) + public FishCommand(Cloud cloud, string path, IList parameters) : base(cloud, path, parameters) { } @@ -48,7 +48,7 @@ public override async Task Execute() Logger.Warn("╚═╝╚═╩══╩═╝╚═╝╚╩╩╝"); Logger.Warn(""); Logger.Warn("¦̵̱ ̵̱ ̵̱ ̵̱ ̵̱(̢ ̡͇̅└͇̅┘͇̅ (▤8כ−◦"); - + } } catch (Exception) diff --git a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/JoinCommand.cs b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/JoinCommand.cs index 41a08397..eb508cb7 100644 --- a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/JoinCommand.cs +++ b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/JoinCommand.cs @@ -7,18 +7,37 @@ namespace YaR.Clouds.SpecialCommands.Commands { - public class JoinCommand: SpecialCommand + public partial class JoinCommand: SpecialCommand { - public JoinCommand(Cloud cloud, string path, IList parames): base(cloud, path, parames) + private const string CommandRegexMask = @"(?snx-) (https://?cloud.mail.ru/public)?(?/\w*/?\w*)/?\s*"; + private const string HashRegexMask = @"#(?\w+)"; + private const string SizeRegexMask = @"(?\w+)"; +#if NET7_0_OR_GREATER + [GeneratedRegex(CommandRegexMask)] + private static partial Regex CommandRegex(); + private static readonly Regex s_commandRegex = CommandRegex(); + [GeneratedRegex(HashRegexMask)] + private static partial Regex HashRegex(); + private static readonly Regex s_hashRegex = HashRegex(); + [GeneratedRegex(SizeRegexMask)] + private static partial Regex SizeRegex(); + private static readonly Regex s_sizeRegex = SizeRegex(); +#else + private static readonly Regex s_commandRegex = new(CommandRegexMask, RegexOptions.Compiled); + private static readonly Regex s_hashRegex = new(HashRegexMask, RegexOptions.Compiled); + private static readonly Regex s_sizeRegex = new(SizeRegexMask, RegexOptions.Compiled); +#endif + + public JoinCommand(Cloud cloud, string path, IList parameters): base(cloud, path, parameters) { - var m = Regex.Match(Parames[0], @"(?snx-) (https://?cloud.mail.ru/public)?(?/\w*/?\w*)/?\s*"); + var m = s_commandRegex.Match(Parames[0]); if (m.Success) //join by shared link _func = () => ExecuteByLink(Path, m.Groups["data"].Value); - else + else { - var mhash = Regex.Match(Parames[0], @"#(?\w+)"); - var msize = Regex.Match(Parames[1], @"(?\w+)"); + var mhash = s_hashRegex.Match(Parames[0]); + var msize = s_sizeRegex.Match(Parames[1]); if (mhash.Success && msize.Success && Parames.Count == 3) //join by hash and size { _func = () => ExecuteByHash(Path, mhash.Groups["data"].Value, long.Parse(Parames[1]), Parames[2]); @@ -32,8 +51,8 @@ public JoinCommand(Cloud cloud, string path, IList parames): base(cloud, public override Task Execute() { - return _func != null - ? _func() + return _func != null + ? _func() : Task.FromResult(new SpecialCommandResult(false, "Invalid parameters")); } diff --git a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/SharedFolderLinkCommand.cs b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/SharedFolderLinkCommand.cs index 420c598e..3c0d6b8e 100644 --- a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/SharedFolderLinkCommand.cs +++ b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/SharedFolderLinkCommand.cs @@ -8,7 +8,7 @@ namespace YaR.Clouds.SpecialCommands.Commands { - public class SharedFolderLinkCommand : SpecialCommand + public partial class SharedFolderLinkCommand : SpecialCommand { public SharedFolderLinkCommand(Cloud cloud, string path, IList parameters) : base(cloud, path, parameters) @@ -17,9 +17,20 @@ public SharedFolderLinkCommand(Cloud cloud, string path, IList parameter protected override MinMax MinMaxParamsCount { get; } = new(1, 2); + + private const string CommandRegexMask = @"(?snx-)\s* (?(https://?cloud.mail.ru/public)?.*)/? \s*"; +#if NET7_0_OR_GREATER + [GeneratedRegex(CommandRegexMask)] + private static partial Regex CommandRegex(); + private static readonly Regex s_commandRegex = CommandRegex(); +#else + private static readonly Regex s_commandRegex = new(CommandRegexMask, RegexOptions.Compiled); +#endif + + public override async Task Execute() { - var m = Regex.Match(Parames[0], @"(?snx-)\s* (?(https://?cloud.mail.ru/public)?.*)/? \s*"); + var m = s_commandRegex.Match(Parames[0]); if (!m.Success) return SpecialCommandResult.Fail; diff --git a/MailRuCloud/MailRuCloudApi/SpecialCommands/SpecialCommandFabric.cs b/MailRuCloud/MailRuCloudApi/SpecialCommands/SpecialCommandFabric.cs index b819dbf8..7b207c49 100644 --- a/MailRuCloud/MailRuCloudApi/SpecialCommands/SpecialCommandFabric.cs +++ b/MailRuCloud/MailRuCloudApi/SpecialCommands/SpecialCommandFabric.cs @@ -10,7 +10,7 @@ namespace YaR.Clouds.SpecialCommands /// /// /// - public class SpecialCommandFabric + public partial class SpecialCommandFabric { private static readonly List CommandContainers = new() { @@ -156,10 +156,20 @@ private static SpecialCommandContainer FindCommandContainer(ICollection return commandContainer; } + + private const string CommandRegexMask = @"((""((?.*?)(?[\S]+))(\s)*)"; +#if NET7_0_OR_GREATER + [GeneratedRegex(CommandRegexMask)] + private static partial Regex CommandRegex(); + private static readonly Regex s_commandRegex = CommandRegex(); +#else + private static readonly Regex s_commandRegex = new(CommandRegexMask, RegexOptions.Compiled); +#endif + private static List ParseParameters(string paramString) { - var list = Regex - .Matches(paramString, @"((""((?.*?)(?[\S]+))(\s)*)") + var list = s_commandRegex + .Matches(paramString) // ReSharper disable once RedundantEnumerableCastCall .Cast() .Select(m => m.Groups["token"].Value) diff --git a/NWebDav/NWebDav.Server/Helpers/RequestHelper.cs b/NWebDav/NWebDav.Server/Helpers/RequestHelper.cs index d95f2244..4a30af29 100644 --- a/NWebDav/NWebDav.Server/Helpers/RequestHelper.cs +++ b/NWebDav/NWebDav.Server/Helpers/RequestHelper.cs @@ -51,13 +51,19 @@ public class Range /// /// Helper methods for objects. /// - public static class RequestHelper + public static partial class RequestHelper { #if DEBUG - private static readonly NWebDav.Server.Logging.ILogger s_log = NWebDav.Server.Logging.LoggerFactory.CreateLogger(typeof(ResponseHelper)); + private static readonly Logging.ILogger s_log = Logging.LoggerFactory.CreateLogger(typeof(ResponseHelper)); +#endif + private const string RangeRegexMask = "bytes\\=(?[0-9]*)-(?[0-9]*)"; +#if NET7_0_OR_GREATER + [GeneratedRegex(RangeRegexMask)] + private static partial Regex RangeRegex(); + private static readonly Regex s_rangeRegex = RangeRegex(); +#else + private static readonly Regex s_rangeRegex = new(RangeRegexMask, RegexOptions.Compiled); #endif - private static readonly Regex s_rangeRegex = new("bytes\\=(?[0-9]*)-(?[0-9]*)"); - /// /// Split an URI into a collection and name part. /// @@ -102,7 +108,7 @@ public static WebDavUri GetDestinationUri(this IHttpRequest request) // Create the destination URI return destinationHeader.StartsWith("/") ? new WebDavUri(request.Url.BaseUrl, destinationHeader) : new WebDavUri(destinationHeader); - } + } /// /// Obtain the depth value from the request. @@ -140,7 +146,7 @@ public static int GetDepth(this IHttpRequest request) /// /// /// If the Overwrite header is not set, then the specification - /// specifies that it should be interpreted as + /// specifies that it should be interpreted as /// . /// public static bool GetOverwrite(this IHttpRequest request) @@ -171,7 +177,7 @@ public static IList GetTimeouts(this IHttpRequest request) return null; // Return each item - int ParseTimeout(string t) + static int ParseTimeout(string t) { // Check for 'infinite' if (t == "Infinite") @@ -255,8 +261,8 @@ public static Range GetRange(this IHttpRequest request) var endText = match.Groups["end"].Value; var range = new Range { - Start = !string.IsNullOrEmpty(startText) ? (long?)long.Parse(startText) : null, - End = !string.IsNullOrEmpty(endText) ? (long?)long.Parse(endText ) : null + Start = !string.IsNullOrEmpty(startText) ? long.Parse(startText) : null, + End = !string.IsNullOrEmpty(endText) ? long.Parse(endText ) : null }; // Check if we also have an If-Range @@ -282,7 +288,7 @@ public static Range GetRange(this IHttpRequest request) /// /// HTTP request. /// - /// XML document that represents the body content (or + /// XML document that represents the body content (or /// if no body content is specified). /// @@ -324,7 +330,7 @@ public static async Task LoadXmlDocumentAsync(this IHttpRequest reque #endif #if DEBUG // Dump the XML document to the logging - if (xDocument.Root != null && s_log.IsLogEnabled(NWebDav.Server.Logging.LogLevel.Debug)) + if (xDocument.Root != null && s_log.IsLogEnabled(Logging.LogLevel.Debug)) { // Format the XML document as an in-memory text representation using (var ms = new MemoryStream()) @@ -348,7 +354,7 @@ public static async Task LoadXmlDocumentAsync(this IHttpRequest reque // Log the XML text to the logging var reader = new StreamReader(ms); - s_log.Log(NWebDav.Server.Logging.LogLevel.Debug, () => reader.ReadToEnd()); + s_log.Log(Logging.LogLevel.Debug, () => reader.ReadToEnd()); } } #endif diff --git a/NWebDav/NWebDav.Server/Props/DavTypedProperties.cs b/NWebDav/NWebDav.Server/Props/DavTypedProperties.cs index 90103aef..d63677af 100644 --- a/NWebDav/NWebDav.Server/Props/DavTypedProperties.cs +++ b/NWebDav/NWebDav.Server/Props/DavTypedProperties.cs @@ -17,7 +17,7 @@ namespace NWebDav.Server.Props /// CLR type. /// /// - /// A dedicated converter should be implemented to convert the property + /// A dedicated converter should be implemented to convert the property /// value to/from an XML value. This class supports both synchronous and /// asynchronous accessor methods. To improve scalability, it is /// recommended to use the asynchronous methods for properties that require @@ -55,7 +55,7 @@ public interface IConverter /// compatible with the requesting WebDAV client. /// object ToXml(IHttpContext httpContext, TType value); - + /// /// Get the typed value of the specified XML representation. /// @@ -102,7 +102,7 @@ public Func Getter base.GetterAsync = (c, s) => { var v = _getter(c, s); - + //return Task.FromResult(Converter != null ? Converter.ToXml(c, v) : v); return new ValueTask(Converter != null ? Converter.ToXml(c, v) : v); }; @@ -168,19 +168,29 @@ public Func Setter /// /// Store item or collection to which this DAV property applies. /// - public abstract class DavRfc1123Date : DavTypedProperty where TEntry : IStoreItem + public abstract partial class DavRfc1123Date : DavTypedProperty where TEntry : IStoreItem { + private const string PropRegexMask = + @"(?\d{1,2})(-|\s+)(?\d\d)(-|\s)(?\d\d\d\d)(-|\s)(?\d\d):(?\d\d):?(?\d\d)?"; +#if NET7_0_OR_GREATER + [GeneratedRegex(PropRegexMask)] + private static partial Regex PropRegex(); + private static readonly Regex s_propRegex = PropRegex(); +#else + private static readonly Regex s_propRegex = new(PropRegexMask, RegexOptions.Compiled); +#endif + private class Rfc1123DateConverter : IConverter { public object ToXml(IHttpContext httpContext, DateTime value) => value.ToString("R", CultureInfo.InvariantCulture); public DateTime FromXml(IHttpContext httpContext, object value) { - bool parsed = DateTime.TryParse((string) value, CultureInfo.InvariantCulture, DateTimeStyles.None, out var date); + string stringValue = (string)value; + bool parsed = DateTime.TryParse(stringValue, CultureInfo.InvariantCulture, DateTimeStyles.None, out var date); if (!parsed) { // try to fix wrong datetime, for example, Far+NetBox send "0023, 23 11 2017 21:0223 'GMT'" - var m = Regex.Match((string) value, - @"(?\d{1,2})(-|\s+)(?\d\d)(-|\s)(?\d\d\d\d)(-|\s)(?\d\d):(?\d\d):?(?\d\d)?"); + var m = s_propRegex.Match(stringValue); if (m.Success) { int year = int.Parse(m.Groups["year"].Value), @@ -193,7 +203,7 @@ public DateTime FromXml(IHttpContext httpContext, object value) date = new DateTime(year, month, day, hour, min, sec).ToLocalTime(); } else - throw new FormatException($"\"{(string)value}\" does not contain a valid string representation of a date and time."); + throw new FormatException($"\"{stringValue}\" does not contain a valid string representation of a date and time."); } return date; } @@ -226,7 +236,7 @@ public object ToXml(IHttpContext httpContext, DateTime value) if (HasIso8601FractionBug(httpContext)) { // We need to recreate the date again, because the Windows 7 - // WebDAV client cannot + // WebDAV client cannot var dt = new DateTime(value.Year, value.Month, value.Day, value.Hour, value.Minute, value.Second, value.Millisecond, DateTimeKind.Utc); return XmlConvert.ToString(dt, XmlDateTimeSerializationMode.Utc); } diff --git a/NWebDav/NWebDav.Server/WebDavUri.cs b/NWebDav/NWebDav.Server/WebDavUri.cs index 7c424166..6d050824 100644 --- a/NWebDav/NWebDav.Server/WebDavUri.cs +++ b/NWebDav/NWebDav.Server/WebDavUri.cs @@ -14,13 +14,13 @@ public WebDavUri(string url) } /// - /// + /// /// /// - /// encoded webdav path - public WebDavUri(string baseUrl, string relaUrl) + /// encoded webdav path + public WebDavUri(string baseUrl, string relativeUrl) { - AbsoluteUri = baseUrl + relaUrl; + AbsoluteUri = baseUrl + relativeUrl; //_fakeurl = new Uri(AbsoluteUri); } @@ -34,7 +34,7 @@ public WebDavUri(string baseUrl, string relaUrl) public string OriginalString => AbsoluteUri; - public string Scheme + public string Scheme { get { @@ -59,7 +59,7 @@ public string Path get { if (string.IsNullOrEmpty(_path)) - { + { var z = PathEncoded; if (string.IsNullOrEmpty(z) || z == "/") _path = z; @@ -140,7 +140,7 @@ public UriAndName Parent Parent = new WebDavUri(trimmedUri.Substring(0, slashOffset)), Name = Uri.UnescapeDataString(trimmedUri.Substring(slashOffset + 1)) }; - + } } diff --git a/WebDAV.Uploader/UploadStub.cs b/WebDAV.Uploader/UploadStub.cs index d527ad80..0a43d2f5 100644 --- a/WebDAV.Uploader/UploadStub.cs +++ b/WebDAV.Uploader/UploadStub.cs @@ -6,8 +6,17 @@ namespace YaR.CloudMailRu.Client.Console { - static class UploadStub + static partial class UploadStub { + private const string UploadRegexMask = @"\A\\\\\\.*?\\.*?\\"; +#if NET7_0_OR_GREATER + [GeneratedRegex(UploadRegexMask)] + private static partial Regex UploadRegex(); + private static readonly Regex s_uploadRegex = UploadRegex(); +#else + private static readonly Regex s_uploadRegex = new(UploadRegexMask, RegexOptions.Compiled); +#endif + public static int Upload(UploadOptions cmdOptions) { string user = cmdOptions.Login; @@ -18,9 +27,7 @@ public static int Upload(UploadOptions cmdOptions) if (targetpath.StartsWith(@"\\\")) -#pragma warning disable SYSLIB1045 // Convert to 'GeneratedRegexAttribute'. - targetpath = Regex.Replace(targetpath, @"\A\\\\\\.*?\\.*?\\", @"\"); -#pragma warning restore SYSLIB1045 // Convert to 'GeneratedRegexAttribute'. + targetpath = s_uploadRegex.Replace(targetpath, @"\"); targetpath = WebDavPath.Clean(targetpath); diff --git a/YandexAuthBrowser/AuthForm.cs b/YandexAuthBrowser/AuthForm.cs index 0223f66c..4d30e858 100644 --- a/YandexAuthBrowser/AuthForm.cs +++ b/YandexAuthBrowser/AuthForm.cs @@ -6,6 +6,15 @@ namespace YandexAuthBrowser { public partial class AuthForm : Form { + [GeneratedRegex("\\\\?\"sk\\\\?\":\\\\?\"(?.*?)\\\\?\"")] + private static partial Regex SkRegex(); + + [GeneratedRegex("\\\\?\"yandexuid\\\\?\":\\\\?\"(?.*?)\\\\?\"")] + private static partial Regex UuidRegex(); + + [GeneratedRegex("\\\\?\"login\\\\?\":\\\\?\"(?.*?)\\\\?\"")] + private static partial Regex LoginRegex(); + private readonly string? DesiredLogin; private readonly BrowserAppResponse Response; private bool WeAreFinished; @@ -97,9 +106,9 @@ private async void WebView_NavigationCompleted(object sender, Microsoft.Web.WebV var htmlEncoded = await WebView.CoreWebView2.ExecuteScriptAsync("document.body.outerHTML"); var html = JsonDocument.Parse(htmlEncoded).RootElement.ToString(); - var matchSk = Regex.Match(html, @"\\?""sk\\?"":\\?""(?.*?)\\?"""); - var matchUuid = Regex.Match(html, @"\\?""yandexuid\\?"":\\?""(?.*?)\\?"""); - var matchLogin = Regex.Match(html, @"\\?""login\\?"":\\?""(?.*?)\\?"""); + var matchSk = SkRegex().Match(html); + var matchUuid = UuidRegex().Match(html); + var matchLogin = LoginRegex().Match(html); var sk = matchSk.Success ? matchSk.Groups["sk"].Value : string.Empty; var uuid = matchUuid.Success ? matchUuid.Groups["uuid"].Value : string.Empty; diff --git a/YandexAuthBrowser/ResidentForm.cs b/YandexAuthBrowser/ResidentForm.cs index cf2f7f7e..eedf7c9d 100644 --- a/YandexAuthBrowser/ResidentForm.cs +++ b/YandexAuthBrowser/ResidentForm.cs @@ -13,8 +13,9 @@ namespace YandexAuthBrowser { public partial class ResidentForm : Form { - private static readonly Regex UrlRegex = - new Regex("http://[^/]*/(?.*?)/(?.*?)/", RegexOptions.Compiled | RegexOptions.IgnoreCase); + [GeneratedRegex("http://[^/]*/(?.*?)/(?.*?)/", RegexOptions.IgnoreCase)] + private static partial Regex CompiledUrlRegex(); + private static readonly Regex UrlRegex = CompiledUrlRegex(); private HttpListener? Listener; private bool RunServer = false; From 3a42431d07f48ca150835195c97a1d64ed6ada3c Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Fri, 17 Nov 2023 23:34:50 +0300 Subject: [PATCH 44/77] =?UTF-8?q?=D0=98=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F:=201)=20=D0=97=D0=B0=D0=BC=D0=B5=D0=BD=D0=BE?= =?UTF-8?q?=D0=B9=20ClientId=20=D0=BD=D0=B0=20cloud-win=20=D0=B2=D0=BE?= =?UTF-8?q?=D1=81=D1=81=D1=82=D0=B0=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D0=B0=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=20=D1=81=20Mail.R?= =?UTF-8?q?u.=20=D0=97=D0=B0=20=D1=81=D1=87=D0=B5=D1=82=20=D0=BE=D0=B1?= =?UTF-8?q?=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD=D0=BD=D0=BE=D0=B3=D0=BE=20?= =?UTF-8?q?=D0=BA=D0=B5=D1=88=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=20=D1=81=20=D0=BE?= =?UTF-8?q?=D0=B1=D0=BB=D0=B0=D0=BA=D0=BE=D0=BC=20=D1=83=D1=81=D0=BA=D0=BE?= =?UTF-8?q?=D1=80=D0=B8=D0=BB=D0=B0=D1=81=D1=8C.=202)=20=D0=95=D1=81=D0=BB?= =?UTF-8?q?=D0=B8=20=D1=8F=D0=B2=D0=BD=D0=BE=20=D0=BD=D0=B5=20=D0=B7=D0=B0?= =?UTF-8?q?=D0=B4=D0=B0=D0=BD=D0=BE=20=D0=B2=20=D0=BF=D0=B0=D1=80=D0=B0?= =?UTF-8?q?=D0=BC=D0=B5=D1=82=D1=80=D0=BE=D0=BC=20=D0=BF=D1=80=D0=BE=D0=B3?= =?UTF-8?q?=D1=80=D0=B0=D0=BC=D0=BC=D1=8B=20=D0=B4=D0=BB=D1=8F=20Mail.Ru?= =?UTF-8?q?=20=D0=B8=D1=81=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D1=83=D0=B5=D1=82?= =?UTF-8?q?=D1=81=D1=8F=20=D0=BF=D1=80=D0=BE=D1=82=D0=BE=D0=BA=D0=BE=D0=BB?= =?UTF-8?q?=20WebM1Bin.=203)=20=D0=98=D1=81=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20=D0=B0=D1=83=D1=82=D0=B5?= =?UTF-8?q?=D0=BD=D1=82=D0=B8=D1=84=D0=B8=D0=BA=D0=B0=D1=86=D0=B8=D0=B8=20?= =?UTF-8?q?=D1=87=D0=B5=D1=80=D0=B5=D0=B7=20=D0=B1=D1=80=D0=B0=D1=83=D0=B7?= =?UTF-8?q?=D0=B5=D1=80=20=D1=82=D0=B5=D0=BF=D0=B5=D1=80=D1=8C=20=D0=BD?= =?UTF-8?q?=D0=B5=20=D0=BE=D1=82=D0=B4=D0=B5=D0=BB=D1=8C=D0=BD=D1=8B=D0=B9?= =?UTF-8?q?=20=D0=BF=D1=80=D0=BE=D1=82=D0=BE=D0=BA=D0=BE=D0=BB,=20=D0=B0?= =?UTF-8?q?=20=D1=87=D0=B0=D1=81=D1=82=D1=8C=20Credentials=20-=20!=20?= =?UTF-8?q?=D0=BF=D0=B5=D1=80=D0=B5=D0=B4=20email=20=D0=BB=D0=BE=D0=B3?= =?UTF-8?q?=D0=B8=D0=BD=D0=B0=20=D0=B7=D0=B0=D0=BF=D1=80=D0=B5=D1=89=D0=B0?= =?UTF-8?q?=D0=B5=D1=82=20=D0=B0=D1=83=D1=82=D0=B5=D0=BD=D1=82=D0=B8=D1=84?= =?UTF-8?q?=D0=B8=D0=BA=D0=B0=D1=86=D0=B8=D1=8E=20=D0=B1=D1=80=D0=B0=D1=83?= =?UTF-8?q?=D0=B7=D0=B5=D1=80=D0=BE=D0=BC,=20=3F=20=D1=82=D1=80=D0=B5?= =?UTF-8?q?=D0=B1=D1=83=D0=B5=D1=82=20=D0=B0=D1=83=D1=82=D0=B5=D0=BD=D1=82?= =?UTF-8?q?=D0=B8=D1=84=D0=B8=D0=BA=D0=B0=D1=86=D0=B8=D1=8E=20=D0=B1=D1=80?= =?UTF-8?q?=D0=B0=D1=83=D0=B7=D0=B5=D1=80=D0=BE=D0=BC.=20=D0=95=D1=81?= =?UTF-8?q?=D0=BB=D0=B8=20=D0=B7=D0=BD=D0=B0=D0=BA=D0=B0=20=D0=BD=D0=B5?= =?UTF-8?q?=D1=82,=20=D0=BD=D0=BE=20=D0=BF=D0=B0=D1=80=D0=BE=D0=BB=D1=8C?= =?UTF-8?q?=20=D1=81=D0=BE=D0=B2=D0=BF=D0=B0=D0=B4=D0=B0=D0=B5=D1=82=20?= =?UTF-8?q?=D1=81=20=D0=BF=D0=B0=D1=80=D0=BE=D0=BB=D0=B5=D0=BC=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=B4=D0=BA=D0=BB=D1=8E=D1=87=D0=B5=D0=BD=D0=B8=D1=8F=20?= =?UTF-8?q?=D0=BA=20BrowserAuthenticator,=20=D0=B8=D1=81=D0=BF=D0=BE=D0=BB?= =?UTF-8?q?=D1=8C=D0=B7=D1=83=D0=B5=D1=82=D1=81=D1=8F=20=D0=B0=D1=83=D1=82?= =?UTF-8?q?=D0=B5=D0=BD=D1=82=D0=B8=D1=84=D0=B8=D0=BA=D0=B0=D1=86=D0=B8?= =?UTF-8?q?=D1=8F=20=D1=87=D0=B5=D1=80=D0=B5=D0=B7=20=D0=B1=D1=80=D0=B0?= =?UTF-8?q?=D1=83=D0=B7=D0=B5=D1=80.=204)=20=D0=9F=D1=80=D0=BE=D1=82=D0=BE?= =?UTF-8?q?=D0=BA=D0=BE=D0=BB=20YadWebV2=20=D0=BE=D0=B1=D1=8A=D0=B5=D0=B4?= =?UTF-8?q?=D0=B8=D0=BD=D0=B5=D0=BD=20=D0=B2=20YadWeb.=20=D0=9E=D1=82?= =?UTF-8?q?=D0=BB=D0=B8=D1=87=D0=B8=D0=B5=20=D0=B2=20=D1=81=D0=BF=D0=BE?= =?UTF-8?q?=D1=81=D0=BE=D0=B1=D0=B5=20=D0=B0=D1=83=D1=82=D0=B5=D0=BD=D1=82?= =?UTF-8?q?=D0=B8=D1=84=D0=B8=D0=BA=D0=B0=D1=86=D0=B8=D0=B8,=20=D0=B2=20?= =?UTF-8?q?=D0=BE=D0=B6=D0=B8=D0=B4=D0=B0=D0=BD=D0=B8=D0=B8=20=D0=B7=D0=B0?= =?UTF-8?q?=D0=B2=D0=B5=D1=80=D1=88=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BE=D0=BF?= =?UTF-8?q?=D0=B5=D1=80=D0=B0=D1=86=D0=B8=D0=B8=20=D0=BF=D0=BE=D1=81=D0=BB?= =?UTF-8?q?=D0=B5=20=D0=BE=D0=BF=D0=B5=D1=80=D0=B0=D1=86=D0=B8=D0=B8,=20?= =?UTF-8?q?=D0=B0=20=D0=BD=D0=B5=20=D0=BF=D0=B5=D1=80=D0=B5=D0=B4=20=D1=87?= =?UTF-8?q?=D1=82=D0=B5=D0=BD=D0=B8=D0=B5=D0=BC=20=D0=B4=D0=B8=D1=80=D0=B5?= =?UTF-8?q?=D0=BA=D1=82=D0=BE=D1=80=D0=B8=D0=B8,=20=D0=BF=D0=B0=D1=80?= =?UTF-8?q?=D0=B0=D0=BB=D0=BB=D0=B5=D0=BB=D1=8C=D0=BD=D0=BE=D0=BC=20=D1=87?= =?UTF-8?q?=D1=82=D0=B5=D0=BD=D0=B8=D0=B8=20=D0=B1=D0=BE=D0=BB=D1=8C=D1=88?= =?UTF-8?q?=D0=B8=D1=85=20=D0=B4=D0=B8=D1=80=D0=B5=D0=BA=D1=82=D0=BE=D1=80?= =?UTF-8?q?=D0=B8=D0=B9=20=D0=B8=20=D0=B4=D1=80=D1=83=D0=B3=D0=B8=D1=85=20?= =?UTF-8?q?=C2=AB=D1=84=D0=B8=D1=88=D0=BA=D0=B0=D1=85=C2=BB.=205)=20"?= =?UTF-8?q?=D0=AD=D0=BA=D0=B7=D0=B5=D0=BC=D0=BF=D0=BB=D1=8F=D1=80=20=D0=BE?= =?UTF-8?q?=D0=B1=D0=BB=D0=B0=D0=BA=D0=B0=20=D0=BF=D0=BE=D0=B4=20=D1=83?= =?UTF-8?q?=D1=87=D0=B5=D1=82=D0=BD=D1=83=D1=8E=20=D0=B7=D0=B0=D0=BF=D0=B8?= =?UTF-8?q?=D1=81=D1=8C=20=D1=81=D0=BE=D0=B7=D0=B4=D0=B0=D0=B5=D1=82=D1=81?= =?UTF-8?q?=D1=8F=20=D0=BF=D1=80=D0=B8=20=D0=BF=D0=B5=D1=80=D0=B2=D0=BE?= =?UTF-8?q?=D0=BC=20=D0=BE=D0=B1=D1=80=D0=B0=D1=89=D0=B5=D0=BD=D0=B8=D0=B8?= =?UTF-8?q?.=20=D0=9F=D0=BE=D1=81=D0=BB=D0=B5=20=D0=BF=D1=80=D0=B5=D0=BA?= =?UTF-8?q?=D1=80=D0=B0=D1=89=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=BE=D0=B1=D1=80?= =?UTF-8?q?=D0=B0=D1=89=D0=B5=D0=BD=D0=B8=D0=B9=20=D1=87=D0=B5=D1=80=D0=B5?= =?UTF-8?q?=D0=B7=20=D0=BD=D0=B0=D1=81=D1=82=D1=80=D0=B0=D0=B8=D0=B2=D0=B0?= =?UTF-8?q?=D0=B5=D0=BC=D0=BE=D0=B5=20=D0=B2=D1=80=D0=B5=D0=BC=D1=8F=20?= =?UTF-8?q?=D1=83=D0=B4=D0=B0=D0=BB=D1=8F=D0=B5=D1=82=D1=81=D1=8F=20=D0=B8?= =?UTF-8?q?=D0=B7=20=D0=BF=D0=B0=D0=BC=D1=8F=D1=82=D0=B8.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .editorconfig | 2 +- BrowserAuthenticator/AuthForm.Designer.cs | 177 ++++ BrowserAuthenticator/AuthForm.cs | 318 +++++++ .../AuthForm.resx | 62 +- .../BrowserAuthenticator.csproj | 21 +- BrowserAuthenticator/JsonResult.cs | 43 + BrowserAuthenticator/Program.cs | 14 + BrowserAuthenticator/ResidentForm.Designer.cs | 313 +++++++ BrowserAuthenticator/ResidentForm.cs | 448 ++++++++++ .../ResidentForm.resx | 83 +- BrowserAuthenticator/Resources.Designer.cs | 96 +++ BrowserAuthenticator/Resources.resx | 124 +++ .../files}/cloud.ico | Bin .../files}/copy-files.png | Bin BrowserAuthenticator/files/start.html | 40 + Hasher/Program.cs | 6 +- .../MailRuCloudApi/Base/Credentials.cs | 707 ++++++++++----- MailRuCloud/MailRuCloudApi/Base/CryptInfo.cs | 2 +- MailRuCloud/MailRuCloudApi/Base/CryptoUtil.cs | 2 +- MailRuCloud/MailRuCloudApi/Base/File.cs | 21 +- MailRuCloud/MailRuCloudApi/Base/FileSize.cs | 2 +- .../MailRuCloudApi/Base/FileSplitInfo.cs | 2 +- .../Base/FilenameServiceInfo.cs | 12 +- MailRuCloud/MailRuCloudApi/Base/Folder.cs | 46 +- .../MailRuCloudApi/Base/HashMatchException.cs | 4 +- .../MailRuCloudApi/Base/HeaderFileContent.cs | 2 +- .../MailRuCloudApi/Base/IBasicCredentials.cs | 2 +- MailRuCloud/MailRuCloudApi/Base/Protocol.cs | 19 +- .../MailRuCloudApi/Base/PublicLinkInfo.cs | 6 +- .../Base/Repos/DtoImportExtensions.cs | 2 +- .../MailRuCloudApi/Base/Repos/IAuth.cs | 2 +- .../MailRuCloudApi/Base/Repos/ICloudHasher.cs | 2 +- .../MailRuCloudApi/Base/Repos/IRequestRepo.cs | 4 +- .../Base/Repos/MailRuCloud/DtoImport.cs | 11 +- .../Base/Repos/MailRuCloud/MailRuBaseRepo.cs | 12 +- .../Base/Repos/MailRuCloud/MailRuSha1Hash.cs | 2 +- .../MailRuCloud/Mobile/MobileRequestRepo.cs | 19 +- .../Mobile/Requests/AccountInfoRequest.cs | 4 +- .../Mobile/Requests/BaseRequestMobile.cs | 4 +- .../Mobile/Requests/CreateFolderRequest.cs | 2 +- .../Mobile/Requests/GetServerRequest.cs | 4 +- .../Mobile/Requests/GetUploadServerRequest.cs | 4 +- .../Mobile/Requests/ListRequest.cs | 10 +- .../Mobile/Requests/MobAddFileRequest.cs | 4 +- .../Mobile/Requests/MobMetaServerRequest.cs | 2 +- .../Mobile/Requests/MoveRequest.cs | 2 +- .../Mobile/Requests/OAuthRefreshRequest.cs | 6 +- .../Mobile/Requests/OAuthRequest.cs | 2 +- .../Mobile/Requests/OAuthSecondStepRequest.cs | 7 +- .../Mobile/Requests/ServerRequest.cs | 2 +- .../Requests/SharedFoldersListRequest.cs | 2 +- .../Mobile/Requests/Types/CloudFolderType.cs | 2 +- .../Mobile/Requests/Types/FsFile.cs | 2 +- .../Mobile/Requests/Types/FsFolder.cs | 4 +- .../Mobile/Requests/Types/FsItem.cs | 2 +- .../Mobile/Requests/Types/Operation.cs | 2 +- .../Requests/Types/RequestBodyStream.cs | 4 +- .../Mobile/Requests/Types/Revision.cs | 2 +- .../Requests/Types/RevisionResponseResult.cs | 2 +- .../Mobile/Requests/Types/TreeId.cs | 2 +- .../Requests/WeblinkGetServerRequest.cs | 2 +- .../Base/Repos/MailRuCloud/OAuth.cs | 11 +- .../Base/Repos/MailRuCloud/ShardManager.cs | 4 +- .../WebBin/Requests/DownloadRequest.cs | 15 +- .../MailRuCloud/WebBin/WebBinRequestRepo.cs | 52 +- .../WebM1/Requests/AccountInfoRequest.cs | 2 +- .../WebM1/Requests/CloneItemRequest.cs | 4 +- .../MailRuCloud/WebM1/Requests/CopyRequest.cs | 10 +- .../WebM1/Requests/CreateFileRequest.cs | 4 +- .../WebM1/Requests/CreateFolderRequest.cs | 4 +- .../WebM1/Requests/FolderInfoRequest.cs | 8 +- .../WebM1/Requests/ItemInfoRequest.cs | 10 +- .../MailRuCloud/WebM1/Requests/MoveRequest.cs | 6 +- .../WebM1/Requests/PublishRequest.cs | 8 +- .../WebM1/Requests/RemoveRequest.cs | 5 +- .../WebM1/Requests/RenameRequest.cs | 6 +- .../WebM1/Requests/ShardInfoRequest.cs | 6 +- .../WebM1/Requests/SharedListRequest.cs | 4 +- .../WebM1/Requests/UnpublishRequest.cs | 10 +- .../MailRuCloud/WebM1/WebM1RequestRepo.cs | 53 +- .../WebV2/Requests/AccountInfoRequest.cs | 2 +- .../WebV2/Requests/CloneItemRequest.cs | 4 +- .../WebV2/Requests/CommonSettings.cs | 2 +- .../MailRuCloud/WebV2/Requests/CopyRequest.cs | 8 +- .../WebV2/Requests/CreateFileRequest.cs | 4 +- .../WebV2/Requests/CreateFolderRequest.cs | 2 +- .../WebV2/Requests/DownloadRequest.cs | 14 +- .../WebV2/Requests/DownloadTokenRequest.cs | 2 +- .../WebV2/Requests/EnsureSdcCookieRequest.cs | 2 +- .../WebV2/Requests/FolderInfoRequest.cs | 8 +- .../WebV2/Requests/ItemInfoRequest.cs | 8 +- .../WebV2/Requests/LoginRequest.cs | 4 +- .../MailRuCloud/WebV2/Requests/MoveRequest.cs | 4 +- .../WebV2/Requests/PublishRequest.cs | 4 +- .../WebV2/Requests/RemoveRequest.cs | 2 +- .../WebV2/Requests/RenameRequest.cs | 4 +- .../WebV2/Requests/SecondStepAuthRequest.cs | 4 +- .../WebV2/Requests/ShardInfoRequest.cs | 8 +- .../WebV2/Requests/UnpublishRequest.cs | 4 +- .../WebV2/Requests/UploadRequest.cs | 12 +- .../Base/Repos/MailRuCloud/WebV2/WebAuth.cs | 4 +- .../MailRuCloud/WebV2/WebV2RequestRepo.cs | 41 +- .../MailRuCloudApi/Base/Repos/RemotePath.cs | 4 +- .../MailRuCloudApi/Base/Repos/RepoFabric.cs | 5 +- .../YandexDisk/YadWeb/DtoImportYadWeb.cs | 64 +- .../YandexDisk/YadWeb/Models/AccountInfo.cs | 4 +- .../Models/ActiveOperations.cs | 47 +- .../YandexDisk/YadWeb/Models/CleanTrash.cs | 2 +- .../Repos/YandexDisk/YadWeb/Models/Copy.cs | 6 +- .../YandexDisk/YadWeb/Models/CreateFolder.cs | 4 +- .../Repos/YandexDisk/YadWeb/Models/Delete.cs | 6 +- .../YandexDisk/YadWeb/Models/FolderInfo.cs | 27 +- .../YadWeb/Models/GetResourceUploadUrl.cs | 6 +- .../YadWeb/Models/GetResourceUrl.cs | 6 +- .../YandexDisk/YadWeb/Models/ItemInfo.cs | 6 +- .../YadWeb/Models/KnownYadModelConverter.cs | 5 +- .../YandexDisk/YadWeb/Models/Media/Albums.cs | 12 +- .../YadWeb/Models/Media/GetAlbumsSlices.cs | 2 +- .../Models/Media/GetClustersWithResources.cs | 15 +- .../YadWeb/Models/Media/InitSnapshot.cs | 4 +- .../Repos/YandexDisk/YadWeb/Models/Move.cs | 6 +- .../YadWeb/Models/OperationStatus.cs | 12 +- .../Repos/YandexDisk/YadWeb/Models/Publish.cs | 4 +- .../Models/YadResourceStatsPostModel.cs | 6 +- .../YadWeb/Requests/YaDCommonRequest.cs | 44 +- .../YadWeb/Requests/YadAuthAccountsRequest.cs | 4 +- .../YadWeb/Requests/YadAuthAskV2Request.cs | 2 +- .../YadWeb/Requests/YadAuthDiskSkRequest.cs | 3 +- .../YadWeb/Requests/YadAuthLoginRequest.cs | 10 +- .../YadWeb/Requests/YadAuthPasswordRequest.cs | 8 +- .../YadWeb/Requests/YadDownloadRequest.cs | 10 +- .../YadWeb/Requests/YadUploadRequest.cs | 2 +- .../Base/Repos/YandexDisk/YadWeb/YadHasher.cs | 2 +- .../Repos/YandexDisk/YadWeb/YadWebAuth.cs | 29 +- .../YandexDisk/YadWeb/YadWebRequestRepo.cs | 224 ++--- .../YandexDisk/YadWeb/YadWebRequestRepo2.cs | 376 ++++++++ .../YandexDisk/YadWebV2/DtoImportYadWeb.cs | 300 ------- .../YandexDisk/YadWebV2/Models/AccountInfo.cs | 42 - .../Repos/YandexDisk/YadWebV2/Models/Base.cs | 120 --- .../YandexDisk/YadWebV2/Models/CleanTrash.cs | 28 - .../Repos/YandexDisk/YadWebV2/Models/Copy.cs | 54 -- .../YadWebV2/Models/CreateFolder.cs | 40 - .../YandexDisk/YadWebV2/Models/Delete.cs | 42 - .../YandexDisk/YadWebV2/Models/FolderInfo.cs | 236 ----- .../YadWebV2/Models/GetResourceUploadUrl.cs | 76 -- .../YadWebV2/Models/GetResourceUrl.cs | 43 - .../YandexDisk/YadWebV2/Models/ItemInfo.cs | 96 --- .../YadWebV2/Models/KnownYadModelConverter.cs | 40 - .../YadWebV2/Models/Media/Albums.cs | 205 ----- .../YadWebV2/Models/Media/GetAlbumsSlices.cs | 53 -- .../Models/Media/GetClustersWithResources.cs | 255 ------ .../YadWebV2/Models/Media/InitSnapshot.cs | 40 - .../Repos/YandexDisk/YadWebV2/Models/Move.cs | 54 -- .../YadWebV2/Models/OperationStatus.cs | 46 - .../YandexDisk/YadWebV2/Models/Publish.cs | 55 -- .../Models/YadResourceStatsPostModel.cs | 50 -- .../YadWebV2/Requests/YaDCommonRequest.cs | 96 --- .../Requests/YadAuthAccountsRequest.cs | 60 -- .../YadWebV2/Requests/YadAuthAskV2Request.cs | 56 -- .../Requests/YadAuthPasswordRequest.cs | 106 --- .../YadWebV2/Requests/YadDownloadRequest.cs | 46 - .../YadWebV2/Requests/YadUploadRequest.cs | 38 - .../Repos/YandexDisk/YadWebV2/YadHasher.cs | 88 -- .../Repos/YandexDisk/YadWebV2/YadWebAuth.cs | 288 ------- .../YandexDisk/YadWebV2/YadWebRequestRepo.cs | 808 ------------------ .../Base/Requests/BaseRequest.cs | 32 +- .../Base/Requests/BaseRequestJson.cs | 4 +- .../Base/Requests/BaseRequestString.cs | 4 +- .../Base/Requests/HttpCommonSettings.cs | 2 +- .../Base/Requests/RequestException.cs | 3 +- .../Base/Requests/Types/AccountInfo.cs | 2 - .../Types/AccountInfoRequestResult.cs | 2 +- .../Base/Requests/Types/ActiveOperation.cs | 2 +- .../Base/Requests/Types/BrowserAppResult.cs | 44 + .../Requests/Types/CommonOperationResult.cs | 2 +- .../Base/Requests/Types/CreateFolderResult.cs | 2 +- .../Requests/Types/DownloadTokenResult.cs | 2 +- .../Base/Requests/Types/ItemOperation.cs | 2 +- .../Base/Requests/Types/LoginResult.cs | 2 +- .../Base/Requests/Types/PublishResult.cs | 2 +- .../Base/Requests/Types/ShardType.cs | 2 +- .../Base/Requests/Types/UnpublishResult.cs | 2 +- .../Base/Requests/Types/UploadFileResult.cs | 2 +- .../Base/ResolveFileConflictMethod.cs | 2 +- .../MailRuCloudApi/Base/SplittedFile.cs | 4 +- .../Base/Streams/Cache/CacheStream.cs | 2 +- .../Base/Streams/Cache/CacheType.cs | 2 +- .../Base/Streams/Cache/DataCache.cs | 2 +- .../Base/Streams/Cache/DeduplicateRule.cs | 2 +- .../Base/Streams/Cache/DeduplicateRulesBag.cs | 2 +- .../Base/Streams/Cache/DiskDataCache.cs | 2 +- .../Base/Streams/Cache/MemoryDataCache.cs | 2 +- .../Base/Streams/DownloadStream.cs | 4 +- .../Base/Streams/HttpClientFabric.cs | 4 +- .../Base/Streams/RingBufferedStream.cs | 12 +- .../Base/Streams/UploadStreamHttpClient.cs | 24 +- .../Base/Streams/UploadStreamHttpClientV2.cs | 2 +- .../Streams/UploadStreamHttpWebRequest.cs | 2 +- MailRuCloud/MailRuCloudApi/Cloud.cs | 64 +- MailRuCloud/MailRuCloudApi/CloudSettings.cs | 6 +- .../MailRuCloudApi/Common/EntryCache.cs | 65 +- .../MailRuCloudApi/Common/ItemCache.cs | 164 ---- .../Common/SharedVideoResolution.cs | 2 +- MailRuCloud/MailRuCloudApi/CryptFileInfo.cs | 2 +- .../Extensions/EnumExtensions.cs | 6 +- .../MailRuCloudApi/Extensions/Extensions.cs | 138 ++- .../MailRuCloudApi/FileUploadedDelegate.cs | 2 +- .../MailRuCloudApi/Links/Dto/ItemLink.cs | 2 +- .../MailRuCloudApi/Links/Dto/ItemList.cs | 2 +- MailRuCloud/MailRuCloudApi/Links/Link.cs | 3 +- .../MailRuCloudApi/Links/LinkManager.cs | 18 +- .../Commands/CleanTrashCommand.cs | 6 +- .../SpecialCommands/Commands/CopyCommand.cs | 10 +- .../Commands/CryptInitCommand.cs | 12 +- .../Commands/CryptPasswdCommand.cs | 8 +- .../SpecialCommands/Commands/DeleteCommand.cs | 12 +- .../SpecialCommands/Commands/FishCommand.cs | 8 +- .../SpecialCommands/Commands/JoinCommand.cs | 18 +- .../SpecialCommands/Commands/ListCommand.cs | 19 +- .../Commands/LocalToServerCopyCommand.cs | 12 +- .../SpecialCommands/Commands/MoveCommand.cs | 8 +- .../Commands/RemoveBadLinksCommand.cs | 6 +- .../SpecialCommands/Commands/ShareCommand.cs | 26 +- .../Commands/SharedFolderLinkCommand.cs | 18 +- .../SpecialCommands/Commands/TestCommand.cs | 6 +- .../SpecialCommands/SpecialCommand.cs | 18 +- .../SpecialCommands/SpecialCommandFabric.cs | 17 +- .../SpecialCommands/SpecialCommandResult.cs | 2 +- .../Streams/DownloadStreamFabric.cs | 3 +- .../Streams/UploadStreamFabric.cs | 8 +- MailRuCloud/MailRuCloudApi/YaR.Clouds.csproj | 8 - .../AuthCodeConsole.cs | 2 +- .../AuthCodeWindow.cs | 2 +- .../MailRuCloudApi.TwoFA.File/AuthCodeFile.cs | 13 +- WDMRC.Console/CommandLineOptions.cs | 7 +- WDMRC.Console/Config.cs | 6 +- WDMRC.Console/HttpListenerOptions.cs | 4 +- WDMRC.Console/Log4NetAdapter.cs | 2 +- WDMRC.Console/Payload.cs | 49 +- WDMRC.Console/Program.cs | 2 +- WDMRC.Console/ProxyFabric.cs | 6 +- WDMRC.Console/wdmrc.config | 26 +- WebDAV.Uploader/UploadStub.cs | 2 +- WebDAVMailRuCloud.sln | 2 +- .../BrowserAuthenticator.cs | 35 +- WebDavMailRuCloudStore/CloudManager.cs | 175 ++-- .../CustomHandlers/CopyHandler.cs | 2 +- .../CustomHandlers/GetAndHeadHandler.cs | 2 - .../CustomHandlers/MkcolHandler.cs | 2 +- .../CustomHandlers/MoveHandler.cs | 2 +- .../CustomProperties/DavBsiisreadonly.cs | 2 +- .../CustomProperties/DavCollection.cs | 2 +- .../CustomProperties/DavHref.cs | 2 +- .../CustomProperties/DavLastAccessed.cs | 2 +- .../CustomProperties/DavLoctoken.cs | 2 +- .../CustomProperties/DavSharedLink.cs | 3 +- .../CustomProperties/DavSrtfileattributes.cs | 2 +- WebDavMailRuCloudStore/Extensions.cs | 16 +- .../RequestHandlerFactory.cs | 8 +- .../StoreBase/EmptyLockingManager.cs | 6 +- .../StoreBase/LocalStore.cs | 2 +- .../StoreBase/LocalStoreCollection.cs | 6 +- .../StoreBase/LocalStoreCollectionProps.cs | 2 +- .../StoreBase/LocalStoreItem.cs | 24 +- .../StoreBase/LocalStoreItemProps.cs | 4 +- WebDavMailRuCloudStore/TwoFaHandlers.cs | 12 +- YandexAuthBrowser/AuthForm.Designer.cs | 82 -- YandexAuthBrowser/AuthForm.cs | 203 ----- YandexAuthBrowser/JsonResult.cs | 47 - YandexAuthBrowser/Program.cs | 15 - YandexAuthBrowser/ResidentForm.Designer.cs | 243 ------ YandexAuthBrowser/ResidentForm.cs | 398 --------- 272 files changed, 3942 insertions(+), 5866 deletions(-) create mode 100644 BrowserAuthenticator/AuthForm.Designer.cs create mode 100644 BrowserAuthenticator/AuthForm.cs rename {YandexAuthBrowser => BrowserAuthenticator}/AuthForm.resx (74%) rename YandexAuthBrowser/YandexAuthBrowser.csproj => BrowserAuthenticator/BrowserAuthenticator.csproj (68%) create mode 100644 BrowserAuthenticator/JsonResult.cs create mode 100644 BrowserAuthenticator/Program.cs create mode 100644 BrowserAuthenticator/ResidentForm.Designer.cs create mode 100644 BrowserAuthenticator/ResidentForm.cs rename {YandexAuthBrowser => BrowserAuthenticator}/ResidentForm.resx (96%) create mode 100644 BrowserAuthenticator/Resources.Designer.cs create mode 100644 BrowserAuthenticator/Resources.resx rename {YandexAuthBrowser => BrowserAuthenticator/files}/cloud.ico (100%) rename {YandexAuthBrowser => BrowserAuthenticator/files}/copy-files.png (100%) create mode 100644 BrowserAuthenticator/files/start.html rename MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/{YadWebV2 => YadWeb}/Models/ActiveOperations.cs (69%) create mode 100644 MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebRequestRepo2.cs delete mode 100644 MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/DtoImportYadWeb.cs delete mode 100644 MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/AccountInfo.cs delete mode 100644 MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/Base.cs delete mode 100644 MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/CleanTrash.cs delete mode 100644 MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/Copy.cs delete mode 100644 MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/CreateFolder.cs delete mode 100644 MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/Delete.cs delete mode 100644 MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/FolderInfo.cs delete mode 100644 MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/GetResourceUploadUrl.cs delete mode 100644 MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/GetResourceUrl.cs delete mode 100644 MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/ItemInfo.cs delete mode 100644 MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/KnownYadModelConverter.cs delete mode 100644 MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/Media/Albums.cs delete mode 100644 MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/Media/GetAlbumsSlices.cs delete mode 100644 MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/Media/GetClustersWithResources.cs delete mode 100644 MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/Media/InitSnapshot.cs delete mode 100644 MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/Move.cs delete mode 100644 MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/OperationStatus.cs delete mode 100644 MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/Publish.cs delete mode 100644 MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/YadResourceStatsPostModel.cs delete mode 100644 MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YaDCommonRequest.cs delete mode 100644 MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YadAuthAccountsRequest.cs delete mode 100644 MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YadAuthAskV2Request.cs delete mode 100644 MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YadAuthPasswordRequest.cs delete mode 100644 MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YadDownloadRequest.cs delete mode 100644 MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YadUploadRequest.cs delete mode 100644 MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/YadHasher.cs delete mode 100644 MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/YadWebAuth.cs delete mode 100644 MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/YadWebRequestRepo.cs create mode 100644 MailRuCloud/MailRuCloudApi/Base/Requests/Types/BrowserAppResult.cs delete mode 100644 MailRuCloud/MailRuCloudApi/Common/ItemCache.cs delete mode 100644 YandexAuthBrowser/AuthForm.Designer.cs delete mode 100644 YandexAuthBrowser/AuthForm.cs delete mode 100644 YandexAuthBrowser/JsonResult.cs delete mode 100644 YandexAuthBrowser/Program.cs delete mode 100644 YandexAuthBrowser/ResidentForm.Designer.cs delete mode 100644 YandexAuthBrowser/ResidentForm.cs diff --git a/.editorconfig b/.editorconfig index d2a34489..caf0e75b 100644 --- a/.editorconfig +++ b/.editorconfig @@ -3,7 +3,7 @@ root = true [*] vsspell_section_id = b0252c83892c418e88e4674d7eb14041 -vsspell_ignored_words_b0252c83892c418e88e4674d7eb14041 = deduplicate|repo|repos|uninstall|Yad|maxthreads|maxconnections|Auth|Versioning|App|codec|Subentries|Utc|refact|nq|unshare|Deserialize|Configurator|csrf|uuid +vsspell_ignored_words_b0252c83892c418e88e4674d7eb14041 = deduplicate|repo|repos|uninstall|Yad|maxthreads|maxconnections|Auth|Versioning|App|codec|Subentries|Utc|refact|nq|unshare|Deserialize|Configurator|csrf|uuid|uid|Kvp|etime|utime|mtime|ctime|dtime|serializer|Ua|Relogin|dest|dst|weblink|Ycrid|ret|templated|Photounlim|Sha|tsa|crypto|Sdc|Denom|util # C# files [*.cs] diff --git a/BrowserAuthenticator/AuthForm.Designer.cs b/BrowserAuthenticator/AuthForm.Designer.cs new file mode 100644 index 00000000..3a700439 --- /dev/null +++ b/BrowserAuthenticator/AuthForm.Designer.cs @@ -0,0 +1,177 @@ +namespace BrowserAuthenticator; + +partial class AuthForm +{ + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + components = new System.ComponentModel.Container(); + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(AuthForm)); + ShowWindowDelay = new System.Windows.Forms.Timer(components); + NobodyHomeTimer = new System.Windows.Forms.Timer(components); + panel1 = new Panel(); + DescriptionText = new TextBox(); + Deny = new Button(); + UseIt = new Button(); + Back = new Button(); + GotoYandex = new LinkLabel(); + GotoMail = new LinkLabel(); + WebViewPanel = new Panel(); + panel1.SuspendLayout(); + SuspendLayout(); + // + // DelayTimer + // + ShowWindowDelay.Interval = 3000; + ShowWindowDelay.Tick += DelayTimer_Tick; + // + // NobodyHomeTimer + // + NobodyHomeTimer.Interval = 3000; + NobodyHomeTimer.Tick += NobodyHomeTimer_Tick; + // + // panel1 + // + panel1.Controls.Add(DescriptionText); + panel1.Controls.Add(Deny); + panel1.Controls.Add(UseIt); + panel1.Controls.Add(Back); + panel1.Controls.Add(GotoYandex); + panel1.Controls.Add(GotoMail); + panel1.Dock = DockStyle.Top; + panel1.Location = new Point(0, 0); + panel1.Name = "panel1"; + panel1.Size = new Size(1654, 151); + panel1.TabIndex = 0; + // + // DescriptionText + // + DescriptionText.Anchor = AnchorStyles.Top | AnchorStyles.Left | AnchorStyles.Right; + DescriptionText.BackColor = SystemColors.Control; + DescriptionText.BorderStyle = BorderStyle.None; + DescriptionText.Location = new Point(545, 14); + DescriptionText.Multiline = true; + DescriptionText.Name = "DescriptionText"; + DescriptionText.ReadOnly = true; + DescriptionText.ScrollBars = ScrollBars.Vertical; + DescriptionText.Size = new Size(1084, 131); + DescriptionText.TabIndex = 6; + DescriptionText.Text = "Требуется вход для учетной записи ..."; + // + // Deny + // + Deny.Location = new Point(396, 12); + Deny.Name = "Deny"; + Deny.Size = new Size(120, 60); + Deny.TabIndex = 4; + Deny.Text = "Отказать"; + Deny.UseVisualStyleBackColor = true; + Deny.Click += Deny_Click; + // + // UseIt + // + UseIt.Enabled = false; + UseIt.Location = new Point(396, 78); + UseIt.Name = "UseIt"; + UseIt.Size = new Size(120, 60); + UseIt.TabIndex = 5; + UseIt.Text = "Готово"; + UseIt.UseVisualStyleBackColor = true; + UseIt.Click += UseIt_Click; + // + // Back + // + Back.Location = new Point(30, 12); + Back.Name = "Back"; + Back.Size = new Size(120, 77); + Back.TabIndex = 0; + Back.Text = "← Back"; + Back.UseVisualStyleBackColor = true; + Back.Click += Back_Click; + // + // GotoYandex + // + GotoYandex.AutoSize = true; + GotoYandex.Font = new Font("Segoe UI", 12F, FontStyle.Regular, GraphicsUnit.Point); + GotoYandex.Location = new Point(178, 51); + GotoYandex.Name = "GotoYandex"; + GotoYandex.Size = new Size(192, 38); + GotoYandex.TabIndex = 2; + GotoYandex.TabStop = true; + GotoYandex.Text = "disk.yandex.ru"; + GotoYandex.LinkClicked += GotoYandex_LinkClicked; + // + // GotoMail + // + GotoMail.AutoSize = true; + GotoMail.Font = new Font("Segoe UI", 12F, FontStyle.Regular, GraphicsUnit.Point); + GotoMail.Location = new Point(178, 12); + GotoMail.Name = "GotoMail"; + GotoMail.Size = new Size(175, 38); + GotoMail.TabIndex = 1; + GotoMail.TabStop = true; + GotoMail.Text = "cloud.mail.ru"; + GotoMail.LinkClicked += GotoMail_LinkClicked; + // + // WebViewPanel + // + WebViewPanel.BorderStyle = BorderStyle.FixedSingle; + WebViewPanel.Dock = DockStyle.Fill; + WebViewPanel.Location = new Point(0, 151); + WebViewPanel.Name = "WebViewPanel"; + WebViewPanel.Size = new Size(1654, 881); + WebViewPanel.TabIndex = 1; + WebViewPanel.TabStop = true; + // + // AuthForm + // + AutoScaleDimensions = new SizeF(12F, 30F); + AutoScaleMode = AutoScaleMode.Font; + ClientSize = new Size(1654, 1032); + Controls.Add(WebViewPanel); + Controls.Add(panel1); + Icon = (Icon)resources.GetObject("$this.Icon"); + Name = "AuthForm"; + Text = "Сервис WebDavMailRuCloud запрашивает вход в облако"; + FormClosed += AuthForm_FormClosed; + Load += AuthForm_Load; + panel1.ResumeLayout(false); + panel1.PerformLayout(); + ResumeLayout(false); + } + + #endregion + private System.Windows.Forms.Timer ShowWindowDelay; + private System.Windows.Forms.Timer NobodyHomeTimer; + private Panel panel1; + private LinkLabel GotoYandex; + private LinkLabel GotoMail; + private Button Back; + private Panel WebViewPanel; + private Button UseIt; + private Button Deny; + private TextBox DescriptionText; +} \ No newline at end of file diff --git a/BrowserAuthenticator/AuthForm.cs b/BrowserAuthenticator/AuthForm.cs new file mode 100644 index 00000000..ba5455f5 --- /dev/null +++ b/BrowserAuthenticator/AuthForm.cs @@ -0,0 +1,318 @@ +using System.Text; +using System.Text.Json; +using System.Text.RegularExpressions; +using Microsoft.Web.WebView2.Core; +using Microsoft.Web.WebView2.WinForms; + +namespace BrowserAuthenticator; + +public partial class AuthForm : Form +{ + #region private static partial Regex LoginRegex(); + + [GeneratedRegex("\\\\?\"sk\\\\?\":\\\\?\"(?.*?)\\\\?\"")] + private static partial Regex SkRegex(); + + [GeneratedRegex("\\\\?\"yandexuid\\\\?\":\\\\?\"(?.*?)\\\\?\"")] + private static partial Regex UuidRegex(); + + [GeneratedRegex("\\\\?\"login\\\\?\":\\\\?\"(?.*?)\\\\?\"")] + private static partial Regex LoginRegex(); + + #endregion + + private string? _desiredLogin = null; + private readonly BrowserAppResult _response; + private bool _exitting; + //private CoreWebView2ControllerOptions? _options = null; + //private CoreWebView2Controller? _controller = null; + private string _profile; + private bool _isYandexCloud; + private bool _isMailCloud; + private bool _manualCommit; + private string? _html; + private List? _cookieList; + //private Form? _form; + private WebView2? _webView2; + + public AuthForm(string desiredLogin, string profile, bool manualCommit, + BrowserAppResult response, bool isYandexCloud, bool isMailCloud) + { + InitializeComponent(); + + //_form = null; + _webView2 = null; + _html = null; + _cookieList = null; + _manualCommit = manualCommit; + _profile = profile; + _response = response; + _exitting = false; + _isYandexCloud = isYandexCloud; + _isMailCloud = isMailCloud; + _desiredLogin = desiredLogin; + Text = $" WebDavMailRuCloud \x2022 : {_profile}"; + + StringBuilder sb = new StringBuilder(" WebDavMailRuCloud . " + + " , .\r\n"); + if (string.IsNullOrWhiteSpace(desiredLogin)) + { + sb.Append(" ( email) " + + " . " + + " , " + + " ."); + } + else + { + Text += $", : {desiredLogin}"; + + sb.Append($" login ( email): {desiredLogin}. "); + if (_isYandexCloud || _isMailCloud) + { + sb.Append( + " . " + + " ."); + } + else + { + sb.Append( + " , " + + " ."); + } + } + + DescriptionText.Text = sb.ToString(); + + /* + * default , + * , . + * , , + * , + * , , + * . + */ + //#if !DEBUG + if (profile != "default" && (isYandexCloud || isMailCloud)) + { + WindowState = FormWindowState.Normal; + var screen = Screen.GetWorkingArea(this); + Top = screen.Height + 100; + ShowInTaskbar = false; + ShowWindowDelay.Interval = 3000; // 3 + } + else + //#endif + { + + ShowWindow(); + } + + /* + * 4 . + * , , + * , . + */ + NobodyHomeTimer.Interval = 4 * 60_000; // 4 minutes to login + NobodyHomeTimer.Enabled = true; + } + + private void DelayTimer_Tick(object sender, EventArgs e) + { + ShowWindow(); + } + + private void ShowWindow() + { + if (_exitting) + return; + + ShowWindowDelay.Enabled = false; + var screen = Screen.GetWorkingArea(this); + Top = screen.Height / 2 - Height / 2; + WindowState = FormWindowState.Maximized; + ShowInTaskbar = true; + } + + private async Task InitializeAsync() + { + var env = await CoreWebView2Environment.CreateAsync( + userDataFolder: Path.Combine("Cloud accounts !!! KEEP FOLDER SECRET !!!", _profile)); + + _webView2 = new WebView2(); + await _webView2.EnsureCoreWebView2Async(env); + WebViewPanel.Controls.Add(_webView2); + _webView2.Dock = DockStyle.Fill; + _webView2.CoreWebView2.FrameNavigationCompleted += WebView_NavigationCompleted; + + + ShowWindowDelay.Enabled = true; + + if (_isMailCloud) + _webView2.CoreWebView2.Navigate("https://cloud.mail.ru/home"); + else + if (_isYandexCloud) + _webView2.CoreWebView2.Navigate("https://disk.yandex.ru/client/disk"); + else + _webView2.CoreWebView2.NavigateToString(Resources.start); + } + + private void AuthForm_Load(object sender, EventArgs e) + { + // - , + // 5 . , -. + // - . + for (int retry = 5; retry > 0; retry--) + { + try + { + _ = InitializeAsync(); + retry = 0; + } + catch (Exception) + { + } + } + } + + private static string GetNameOnly(string? value) + { + if (string.IsNullOrEmpty(value)) + return string.Empty; + int pos = value.IndexOf('@'); + if (pos == 0) + return string.Empty; + if (pos > 0) + return value.Substring(0, pos); + return value; + } + + private void AuthForm_FormClosed(object sender, FormClosedEventArgs e) + { + try + { + _webView2?.Dispose(); + } + catch { } + } + + private void Quit() + { + _exitting = true; + if (InvokeRequired) + { + Invoke((MethodInvoker)delegate + { + // Running on the UI thread + ShowWindowDelay.Enabled = false; + Close(); + }); + } + else + { + // Running on the UI thread + ShowWindowDelay.Enabled = false; + Close(); + } + } + + private void NobodyHomeTimer_Tick(object sender, EventArgs e) + { + Quit(); + } + + private void Deny_Click(object sender, EventArgs e) + { + Quit(); + } + + private void GotoMail_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) + { + _webView2?.CoreWebView2.Navigate("https://cloud.mail.ru/home"); + } + + private void GotoYandex_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) + { + _webView2?.CoreWebView2.Navigate("https://disk.yandex.ru/client/disk"); + } + + private void Back_Click(object sender, EventArgs e) + { + _webView2?.CoreWebView2.GoBack(); + } + + private async void WebView_NavigationCompleted(object? sender, CoreWebView2NavigationCompletedEventArgs e) + { + string url = e.IsSuccess ? _webView2?.CoreWebView2.Source ?? string.Empty : string.Empty; + _html = null; + _cookieList = null; + + if (e.IsSuccess && + _webView2 is not null && + (url.StartsWith("https://disk.yandex.ru/client/disk") || url.StartsWith("https://cloud.mail.ru/home"))) + { + var htmlEncoded = await _webView2!.CoreWebView2.ExecuteScriptAsync("document.body.outerHTML"); + _html = JsonDocument.Parse(htmlEncoded).RootElement.ToString(); + + _cookieList = await _webView2.CoreWebView2.CookieManager.GetCookiesAsync(url); + + if (!_manualCommit) + Extract(); + } + + UseIt.Enabled = _html is not null; + } + + private void UseIt_Click(object sender, EventArgs e) + { + Extract(); + } + + private void Extract() + { + if (_html is null || _cookieList is null) + return; + + var matchSk = SkRegex().Match(_html); + var matchUuid = UuidRegex().Match(_html); + var matchLogin = LoginRegex().Match(_html); + + var sk = matchSk.Success ? matchSk.Groups["sk"].Value : string.Empty; + var uuid = matchUuid.Success ? matchUuid.Groups["uuid"].Value : string.Empty; + var login = matchLogin.Success ? matchLogin.Groups["login"].Value : string.Empty; + + // Ivan Ivan@yandex.ru, + // , , @ + + if (!string.IsNullOrEmpty(sk) && !string.IsNullOrEmpty(uuid) && + !string.IsNullOrEmpty(login) && + GetNameOnly(login).Equals(GetNameOnly(_desiredLogin), StringComparison.OrdinalIgnoreCase)) + { + _response.Cloud = null; + _response.Login = login; + _response.Sk = sk; + _response.Uuid = uuid; + _response.Cookies = new List(); + + + foreach (var item in _cookieList) + { + BrowserAppCookieResponse cookie = new BrowserAppCookieResponse() + { + Name = item.Name, + Value = item.Value, + Path = item.Path, + Domain = item.Domain + }; + _response.Cookies.Add(cookie); + + _response.Cloud ??= + item.Domain.Contains(".yandex.ru", StringComparison.InvariantCultureIgnoreCase) || + item.Domain.Contains(".ya.ru", StringComparison.InvariantCultureIgnoreCase) + ? "yandex.ru" + : "mail.ru"; + } + + Quit(); + } + } +} diff --git a/YandexAuthBrowser/AuthForm.resx b/BrowserAuthenticator/AuthForm.resx similarity index 74% rename from YandexAuthBrowser/AuthForm.resx rename to BrowserAuthenticator/AuthForm.resx index 7d9d4f9d..2547b992 100644 --- a/YandexAuthBrowser/AuthForm.resx +++ b/BrowserAuthenticator/AuthForm.resx @@ -1,4 +1,64 @@ - + + + diff --git a/YandexAuthBrowser/YandexAuthBrowser.csproj b/BrowserAuthenticator/BrowserAuthenticator.csproj similarity index 68% rename from YandexAuthBrowser/YandexAuthBrowser.csproj rename to BrowserAuthenticator/BrowserAuthenticator.csproj index 1e1fa84e..adaac8ff 100644 --- a/YandexAuthBrowser/YandexAuthBrowser.csproj +++ b/BrowserAuthenticator/BrowserAuthenticator.csproj @@ -7,7 +7,7 @@ enable true enable - cloud.ico + files\cloud.ico $(ReleaseVersion) $(ReleaseVersion) $(ReleaseVersion) @@ -18,13 +18,24 @@ - + + + - - - + + True + True + Resources.resx + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + \ No newline at end of file diff --git a/BrowserAuthenticator/JsonResult.cs b/BrowserAuthenticator/JsonResult.cs new file mode 100644 index 00000000..5322dcca --- /dev/null +++ b/BrowserAuthenticator/JsonResult.cs @@ -0,0 +1,43 @@ +using Newtonsoft.Json; + +namespace BrowserAuthenticator; + +public class BrowserAppResult +{ + [JsonProperty("ErrorMessage")] + public string? ErrorMessage { get; set; } + + [JsonProperty("Login")] + public string? Login { get; set; } + + [JsonProperty("Cloud")] + public string? Cloud { get; set; } + + [JsonProperty("Uuid")] + public string? Uuid { get; set; } + + [JsonProperty("Sk")] + public string? Sk { get; set; } + + [JsonProperty("Cookies")] + public List? Cookies { get; set; } + + public string Serialize() + { + return JsonConvert.SerializeObject(this); + } +} +public class BrowserAppCookieResponse +{ + [JsonProperty("name")] + public string? Name { get; set; } + + [JsonProperty("Value")] + public string? Value { get; set; } + + [JsonProperty("Path")] + public string? Path { get; set; } + + [JsonProperty("Domain")] + public string? Domain { get; set; } +} diff --git a/BrowserAuthenticator/Program.cs b/BrowserAuthenticator/Program.cs new file mode 100644 index 00000000..73914167 --- /dev/null +++ b/BrowserAuthenticator/Program.cs @@ -0,0 +1,14 @@ +namespace BrowserAuthenticator; + +internal static class Program +{ + [STAThread] + static void Main() + { + // To customize application configuration such as set high DPI settings or default font, + // see https://aka.ms/applicationconfiguration. + ApplicationConfiguration.Initialize(); + + Application.Run(new ResidentForm()); + } +} diff --git a/BrowserAuthenticator/ResidentForm.Designer.cs b/BrowserAuthenticator/ResidentForm.Designer.cs new file mode 100644 index 00000000..f602ffc2 --- /dev/null +++ b/BrowserAuthenticator/ResidentForm.Designer.cs @@ -0,0 +1,313 @@ +namespace BrowserAuthenticator; + +partial class ResidentForm +{ + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + components = new System.ComponentModel.Container(); + System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ResidentForm)); + NotifyIcon = new NotifyIcon(components); + HideButton = new Button(); + HideTimer = new System.Windows.Forms.Timer(components); + TestButton = new Button(); + SaveConfigTimer = new System.Windows.Forms.Timer(components); + label3 = new Label(); + Counter = new Label(); + groupBox1 = new GroupBox(); + Lock = new CheckBox(); + CopyPasswordPic = new PictureBox(); + GeneratePassword = new LinkLabel(); + Password = new TextBox(); + label2 = new Label(); + CopyPortPic = new PictureBox(); + Port = new TextBox(); + label1 = new Label(); + ToolTip = new ToolTip(components); + groupBox2 = new GroupBox(); + ManualCommit = new CheckBox(); + TestLogin = new ComboBox(); + label4 = new Label(); + groupBox1.SuspendLayout(); + ((System.ComponentModel.ISupportInitialize)CopyPasswordPic).BeginInit(); + ((System.ComponentModel.ISupportInitialize)CopyPortPic).BeginInit(); + groupBox2.SuspendLayout(); + SuspendLayout(); + // + // NotifyIcon + // + NotifyIcon.BalloonTipIcon = ToolTipIcon.Info; + NotifyIcon.BalloonTipText = "WebDAVCloud для Яндекс.Диск"; + NotifyIcon.BalloonTipTitle = "Резидентная часть для отображения на десктопе окна браузера для входа на Яндекс.Диск"; + NotifyIcon.Icon = (Icon)resources.GetObject("NotifyIcon.Icon"); + NotifyIcon.Text = "WebDAVCloud"; + NotifyIcon.MouseDoubleClick += NotifyIcon_MouseDoubleClick; + // + // HideButton + // + HideButton.Location = new Point(18, 492); + HideButton.Name = "HideButton"; + HideButton.Size = new Size(630, 40); + HideButton.TabIndex = 13; + HideButton.Text = "Свернуть программу в System Tray"; + HideButton.UseVisualStyleBackColor = true; + HideButton.Click += HideButton_Click; + // + // HideTimer + // + HideTimer.Tick += HideTimer_Tick; + // + // TestButton + // + TestButton.Location = new Point(531, 72); + TestButton.Name = "TestButton"; + TestButton.Size = new Size(81, 40); + TestButton.TabIndex = 11; + TestButton.Text = "Test"; + TestButton.UseVisualStyleBackColor = true; + TestButton.Click += TestButton_Click; + // + // SaveConfigTimer + // + SaveConfigTimer.Tick += SaveConfigTimer_Tick; + // + // label3 + // + label3.AutoSize = true; + label3.Location = new Point(53, 535); + label3.Name = "label3"; + label3.Size = new Size(564, 30); + label3.TabIndex = 14; + label3.Text = "Для выхода используйте меню иконки в системном трее"; + // + // Counter + // + Counter.AutoSize = true; + Counter.Location = new Point(18, 448); + Counter.Name = "Counter"; + Counter.Size = new Size(29, 30); + Counter.TabIndex = 12; + Counter.Text = "--"; + // + // groupBox1 + // + groupBox1.Controls.Add(Lock); + groupBox1.Controls.Add(CopyPasswordPic); + groupBox1.Controls.Add(GeneratePassword); + groupBox1.Controls.Add(Password); + groupBox1.Controls.Add(label2); + groupBox1.Controls.Add(CopyPortPic); + groupBox1.Controls.Add(Port); + groupBox1.Controls.Add(label1); + groupBox1.Location = new Point(18, 12); + groupBox1.Name = "groupBox1"; + groupBox1.Size = new Size(630, 250); + groupBox1.TabIndex = 1; + groupBox1.TabStop = false; + // + // Lock + // + Lock.AutoSize = true; + Lock.Checked = true; + Lock.CheckState = CheckState.Checked; + Lock.Cursor = Cursors.Hand; + Lock.Location = new Point(15, 0); + Lock.Name = "Lock"; + Lock.Size = new Size(188, 34); + Lock.TabIndex = 0; + Lock.Text = "Заблокировано"; + ToolTip.SetToolTip(Lock, "Для изменения порта или пароля снимите блокировку"); + Lock.UseVisualStyleBackColor = true; + Lock.CheckedChanged += Lock_CheckedChanged; + // + // CopyPasswordPic + // + CopyPasswordPic.Cursor = Cursors.Hand; + CopyPasswordPic.Image = (Image)resources.GetObject("CopyPasswordPic.Image"); + CopyPasswordPic.InitialImage = (Image)resources.GetObject("CopyPasswordPic.InitialImage"); + CopyPasswordPic.Location = new Point(15, 154); + CopyPasswordPic.Name = "CopyPasswordPic"; + CopyPasswordPic.Size = new Size(35, 35); + CopyPasswordPic.SizeMode = PictureBoxSizeMode.Zoom; + CopyPasswordPic.TabIndex = 16; + CopyPasswordPic.TabStop = false; + CopyPasswordPic.Click += CopyPasswordPic_Click; + // + // GeneratePassword + // + GeneratePassword.AutoSize = true; + GeneratePassword.Location = new Point(56, 192); + GeneratePassword.Name = "GeneratePassword"; + GeneratePassword.Size = new Size(429, 30); + GeneratePassword.TabIndex = 6; + GeneratePassword.TabStop = true; + GeneratePassword.Text = "Сгенерировать пароль в виде нового GUID"; + GeneratePassword.LinkClicked += GeneratePassword_LinkClicked; + // + // Password + // + Password.Enabled = false; + Password.Location = new Point(56, 154); + Password.Name = "Password"; + Password.Size = new Size(556, 35); + Password.TabIndex = 5; + // + // label2 + // + label2.AutoSize = true; + label2.Location = new Point(56, 121); + label2.Name = "label2"; + label2.Size = new Size(358, 30); + label2.TabIndex = 4; + label2.Text = "Пароль для входящего соединения:"; + // + // CopyPortPic + // + CopyPortPic.Cursor = Cursors.Hand; + CopyPortPic.Image = (Image)resources.GetObject("CopyPortPic.Image"); + CopyPortPic.InitialImage = (Image)resources.GetObject("CopyPortPic.InitialImage"); + CopyPortPic.Location = new Point(15, 75); + CopyPortPic.Name = "CopyPortPic"; + CopyPortPic.Size = new Size(35, 35); + CopyPortPic.SizeMode = PictureBoxSizeMode.Zoom; + CopyPortPic.TabIndex = 12; + CopyPortPic.TabStop = false; + CopyPortPic.Click += CopyPortPic_Click; + // + // Port + // + Port.Enabled = false; + Port.Location = new Point(56, 75); + Port.Name = "Port"; + Port.Size = new Size(116, 35); + Port.TabIndex = 3; + Port.Text = "54321"; + Port.Validating += Port_Validating; + // + // label1 + // + label1.AutoSize = true; + label1.Location = new Point(56, 42); + label1.Name = "label1"; + label1.Size = new Size(334, 30); + label1.TabIndex = 2; + label1.Text = "Порт для входящего соединения:"; + // + // groupBox2 + // + groupBox2.Controls.Add(ManualCommit); + groupBox2.Controls.Add(TestLogin); + groupBox2.Controls.Add(label4); + groupBox2.Controls.Add(TestButton); + groupBox2.Location = new Point(18, 268); + groupBox2.Name = "groupBox2"; + groupBox2.Size = new Size(630, 170); + groupBox2.TabIndex = 7; + groupBox2.TabStop = false; + groupBox2.Text = "Проверка"; + ToolTip.SetToolTip(groupBox2, "Задайте логин вида \nlogin@yandex.ru для старта со страницы Яндекс.Диска, \nlogin@mail.ru для старта со страницы Облако Mail.Ru, \n? для общей стартовой страницы."); + // + // ManualCommit + // + ManualCommit.AutoSize = true; + ManualCommit.Location = new Point(15, 118); + ManualCommit.Name = "ManualCommit"; + ManualCommit.Size = new Size(593, 34); + ManualCommit.TabIndex = 10; + ManualCommit.Text = "Только по кнопке «Готов» считать вход осуществлённым"; + ManualCommit.UseVisualStyleBackColor = true; + // + // TestLogin + // + TestLogin.FormattingEnabled = true; + TestLogin.Location = new Point(15, 74); + TestLogin.Name = "TestLogin"; + TestLogin.Size = new Size(470, 38); + TestLogin.TabIndex = 9; + TestLogin.Text = "?"; + // + // label4 + // + label4.AutoSize = true; + label4.Location = new Point(15, 41); + label4.Name = "label4"; + label4.Size = new Size(331, 30); + label4.TabIndex = 8; + label4.Text = "Login (email или учетная запись):"; + // + // ResidentForm + // + AutoScaleDimensions = new SizeF(12F, 30F); + AutoScaleMode = AutoScaleMode.Font; + ClientSize = new Size(666, 576); + Controls.Add(groupBox2); + Controls.Add(groupBox1); + Controls.Add(HideButton); + Controls.Add(Counter); + Controls.Add(label3); + FormBorderStyle = FormBorderStyle.FixedDialog; + Icon = (Icon)resources.GetObject("$this.Icon"); + MaximizeBox = false; + MinimizeBox = false; + Name = "ResidentForm"; + StartPosition = FormStartPosition.CenterScreen; + Text = "WebDavMailRuCloud Bowser Authenticator"; + FormClosing += ResidentForm_FormClosing; + Load += ResidentForm_Load; + Move += ResidentForm_Move; + groupBox1.ResumeLayout(false); + groupBox1.PerformLayout(); + ((System.ComponentModel.ISupportInitialize)CopyPasswordPic).EndInit(); + ((System.ComponentModel.ISupportInitialize)CopyPortPic).EndInit(); + groupBox2.ResumeLayout(false); + groupBox2.PerformLayout(); + ResumeLayout(false); + PerformLayout(); + } + + #endregion + private NotifyIcon NotifyIcon; + private Button HideButton; + private System.Windows.Forms.Timer HideTimer; + private Button TestButton; + private System.Windows.Forms.Timer SaveConfigTimer; + private Label label3; + private Label Counter; + private GroupBox groupBox1; + private PictureBox CopyPasswordPic; + private LinkLabel GeneratePassword; + private TextBox Password; + private Label label2; + private PictureBox CopyPortPic; + private TextBox Port; + private Label label1; + private CheckBox Lock; + private ToolTip ToolTip; + private GroupBox groupBox2; + private Label label4; + private ComboBox TestLogin; + private CheckBox ManualCommit; +} \ No newline at end of file diff --git a/BrowserAuthenticator/ResidentForm.cs b/BrowserAuthenticator/ResidentForm.cs new file mode 100644 index 00000000..e11a5997 --- /dev/null +++ b/BrowserAuthenticator/ResidentForm.cs @@ -0,0 +1,448 @@ +using System.ComponentModel; +using System.Configuration; +using System.Net; +using System.Text; +using System.Text.RegularExpressions; + +/* + * Частично код взят отсюда: + * https://gist.github.com/define-private-public/d05bc52dd0bed1c4699d49e2737e80e7 + */ + +namespace BrowserAuthenticator; + +public partial class ResidentForm : Form +{ + public static readonly string[] YandexDomains = { "yandex", "ya" }; + public static readonly string[] MailDomains = { "mail", "inbox", "bk", "list", "vk", "internet" }; + + [GeneratedRegex("https?://[^/]*/(?.*?)/(?.*?)/", RegexOptions.IgnoreCase)] + private static partial Regex CompiledUrlRegex(); + private static readonly Regex UrlRegex = CompiledUrlRegex(); + + private HttpListener? Listener; + private bool RunServer = false; + private string PreviousPort; + public delegate void Execute(string desiredLogin, BrowserAppResult response); + public Execute AuthExecuteDelegate; + private readonly int? SavedTop = null; + private readonly int? SavedLeft = null; + private SemaphoreSlim _showBrowserLocker; + private bool _doNotSave = false; + private bool _stopUI = false; + + private int AuthenticationOkCounter = 0; + private int AuthenticationFailCounter = 0; + + + public ResidentForm() + { + InitializeComponent(); + + _showBrowserLocker = new SemaphoreSlim(1, 1); + + var screen = Screen.GetWorkingArea(this); + Top = screen.Height + 100; + ShowInTaskbar = false; + + NotifyIcon.Visible = true; + + // Get the current configuration file. + Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None); + + _doNotSave = true; + string? value = config.AppSettings?.Settings?["port"]?.Value; + if (!string.IsNullOrWhiteSpace(value) && int.TryParse(value, out _)) + Port.Text = value; +#if DEBUG + else + Port.Text = "54322"; +#endif + + value = config.AppSettings?.Settings?["password"]?.Value; + if (!string.IsNullOrWhiteSpace(value)) + Password.Text = value; +#if DEBUG + else + Password.Text = "adb4bcd5-b4b6-45b7-bb7d-b38470917448"; +#endif + TestLogin.BeginUpdate(); + value = config.AppSettings?.Settings?["logins"]?.Value ?? string.Empty; + string[] logins = value.Split('/', StringSplitOptions.RemoveEmptyEntries); + foreach (var s in logins) + { + TestLogin.Items.Add(s); + } + TestLogin.EndUpdate(); + + value = config.AppSettings?.Settings?["Top"]?.Value; + if (!string.IsNullOrWhiteSpace(value) && int.TryParse(value, out int top)) + SavedTop = top; + value = config.AppSettings?.Settings?["Left"]?.Value; + if (!string.IsNullOrWhiteSpace(value) && int.TryParse(value, out int left)) + SavedLeft = left; + _doNotSave = false; + + + PreviousPort = Port.Text; + + NotifyIcon.ContextMenuStrip = new ContextMenuStrip(); + NotifyIcon.ContextMenuStrip.Items.Add("Показать окно", null, NotifyIcon_ShowClick); + NotifyIcon.ContextMenuStrip.Items.Add("Выход", null, NotifyIcon_ExitClick); + + AuthExecuteDelegate = OpenDialog; + + Counter.Text = ""; + + _stopUI = true; + Lock.Checked = false; + _stopUI = false; + // Специально провоцируем вызов события для инициализации + Lock.Checked = true; + //StartServer(); + } + + private void ResidentForm_Load(object sender, EventArgs e) + { + Lock.Checked = true; + Lock.Focus(); + HideTimer.Interval = 100; + HideTimer.Enabled = true; + } + private void HideTimer_Tick(object sender, EventArgs e) + { + HideTimer.Enabled = false; + + HideShow(false); + } + + private void HideShow(bool show) + { + if (show) + { + if (!ShowInTaskbar) + { + var screen = Screen.GetWorkingArea(this); + + if (SavedTop.HasValue && SavedLeft.HasValue && + SavedTop.Value >= 0 && SavedTop.Value + Height < screen.Height && + SavedLeft.Value >= 0 && SavedLeft.Value + Width < screen.Width) + + { + Top = SavedTop.Value; + Left = SavedLeft.Value; + } + else + { + Left = screen.Width - Width - 10; + Top = screen.Height - Height - 100; + } + ShowInTaskbar = true; + } + Visible = true; + } + else + { + Visible = false; + } + } + private void ResidentForm_Move(object sender, EventArgs e) + { + if (Visible) + { + SaveConfigTimer.Interval = 1000; + SaveConfigTimer.Enabled = true; + } + } + + private void SaveConfigTimer_Tick(object sender, EventArgs e) + { + if (_doNotSave) + return; + + SaveConfigTimer.Enabled = false; + + Configuration config = + ConfigurationManager.OpenExeConfiguration( + ConfigurationUserLevel.None); + + config.AppSettings.Settings.Remove("Top"); + config.AppSettings.Settings.Remove("Left"); + + config.AppSettings.Settings.Add("Top", Top.ToString()); + config.AppSettings.Settings.Add("Left", Left.ToString()); + + // Save the configuration file. + config.Save(ConfigurationSaveMode.Modified); + } + private void NotifyIcon_MouseDoubleClick(object? sender, MouseEventArgs e) + { + HideShow(!Visible); + } + + /* + * Метод + * ResidentForm_FormClosed( object? sender, FormClosingEventArgs e ) + * здесь не использовать, т.к. событие перекрывается и обрабатывается + * в HiddenContent. См. там. + */ + + private void ResidentForm_FormClosing(object? sender, FormClosingEventArgs e) + { + HideShow(false); + e.Cancel = RunServer ? /*просто закрывается окно*/ true : /*Выход в меню TrayIcon*/ false; + } + private void NotifyIcon_ExitClick(object? sender, EventArgs e) + { + NotifyIcon.Visible = false; + StopServer(); + if (SaveConfigTimer.Enabled) + { + SaveConfigTimer.Enabled = false; + SaveConfig(); + } + + // При вызове Close дальше будет обработка в HiddenContext, см. там. + Close(); + } + + private void NotifyIcon_ShowClick(object? sender, EventArgs e) + { + HideShow(true); + } + + private void HideButton_Click(object sender, EventArgs e) + { + HideShow(false); + } + + private void SaveConfig() + { + if (_doNotSave) + return; + + // Get the current configuration file. + Configuration config = + ConfigurationManager.OpenExeConfiguration( + ConfigurationUserLevel.None); + + config.AppSettings.Settings.Remove("port"); + config.AppSettings.Settings.Remove("password"); + config.AppSettings.Settings.Remove("logins"); + + config.AppSettings.Settings.Add("port", Port.Text); + config.AppSettings.Settings.Add("password", Password.Text); + + string login = TestLogin.Text; + if (!TestLogin.Items.Contains(login)) + TestLogin.Items.Add(login); + + StringBuilder sb = new StringBuilder(); + foreach (var s in TestLogin.Items) + { + if (sb.Length > 0) + sb.Append('/'); + sb.Append(s); + } + config.AppSettings.Settings.Add("logins", sb.ToString()); + + // Save the configuration file. + config.Save(ConfigurationSaveMode.Modified); + } + + private void StartServer() + { + if (!int.TryParse(Port.Text, out int port)) + return; + + try + { + Listener = new HttpListener(); + // Create a http server and start listening for incoming connections + Listener?.Prefixes.Add($"http://localhost:{port}/"); + Listener?.Start(); + RunServer = true; + + // Handle requests + _ = Task.Run(HandleIncomingConnections); + } + catch (Exception ex) + { + MessageBox.Show(ex.Message, + "Ошибка инициализации сервера аутентификации Яндекс.Диска", MessageBoxButtons.OK, MessageBoxIcon.Error); + Application.Exit(); + } + } + + private void StopServer() + { + RunServer = false; + Listener?.Abort(); + Listener?.Close(); + Listener = null; + } + + private void TestButton_Click(object sender, EventArgs e) + { + SaveConfig(); + BrowserAppResult response = new BrowserAppResult(); + OpenDialog(TestLogin.Text, response); + } + + private void OpenDialog(string desiredLogin, BrowserAppResult response) + { + bool isYandexCloud = false; + bool isMailCloud = false; + + foreach (var domain in YandexDomains) + { + isYandexCloud |= desiredLogin != null && desiredLogin.Contains(string.Concat("@", domain, ".ru")); + } + foreach (var domain in MailDomains) + { + isMailCloud |= desiredLogin != null && desiredLogin.Contains(string.Concat("@", domain, ".ru")); + } + + desiredLogin ??= string.Empty; + desiredLogin = string.Concat(desiredLogin.Split(Path.GetInvalidFileNameChars())).Trim(); + string profile = string.IsNullOrWhiteSpace(desiredLogin) ? "default" : desiredLogin; + + // Переключение на поток, обрабатывающий UI. + //System.Threading.SynchronizationContext.Current?.Post( ( _ ) => + //{ + // new AuthForm( desiredLogin, response ).ShowDialog(); + //}, null ); + new AuthForm(desiredLogin, profile, ManualCommit.Checked, response, isYandexCloud, isMailCloud).ShowDialog(); + + if (response.Cookies != null) + AuthenticationOkCounter++; + else + AuthenticationFailCounter++; + + Counter.Text = $"Входов успешных / не успешных : {AuthenticationOkCounter} / {AuthenticationFailCounter}"; + } + + public async Task HandleIncomingConnections() + { + string passwordToCompre = Password.Text; + + while (RunServer) + { + try + { + if (Listener == null) + break; + + // Will wait here until we hear from a connection + HttpListenerContext ctx = await Listener.GetContextAsync(); + + // Peel out the requests and response objects + HttpListenerRequest req = ctx.Request; + using HttpListenerResponse resp = ctx.Response; + + var match = UrlRegex.Match(req.Url?.AbsoluteUri ?? ""); + + var login = Uri.UnescapeDataString(match.Success ? match.Groups["login"].Value : string.Empty); + var password = Uri.UnescapeDataString(match.Success ? match.Groups["password"].Value : string.Empty); + + BrowserAppResult response = new BrowserAppResult(); + + if (string.IsNullOrEmpty(login)) + response.ErrorMessage = "Login is not provided"; + else + if (string.IsNullOrEmpty(password)) + response.ErrorMessage = "Password is not provided"; + else + if (password != passwordToCompre) + response.ErrorMessage = "Password is wrong"; + else + { + _showBrowserLocker.Wait(); + // Окно с браузером нужно открыть в потоке, обрабатывающем UI + if (TestButton.InvokeRequired) + TestButton.Invoke(AuthExecuteDelegate, login, response); + else + AuthExecuteDelegate(login, response); + _showBrowserLocker.Release(); + } + + string text = response.Serialize(); + byte[] data = Encoding.UTF8.GetBytes(text); + resp.ContentType = "application/json"; + resp.ContentEncoding = Encoding.UTF8; + resp.ContentLength64 = data.Length; + + // Write out to the response stream (asynchronously), then close it + await resp.OutputStream.WriteAsync(data); + resp.Close(); + } + catch (ObjectDisposedException) + { + // Такое исключение при Listener.Abort(), значит работа закончена + return; + } + catch (HttpListenerException) + { + if (!RunServer) + return; + } + } + } + + private void Lock_CheckedChanged(object sender, EventArgs e) + { + if (_stopUI) + return; + + if (Lock.Checked) + { + Port.Enabled = false; + Password.Enabled = false; + Lock.Text = "Заблокировано"; + ToolTip.SetToolTip(Lock, "Для изменения порта или пароля снимите блокировку"); + GeneratePassword.Visible = false; + + SaveConfig(); + if (RunServer) + { + StopServer(); + } + StartServer(); + } + else + { + Port.Enabled = true; + Password.Enabled = true; + Lock.Text = "Применить"; + ToolTip.SetToolTip(Lock, "Для применения порта и пароля установите блокировку"); + GeneratePassword.Visible = true; + } + } + + private void CopyPortPic_Click(object sender, EventArgs e) + { + Clipboard.SetText(Port.Text); + ToolTip.Show("Порт сохранен в буфер обмена", Port, 1000); + } + + private void CopyPasswordPic_Click(object sender, EventArgs e) + { + Clipboard.SetText(Password.Text); + ToolTip.Show("Пароль сохранен в буфер обмена", Password, 1000); + } + + private void GeneratePassword_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) + { + if (Lock.Checked) + return; + Password.Text = Guid.NewGuid().ToString(); + } + + private void Port_Validating(object sender, CancelEventArgs e) + { + if (!int.TryParse(Port.Text, out int value) || value < 1 || value > ushort.MaxValue) + { + e.Cancel = true; + } + } +} diff --git a/YandexAuthBrowser/ResidentForm.resx b/BrowserAuthenticator/ResidentForm.resx similarity index 96% rename from YandexAuthBrowser/ResidentForm.resx rename to BrowserAuthenticator/ResidentForm.resx index 0dde4f38..a625696e 100644 --- a/YandexAuthBrowser/ResidentForm.resx +++ b/BrowserAuthenticator/ResidentForm.resx @@ -1,4 +1,64 @@ - + + + @@ -58,7 +118,7 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 - 17, 17 + 172, 17 @@ -125,9 +185,15 @@ - 404, 20 + 623, 17 - + + 362, 17 + + + 17, 17 + + iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAYAAAD0eNT6AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAB/ NgAAfzYB9NfszgAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAADgcSURBVHhe7d15 @@ -373,7 +439,7 @@ 4gG4Da4aPavSdnziU/4/Ni4c5GLuiYIAAAAASUVORK5CYII= - + iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAYAAAD0eNT6AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAB/ NgAAfzYB9NfszgAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAADgcSURBVHhe7d15 @@ -619,7 +685,7 @@ 4gG4Da4aPavSdnziU/4/Ni4c5GLuiYIAAAAASUVORK5CYII= - + iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAYAAAD0eNT6AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAB/ NgAAfzYB9NfszgAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAADgcSURBVHhe7d15 @@ -865,7 +931,7 @@ 4gG4Da4aPavSdnziU/4/Ni4c5GLuiYIAAAAASUVORK5CYII= - + iVBORw0KGgoAAAANSUhEUgAAAgAAAAIACAYAAAD0eNT6AAAABGdBTUEAALGPC/xhBQAAAAlwSFlzAAB/ NgAAfzYB9NfszgAAABl0RVh0U29mdHdhcmUAd3d3Lmlua3NjYXBlLm9yZ5vuPBoAADgcSURBVHhe7d15 @@ -1111,9 +1177,6 @@ 4gG4Da4aPavSdnziU/4/Ni4c5GLuiYIAAAAASUVORK5CYII= - - 187, 24 - AAABAAEAIBoAAAEAIACQDQAAFgAAACgAAAAgAAAANAAAAAEAIAAAAAAAaA0AAAAAAAAAAAAAAAAAAAAA diff --git a/BrowserAuthenticator/Resources.Designer.cs b/BrowserAuthenticator/Resources.Designer.cs new file mode 100644 index 00000000..3bcdd4ce --- /dev/null +++ b/BrowserAuthenticator/Resources.Designer.cs @@ -0,0 +1,96 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace BrowserAuthenticator { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "17.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("BrowserAuthenticator.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to <!DOCTYPE html> + ///<html> + ///<head> + /// <meta charset="utf-8" /> + /// <title>Выберите облако</title> + /// <style> + /// .center-screen { + /// display: flex; + /// justify-content: center; + /// align-items: center; + /// text-align: center; + /// min-height: 100vh; + /// } + /// body { + /// font: 1.2em "Tahoma", sans-serif; + /// } + /// </style> + ///</head> + ///<body class="center-screen"> + ///<table cellspacing="50"><tr><td> + ///Выберите облако: + ///</td></tr> + ///<tr><td> + ///<a href="https://cloud.mail.ru"> + ///<img src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTM5IiBoZWlnaHQ9IjMyIiB2aWV3Qm94PS [rest of string was truncated]";. + /// + internal static string start { + get { + return ResourceManager.GetString("start", resourceCulture); + } + } + } +} diff --git a/BrowserAuthenticator/Resources.resx b/BrowserAuthenticator/Resources.resx new file mode 100644 index 00000000..2dc1dddc --- /dev/null +++ b/BrowserAuthenticator/Resources.resx @@ -0,0 +1,124 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + + files\start.html;System.String, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089;utf-8 + + \ No newline at end of file diff --git a/YandexAuthBrowser/cloud.ico b/BrowserAuthenticator/files/cloud.ico similarity index 100% rename from YandexAuthBrowser/cloud.ico rename to BrowserAuthenticator/files/cloud.ico diff --git a/YandexAuthBrowser/copy-files.png b/BrowserAuthenticator/files/copy-files.png similarity index 100% rename from YandexAuthBrowser/copy-files.png rename to BrowserAuthenticator/files/copy-files.png diff --git a/BrowserAuthenticator/files/start.html b/BrowserAuthenticator/files/start.html new file mode 100644 index 00000000..1b1963a4 --- /dev/null +++ b/BrowserAuthenticator/files/start.html @@ -0,0 +1,40 @@ + + + + + Выберите облако + + + + + + + +
+Выберите облако: +
+ + +
+cloud.mail.ru +
+
+ + +
+disk.yandex.ru +
+
+ + diff --git a/Hasher/Program.cs b/Hasher/Program.cs index 55cbd3a1..0e082574 100644 --- a/Hasher/Program.cs +++ b/Hasher/Program.cs @@ -24,7 +24,7 @@ private static void Main(string[] args) Protocol = options.Protocol }; - var repoFabric = new RepoFabric(settings, new Credentials(string.Empty, string.Empty)); + var repoFabric = new RepoFabric(settings, new Credentials(settings, string.Empty, string.Empty)); var repo = repoFabric.Create(); var cards = new List(); @@ -32,7 +32,7 @@ private static void Main(string[] args) foreach (string filelist in options.Filelists) { - if (string.IsNullOrEmpty(filelist)) + if (string.IsNullOrEmpty(filelist)) continue; if (!File.Exists(filelist)) @@ -55,7 +55,7 @@ private static void Main(string[] args) //string absPathWithFilename = Path.GetFullPath(card); //string absPath = Path.GetDirectoryName(string.IsNullOrEmpty(path) ? "." : path); - string[] filenames = Directory.GetFiles(absPath, pattern, + string[] filenames = Directory.GetFiles(absPath, pattern, options.IsRecursive ? SearchOption.AllDirectories : SearchOption.TopDirectoryOnly); foreach (var filename in filenames) diff --git a/MailRuCloud/MailRuCloudApi/Base/Credentials.cs b/MailRuCloud/MailRuCloudApi/Base/Credentials.cs index 816c26ea..13abe34b 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Credentials.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Credentials.cs @@ -1,271 +1,546 @@ using System; +using System.IO; using System.Linq; +using System.Net; +using System.Net.Http; using System.Security.Authentication; -using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Newtonsoft.Json; using YaR.Clouds.Base.Repos.MailRuCloud; +using YaR.Clouds.Base.Requests.Types; +using YaR.Clouds.Extensions; -namespace YaR.Clouds.Base +namespace YaR.Clouds.Base; + +public partial class Credentials : IBasicCredentials { - public partial class Credentials : IBasicCredentials + private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(Credentials)); + + private static readonly string[] AnonymousLogins = { "anonymous", "anon", "anonym", string.Empty }; + + + public bool IsAnonymous { get; private set; } + + public Protocol Protocol { get; private set; } = Protocol.Autodetect; + public CloudType CloudType { get; private set; } + + public string Login { get; private set; } + public string Password { get; private set; } + + public string PasswordCrypt { get; set; } + + public bool CanCrypt => !string.IsNullOrEmpty(PasswordCrypt); + + public bool AuthenticationUsingBrowser { get; private set; } + + + #region На текущий момент специфично только для Янднекс.Диска + + public bool IsCacheUsed { get; private set; } + public CookieContainer Cookies { get; private set; } + + public string Sk { get; set; } + public string Uuid { get; set; } + + #endregion + + private readonly CloudSettings _settings; + + public Credentials(CloudSettings settings, string login, string password) { - private static readonly string[] AnonymousLogins = { "anonymous", "anon", "anonym", string.Empty }; - - // протокол # логин # разделитель - private const string RegexMask1 = "^([^#]*)#([^#]*)#([^#]*)$"; - // протокол # логин - private const string RegexMask2 = "^(1|2|WebM1Bin|WebV2|YadWeb|YadWebV2)#([^#]*)$"; - // логин # разделитель - private const string RegexMask3 = "^([^#]*)#([^#]*)$"; - -#if NET7_0_OR_GREATER - private static readonly Regex _loginRegex1 = LoginRegexMask1(); - private static readonly Regex _loginRegex2 = LoginRegexMask2(); - private static readonly Regex _loginRegex3 = LoginRegexMask3(); - - [GeneratedRegex(RegexMask1, RegexOptions.IgnoreCase | RegexOptions.Compiled)] - private static partial Regex LoginRegexMask1(); - [GeneratedRegex(RegexMask2, RegexOptions.IgnoreCase | RegexOptions.Compiled)] - private static partial Regex LoginRegexMask2(); - [GeneratedRegex(RegexMask3, RegexOptions.IgnoreCase | RegexOptions.Compiled)] - private static partial Regex LoginRegexMask3(); -#else - private static readonly Regex _loginRegex1 = new Regex(RegexMask1, RegexOptions.IgnoreCase | RegexOptions.Compiled); - private static readonly Regex _loginRegex2 = new Regex(RegexMask2, RegexOptions.IgnoreCase | RegexOptions.Compiled); - private static readonly Regex _loginRegex3 = new Regex(RegexMask3, RegexOptions.IgnoreCase | RegexOptions.Compiled); -#endif + _settings = settings; + IsCacheUsed = false; + Cookies = new CookieContainer(); + + if (string.IsNullOrWhiteSpace(login)) + login = string.Empty; - public Credentials(string login, string password) + if (AnonymousLogins.Contains(login)) { - if (string.IsNullOrWhiteSpace(login)) - login = string.Empty; + IsAnonymous = true; + Login = login; + Password = string.Empty; + PasswordCrypt = string.Empty; + CloudType = GetCloundTypeFromLogin(Login); + Protocol = Protocol.Autodetect; + + return; + } + + ParseLoginPassword(login, password); + } - if (AnonymousLogins.Contains(login)) + private static CloudType GetCloundTypeFromLogin(string login) + { + foreach (var domain in MailRuBaseRepo.AvailDomains) + { + bool hasMail = login.ContainsIgnoreCase(string.Concat("@", domain, ".")); + if (hasMail) + return CloudType.Mail; + } + + bool hasYandex = login.ContainsIgnoreCase("@yandex.") || login.ContainsIgnoreCase("@ya."); + + if (hasYandex) + return CloudType.Yandex; + + return CloudType.Unkown; + } + + private void ParseLoginPassword(string login, string password) + { + CloudType = CloudType.Unkown; + Protocol = Protocol.Autodetect; + + /* + * Ожидаемые форматы логина и пароля: + * login = <имя> # <разделитель> | <имя> + * Если login содержит символ #, то пароль должен иметь вид + * password = <пароль> <разделитель> <ключ шифрования>, + * при этом <разделитель> и <ключ шифрования> не могут быть пустыми. + * Если login не содержит символ #, то пароль должен иметь вид + * password = <пароль>. + * <имя> может быть представлено в следующих вариантах: + * <имя> = | | + * Здесь - это email или иное название, идентифицирующее учетную запись на облачном сервере, + * - вопросительный знак перед email указывает на необходимость аутентификации через браузер + * и соответствующий протокол обращения к облаку, + * - восклицательный знак перед email указывает на запрет аутентификации через браузер + * и соответствующий протокол обращения к облаку. + * На текущий момент знаки ? и ! обрабатываются всегда, но обращение к браузеру для аутентификации + * для email с доменами Mail.Ru не производится. + */ + + login ??= string.Empty; + password ??= string.Empty; + string[] loginParts = login.Split(new char[] { '#' }, StringSplitOptions.RemoveEmptyEntries); + switch (loginParts.Length) + { + case 0: + IsAnonymous = true; + Login = login.Trim(); + Password = string.Empty; + PasswordCrypt = string.Empty; + break; + case 1: + IsAnonymous = false; + Login = loginParts[0].Trim(); + Password = password; + PasswordCrypt = string.Empty; + break; + case 2: + IsAnonymous = false; + Login = loginParts[0].Trim(); + + string sep = loginParts[1].Trim(); + if (string.IsNullOrEmpty(sep)) { - IsAnonymous = true; - Login = login; - Password = string.Empty; - PasswordCrypt = string.Empty; - CloudType = StringToCloud(Login); - Protocol = Protocol.Autodetect; - - return; + throw new InvalidCredentialException("Invalid credential format: " + + "encryption part after # symbol in login string is too short. See manuals."); } - /* - * Login ожидается в форматах: - * John - здесь John - учетная запись в облаке без указания mail или yandex - * John#SEP - здесь SEP - это разделитель для строки пароля - * John@yandex.com - здесь John@yandex.com - учетная запись в облаке с указанием mail или yandex - * John@yandex.com#SEP - указано облако и разделитель для строки пароля - * Выше в форматах не задан протокол, поэтому принимается значение Autodetect - * 1#John - учетная запись без указания mail или yandex с указанием версии протокола - не допустимо - * WebM1Bin#John - учетная запись без указания mail или yandex с указанием версии протокола - * WebM1Bin#John#SEP - учетная запись без указания mail или yandex с указанием версии протокола и разделителя - * 1#John@mail.ru - учетная запись с указанием mail или yandex с указанием версии протокола - * 1#John@mail.ru#SEP - учетная запись с указанием mail или yandex с указанием версии протокола и разделителя строки пароля - * WebM1Bin#John@yandex.ru - учетная запись облака yandex с указанием несовместимой версии протокола - не допустимо - */ - - // протокол # логин # разделитель - Match m = _loginRegex1.Match(login); - if (m.Success) + int sepPos = password.IndexOf(sep, StringComparison.InvariantCulture /* case sensitive! */); + if (sepPos < 0 || sepPos + sep.Length >= password.Length) { - if (string.IsNullOrEmpty(m.Groups[1].Value)) + throw new InvalidCredentialException("Invalid credential format: " + + "password doesn't contain encryption part. See manuals."); + } + PasswordCrypt = password.Substring(sepPos + sep.Length); + Password = password.Substring(0, sepPos); + break; + default: + throw new InvalidCredentialException("Invalid credential format: " + + "too many # symbols in login string. See manuals."); + } + + CloudType = GetCloundTypeFromLogin(Login); + /* + * Восклицательный знак перед логином указывает на запрет использования + * браузера для аутентификации. + * Наоборот, вопросительный знак перед логином указывает на + * обязательное использование браузера для аутентификации. + */ + bool browserAuthenticatorDisabled = Login.StartsWith("!"); + bool browserAuthenticatorAsked = Login.StartsWith("?"); + + if (browserAuthenticatorAsked || browserAuthenticatorDisabled) + Login = Login.Remove(0, 1).Trim(); + + AuthenticationUsingBrowser = false; + + if (browserAuthenticatorAsked && CloudType != CloudType.Mail) + { + AuthenticationUsingBrowser = true; + } + else + if (!browserAuthenticatorDisabled) + { + // Если аутентификация браузером не запрошена через строку логина, + // но пароль совпадает с паролем сервиса аутентификации через браузер, + // считаем это указанием использовать браузер для аутентификации. + if (!string.IsNullOrEmpty(_settings.BrowserAuthenticatorPassword) && + password == _settings.BrowserAuthenticatorPassword && + CloudType != CloudType.Mail) + { + AuthenticationUsingBrowser = true; + } + } + + if (AuthenticationUsingBrowser) + GetBrowserCookiesAsync().Wait(); + + // Если тип облака на данном этапе еще не определен, + // пытаемся определить его из строки логина, через домен учетной записи. + if (CloudType == CloudType.Unkown) + CloudType = GetCloundTypeFromLogin(Login); + + // Если на данном этапе протокол еще не определен, + // определяем его по типу облака и подсказке типа протокола в параметре запуска программы. + if (Protocol == Protocol.Autodetect) + { + Protocol = CloudType == CloudType.Yandex + ? Protocol.YadWeb + : _settings.Protocol == Protocol.WebV2 + ? Protocol.WebV2 + : Protocol.WebM1Bin; + } + + // Если на этом этапа все еще не определены протокол и облако, + // но при этом задан протокол в параметре запуска программы, + // выставляем облако и протокол по его значению. + if (Protocol == Protocol.Autodetect && + CloudType == CloudType.Unkown && + _settings.Protocol != Protocol.Autodetect) + { + Protocol = _settings.Protocol; + CloudType = Protocol switch + { + Protocol.WebM1Bin => CloudType.Mail, + Protocol.WebV2 => CloudType.Mail, + Protocol.YadWeb => CloudType.Yandex, + _ => CloudType.Unkown + }; + } + + if (Protocol == Protocol.Autodetect && + CloudType == CloudType.Unkown) + { + throw new InvalidCredentialException("Invalid credential format: " + + "cloud server is not detected by supplied login, use fully qualified email. See manuals."); + } + + if (CloudType == CloudType.Mail && + !(Protocol == Protocol.WebM1Bin || Protocol == Protocol.WebV2) || + CloudType == CloudType.Yandex && Protocol != Protocol.YadWeb) + { + throw new InvalidCredentialException("Invalid credential format: " + + "cloud type and protocol are incompatible. See manuals."); + } + } + + private async Task GetBrowserCookiesAsync() + { + Cookies = new CookieContainer(); + bool doRegularLogin = true; + BrowserAppResult response = null; + + // Куки уже может быть сохранен в кеше на диске, проверяем + if (!string.IsNullOrEmpty(_settings.BrowserAuthenticatorCacheDir)) + { + string path = null; + + try + { + string fileName = string.IsNullOrWhiteSpace(Login) ? "anonymous" : Login; + path = Path.Combine(_settings.BrowserAuthenticatorCacheDir, fileName); + + if (System.IO.File.Exists(path)) { - throw new InvalidCredentialException("Invalid credential format: " + - "login doesn't have protocol part. See manuals."); +#if NET48 + string content = System.IO.File.ReadAllText(path); +#else + string content = await System.IO.File.ReadAllTextAsync(path).ConfigureAwait(false); +#endif + response = JsonConvert.DeserializeObject(content); + + IsCacheUsed = true; + + doRegularLogin = false; + Logger.Info($"Browser authentication: cache is used"); } - if (string.IsNullOrEmpty(m.Groups[2].Value)) + } + catch (Exception) + { + // Request for user info using cached cookie failed + + // Delete file with cache first + try { - throw new InvalidCredentialException("Invalid credential format: " + - "login doesn't have email part. See manuals."); + System.IO.File.Delete(path); } - if (string.IsNullOrEmpty(m.Groups[3].Value)) + catch (Exception) { } + // Then make regular login + doRegularLogin = true; + } + } + + if (doRegularLogin) + { + // Куки нет в кеше, кеш не задан или ошибка при использовании куки из кеша, + // тогда полноценный вход через браузер + + try + { + response = await MakeLogin().ConfigureAwait(false); + } + catch (Exception e) when (e.OfType().Any()) + { + Logger.Error("Browser authentication failed! " + + "Please check browser authentication component is running!"); + + throw new InvalidCredentialException("Browser authentication failed! Browser component is not running!"); + } + catch (Exception e) + { + if (e.OfType() is AuthenticationException ae) { - throw new InvalidCredentialException("Invalid credential format: " + - "login doesn't have encryption part. See manuals."); + string txt = string.Concat("Browser authentication failed! ", ae.Message); + Logger.Error(txt); + + throw new InvalidCredentialException(txt); } - (Login, Password, PasswordCrypt) = StringToLoginPassword(m.Groups[2].Value, m.Groups[3].Value, password); - CloudType = StringToCloud(Login); - Protocol = StringToProtocol(m.Groups[1].Value, CloudType); + Logger.Error("Browser authentication failed! " + + "Check the URL and the password for browser authentication component!"); + + throw new InvalidCredentialException("Browser authentication failed!"); } - else + + IsCacheUsed = false; + Logger.Info($"Browser authentication successful"); + + // Сохраняем новый куки, если задан путь для кеша + if (!string.IsNullOrEmpty(_settings.BrowserAuthenticatorCacheDir)) { - // протокол # логин - m = _loginRegex2.Match(login); - if (m.Success) + string fileName = string.IsNullOrWhiteSpace(Login) ? "anonymous" : Login; + string path = Path.Combine(_settings.BrowserAuthenticatorCacheDir, fileName); + try { - if (string.IsNullOrEmpty(m.Groups[1].Value)) + string content = JsonConvert.SerializeObject(response); + + try { - throw new InvalidCredentialException("Invalid credential format: " + - "login doesn't have protocol part. See manuals."); + if (!Directory.Exists(_settings.BrowserAuthenticatorCacheDir)) + Directory.CreateDirectory(_settings.BrowserAuthenticatorCacheDir); } - if (string.IsNullOrEmpty(m.Groups[2].Value)) + catch (Exception ex) { - throw new InvalidCredentialException("Invalid credential format: " + - "login doesn't have email part. See manuals."); - } + Logger.Error( + $"Directory for cache can not be created, " + + $"remove attribute CacheDir in BrowserAuthenticator tag in configuration file! " + + $"{ex.Message}"); - (Login, Password, PasswordCrypt) = StringToLoginPassword(m.Groups[2].Value, null, password); - CloudType = StringToCloud(Login); - Protocol = StringToProtocol(m.Groups[1].Value, CloudType); - } - else - { - // логин # разделитель - m = _loginRegex3.Match(login); - if (m.Success) - { - if (string.IsNullOrEmpty(m.Groups[1].Value)) - { - throw new InvalidCredentialException("Invalid credential format: " + - "login doesn't have email part. See manuals."); - } - if (string.IsNullOrEmpty(m.Groups[2].Value)) - { - throw new InvalidCredentialException("Invalid credential format: " + - "login doesn't have encryption part. See manuals."); - } - - (Login, Password, PasswordCrypt) = StringToLoginPassword(m.Groups[1].Value, m.Groups[2].Value, password); - CloudType = StringToCloud(Login); - Protocol = CloudType == CloudType.Mail - // Т.к. протокол WebV2 отмечен как deprecated, всегда автоматически выбирается WebM1Bin - ? Protocol = Protocol.WebM1Bin - : CloudType == CloudType.Yandex && string.IsNullOrWhiteSpace(Password) - ? Protocol.YadWebV2 - : Protocol.Autodetect; + path = null; } - else + + if (path is not null) { - (Login, Password, PasswordCrypt) = StringToLoginPassword(login, null, password); - CloudType = StringToCloud(Login); - Protocol = Protocol.Autodetect; +#if NET48 + System.IO.File.WriteAllText(path, content); +#else + await System.IO.File.WriteAllTextAsync(path, content).ConfigureAwait(false); +#endif } } + catch (Exception ex) + { + Logger.Error($"Error saving cookies to the file {path}: {ex.Message}"); + } } } - public bool IsAnonymous { get; set; } - - public Protocol Protocol { get; set; } = Protocol.Autodetect; - public CloudType CloudType { get; set; } - - public string Login { get; } - public string Password { get; } - - public string PasswordCrypt { get; set; } + if (response.Cookies is null) + { + AuthenticationUsingBrowser = false; + } + else + { + AuthenticationUsingBrowser = true; + foreach (var item in response.Cookies) + { + var cookie = new Cookie(item.Name, item.Value, item.Path, item.Domain); + Cookies.Add(cookie); + } + } - public bool CanCrypt => !string.IsNullOrEmpty(PasswordCrypt); + CloudType = response.Cloud == "mail.ru" ? CloudType.Mail : CloudType.Yandex; + if (CloudType == CloudType.Yandex && + !string.IsNullOrEmpty(response.Sk) && + !string.IsNullOrEmpty(response.Uuid)) + { + Sk = response.Sk; + Uuid = response.Uuid; + } + else + { + Sk = null; + Uuid = null; + AuthenticationUsingBrowser = false; + } + } - private static CloudType StringToCloud(string login) + /// + /// Если аутентификация была через браузер, + /// стирает файл с кешем куки и запрашивает повторную аутентификацию через браузер. + /// Возвращает true, если обновление прошло. + /// Возвращает false, если обновление не прошло и надо отправить исключение. + /// + /// + public bool Refresh() + { + if (!string.IsNullOrEmpty(_settings.BrowserAuthenticatorCacheDir)) { - foreach (var domain in MailRuBaseRepo.AvailDomains) + string fileName = string.IsNullOrWhiteSpace(Login) ? "anonymous" : Login; + string path = Path.Combine(_settings.BrowserAuthenticatorCacheDir, fileName); + + try { -#if NET48 - bool hasMail = System.Globalization.CultureInfo.CurrentCulture.CompareInfo - .IndexOf(login, string.Concat("@", domain, "."), System.Globalization.CompareOptions.OrdinalIgnoreCase) >= 0; -#else - bool hasMail = login.Contains(string.Concat("@", domain, "."), StringComparison.InvariantCultureIgnoreCase); -#endif - if (hasMail) - return CloudType.Mail; + if (System.IO.File.Exists(path)) + System.IO.File.Delete(path); } + catch (Exception ex) + { + Logger.Error($"Error deleting cookie file {path}: {ex.Message}"); + } + } + if (AuthenticationUsingBrowser) + { + Protocol saveProtocol = Protocol; + CloudType saveCloudType = CloudType; + // Если аутентификация не прошла, будет исключение + GetBrowserCookiesAsync().Wait(); + if (saveCloudType != CloudType || saveProtocol != Protocol) + return false; + return true; + } + return false; + } -#if NET48 - bool hasYandex = System.Globalization.CultureInfo.CurrentCulture.CompareInfo - .IndexOf(login, "@yandex.", System.Globalization.CompareOptions.OrdinalIgnoreCase) >= 0 || - System.Globalization.CultureInfo.CurrentCulture.CompareInfo - .IndexOf(login, "@ya.", System.Globalization.CompareOptions.OrdinalIgnoreCase) >= 0; -#else - bool hasYandex = - login.Contains("@yandex.", StringComparison.InvariantCultureIgnoreCase) || - login.Contains("@ya.", StringComparison.InvariantCultureIgnoreCase); -#endif - if (hasYandex) - return CloudType.Yandex; + private static string GetNameOnly(string value) + { + if (string.IsNullOrEmpty(value)) + return value; + int pos = value.IndexOf('@'); + if (pos == 0) + return ""; + if (pos > 0) + return value.Substring(0, pos); + return value; + } - return CloudType.Unkown; - } + private async Task<(BrowserAppResult, string)> ConnectToBrowserApp() + { + string url = _settings.BrowserAuthenticatorUrl; + string password = string.IsNullOrWhiteSpace(Password) + ? _settings.BrowserAuthenticatorPassword + : Password; - private static Protocol StringToProtocol(string protocol, CloudType cloud) + if (string.IsNullOrEmpty(url)) { - switch (protocol) - { - case "1": - if (cloud == CloudType.Mail) - return Protocol.WebM1Bin; - if (cloud == CloudType.Yandex) - return Protocol.YadWeb; - break; - - case "2": - if (cloud == CloudType.Mail) - return Protocol.WebV2; - if (cloud == CloudType.Yandex) - return Protocol.YadWebV2; - break; - - case "WebM1Bin": - if (cloud == CloudType.Mail) - return Protocol.WebM1Bin; - if (cloud == CloudType.Yandex) - throw new InvalidCredentialException("Invalid credential format: " + - "protocol version isn't compatible with cloud specified by login. See manuals."); - break; - - case "WebV2": - if (cloud == CloudType.Mail) - return Protocol.WebV2; - if (cloud == CloudType.Yandex) - throw new InvalidCredentialException("Invalid credential format: " + - "protocol version isn't compatible with cloud specified by login. See manuals."); - break; - - case "YadWeb": - if (cloud == CloudType.Yandex) - return Protocol.YadWeb; - if (cloud == CloudType.Mail) - throw new InvalidCredentialException("Invalid credential format: " + - "protocol version isn't compatible with cloud specified by login. See manuals."); - break; - - case "YadWebV2": - if (cloud == CloudType.Yandex) - return Protocol.YadWebV2; - if (cloud == CloudType.Mail) - throw new InvalidCredentialException("Invalid credential format: " + - "protocol version isn't compatible with cloud specified by login. See manuals."); - break; - - default: - throw new InvalidCredentialException("Invalid credential format: " + - "unknown protocol. See manuals."); - }; + throw new AuthenticationException("Error connecting to browser authenticator application. " + + "Check the BrowserAuthenticator is running and have correct port."); + } - throw new InvalidCredentialException("Invalid credential format: " + - "protocol version needs fully qualified login using email format. See manuals."); + using var client = new HttpClient { BaseAddress = new Uri(url) }; + var httpRequestMessage = new HttpRequestMessage + { + Method = HttpMethod.Get, + RequestUri = new Uri($"/{Uri.EscapeDataString(Login)}/{Uri.EscapeDataString(password)}/", UriKind.Relative), + Headers = { + { HttpRequestHeader.Accept.ToString(), "application/json" }, + { HttpRequestHeader.ContentType.ToString(), "application/json" }, + }, + }; + + client.Timeout = new TimeSpan(0, 5, 0); + try + { + using var response = await client.SendAsync(httpRequestMessage); + var responseText = await response.Content.ReadAsStringAsync(); + response.EnsureSuccessStatusCode(); + BrowserAppResult data = JsonConvert.DeserializeObject(responseText); + return (data, responseText); + } + catch (Exception) + { + throw; } + } - private static (string login, string password, string encPassword) StringToLoginPassword( - string loginPart, string separatorPart, string passwordPart) + private async Task MakeLogin() + { + (BrowserAppResult response, string responseHtml) = await ConnectToBrowserApp(); + + if (response != null && + !string.IsNullOrEmpty(response.Sk) && + !string.IsNullOrEmpty(response.Uuid) && + !string.IsNullOrEmpty(response.Login) && + GetNameOnly(response.Login) + .Equals(GetNameOnly(Login), StringComparison.OrdinalIgnoreCase) && + string.IsNullOrEmpty(response.ErrorMessage) + ) { - if (string.IsNullOrEmpty(separatorPart)) - return (loginPart, passwordPart, string.Empty); + CloudType = response.Cloud.Equals("mail.ru", StringComparison.InvariantCultureIgnoreCase) + ? CloudType.Mail + : CloudType.Yandex; + Sk = response.Sk; + Uuid = response.Uuid; - int sepPos = passwordPart.IndexOf(separatorPart, StringComparison.InvariantCulture /* case sensitive! */); - if (sepPos < 0) - throw new InvalidCredentialException("Invalid credential format: " + - "password doesn't contain encryption part. See manuals."); + foreach (var item in response.Cookies) + { + var cookie = new Cookie(item.Name, item.Value, item.Path, item.Domain); + Cookies.Add(cookie); + } - string password = passwordPart.Substring(0, sepPos); - if (sepPos + separatorPart.Length >= passwordPart.Length) - throw new InvalidCredentialException("Invalid credential format."); + // Если аутентификация прошла успешно, сохраняем результат в кеш в файл + if (!string.IsNullOrEmpty(_settings.BrowserAuthenticatorCacheDir)) + { + string path = Path.Combine(_settings.BrowserAuthenticatorCacheDir, Login); - string passwordCrypt = passwordPart.Substring(sepPos + separatorPart.Length); + try + { + string dir = Path.GetDirectoryName(path); + if (!Directory.Exists(dir)) + Directory.CreateDirectory(dir); + } + catch (Exception) + { + string text = "Failed to create cache storage directory. " + + "The attribute CacheDir of BrowserAuthenticator tag in configuration file must be removed!"; + Logger.Error(text); + throw new AuthenticationException(text); + } + try + { +#if NET48 + System.IO.File.WriteAllText(path, responseHtml); +#else + await System.IO.File.WriteAllTextAsync(path, responseHtml); +#endif + } + catch (Exception) { } + } + } + else + { + string text = string.IsNullOrEmpty(response?.ErrorMessage) + ? "Authentication using BrowserAuthenticator application is failed!" + : string.Concat("Authentication using BrowserAuthenticator application is failed! ", response.ErrorMessage); - return (loginPart, password, passwordCrypt); + Logger.Error(text); + throw new AuthenticationException(text); } + + return response; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/CryptInfo.cs b/MailRuCloud/MailRuCloudApi/Base/CryptInfo.cs index fcabab19..32e68cc9 100644 --- a/MailRuCloud/MailRuCloudApi/Base/CryptInfo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/CryptInfo.cs @@ -13,4 +13,4 @@ public class CryptoKeyInfo public byte[] Salt { get; set; } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/CryptoUtil.cs b/MailRuCloud/MailRuCloudApi/Base/CryptoUtil.cs index 428c3cda..91623aab 100644 --- a/MailRuCloud/MailRuCloudApi/Base/CryptoUtil.cs +++ b/MailRuCloud/MailRuCloudApi/Base/CryptoUtil.cs @@ -60,4 +60,4 @@ public class KeyAndSalt public byte[] IV { get; set; } } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/File.cs b/MailRuCloud/MailRuCloudApi/Base/File.cs index 86ff6284..4574f779 100644 --- a/MailRuCloud/MailRuCloudApi/Base/File.cs +++ b/MailRuCloud/MailRuCloudApi/Base/File.cs @@ -28,7 +28,7 @@ public File(string fullPath, long size, IFileHash hash = null) _hash = hash; } - public File(string fullPath, long size, params PublicLinkInfo[] links) + public File(string fullPath, long size, params PublicLinkInfo[] links) : this(fullPath, size) { foreach (var link in links) @@ -149,7 +149,7 @@ public ConcurrentDictionary PublicLinks public IEnumerable GetPublicLinks(Cloud cloud) { return PublicLinks.IsEmpty - ? cloud.GetSharedLinks(FullPath) + ? cloud.GetSharedLinks(FullPath) : PublicLinks.Values; } @@ -191,7 +191,7 @@ public void SetName(string destinationName) public void SetPath(string fullPath) { FullPath = WebDavPath.Combine(fullPath, Name); - if (Parts.Count <= 1) + if (Parts.Count <= 1) return; foreach (var fiFile in Parts) @@ -225,8 +225,8 @@ public PublishInfo ToPublishInfo(Cloud cloud, bool generateDirectVideoLink, Shar Path = innerFile.FullPath, Urls = innerFile.PublicLinks.Select(pli => pli.Value.Uri).ToList(), PlayListUrl = !isSplitted || cnt > 0 - ? generateDirectVideoLink - ? ConvertToVideoLink(cloud, innerFile.PublicLinks.Values.FirstOrDefault()?.Uri, videoResolution) + ? generateDirectVideoLink + ? File.ConvertToVideoLink(cloud, innerFile.PublicLinks.Values.FirstOrDefault()?.Uri, videoResolution) : null : null }); @@ -239,12 +239,11 @@ public PublishInfo ToPublishInfo(Cloud cloud, bool generateDirectVideoLink, Shar private static string ConvertToVideoLink(Cloud cloud, Uri publicLink, SharedVideoResolution videoResolution) { return cloud.RequestRepo.ConvertToVideoLink(publicLink, videoResolution); - - - // GetShardInfo(ShardType.WeblinkVideo).Result.Url + - //videoResolution.ToEnumMemberValue() + "/" + //"0p/" + - //Base64Encode(publicLink.TrimStart('/')) + - //".m3u8?double_encode=1"; + + // GetShardInfo(ShardType.WeblinkVideo).Result.Url + + //videoResolution.ToEnumMemberValue() + "/" + //"0p/" + + //Base64Encode(publicLink.TrimStart('/')) + + //".m3u8?double_encode=1"; } } } diff --git a/MailRuCloud/MailRuCloudApi/Base/FileSize.cs b/MailRuCloud/MailRuCloudApi/Base/FileSize.cs index 320c2e75..2eee9500 100644 --- a/MailRuCloud/MailRuCloudApi/Base/FileSize.cs +++ b/MailRuCloud/MailRuCloudApi/Base/FileSize.cs @@ -54,7 +54,7 @@ public bool Equals(FileSize other) public override bool Equals(object obj) { - if (ReferenceEquals(null, obj)) + if (ReferenceEquals(null, obj)) return false; return obj.GetType() == GetType() && Equals((FileSize)obj); diff --git a/MailRuCloud/MailRuCloudApi/Base/FileSplitInfo.cs b/MailRuCloud/MailRuCloudApi/Base/FileSplitInfo.cs index 4e38d94a..963d0887 100644 --- a/MailRuCloud/MailRuCloudApi/Base/FileSplitInfo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/FileSplitInfo.cs @@ -6,4 +6,4 @@ public class FileSplitInfo public bool IsPart => PartNumber > 0; public int PartNumber { get; set; } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/FilenameServiceInfo.cs b/MailRuCloud/MailRuCloudApi/Base/FilenameServiceInfo.cs index 8372d472..5a66ae5f 100644 --- a/MailRuCloud/MailRuCloudApi/Base/FilenameServiceInfo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/FilenameServiceInfo.cs @@ -26,7 +26,7 @@ public string ToString(bool withName) public static FilenameServiceInfo Parse(string filename) { - + static int HexToInt(char h) { @@ -40,7 +40,7 @@ static int HexToInt(char h) } static bool IsDigit(char c) => c is >= '0' and <= '9'; - + var res = new FilenameServiceInfo { CleanName = filename, SplitInfo = new FileSplitInfo { IsHeader = true } }; @@ -59,14 +59,14 @@ static int HexToInt(char h) int parselen = fns.Length - pos; int align = parselen == 4 ? HexToInt(fns[pos + 3]) : -1; - bool hasDigits = (parselen == 3 || (parselen == 4 && align > -1)) + bool hasDigits = (parselen == 3 || (parselen == 4 && align > -1)) && IsDigit(fns[pos]) && IsDigit(fns[pos + 1]) && IsDigit(fns[pos + 2]); - if (!hasDigits) + if (!hasDigits) return res; res.CleanName = fns[..startpos].ToString(); - + res.SplitInfo.IsHeader = false; #if NET48 res.SplitInfo.PartNumber = int.Parse(fns.Slice(pos, 3).ToString()); @@ -82,4 +82,4 @@ static int HexToInt(char h) private const string WdmrcDots = ".wdmrc."; } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Folder.cs b/MailRuCloud/MailRuCloudApi/Base/Folder.cs index 1faa526c..94f94963 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Folder.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Folder.cs @@ -41,7 +41,7 @@ public Folder(FileSize size, string fullPath, IEnumerable public } /// - /// makes copy of this file with new path + /// Makes copy of this file with new path /// /// /// @@ -66,25 +66,6 @@ public virtual Folder New(string newFullPath, IEnumerable children = nul return folder; } - //public IEnumerable Entries - //{ - // get - // { - // foreach (var file in Files.Values) - // yield return file; - // foreach (var folder in Folders.Values) - // yield return folder; - // } - //} - - // Больше никакой вложенной информации - //public ConcurrentDictionary Files { get; set; } - // = new(StringComparer.InvariantCultureIgnoreCase); - - //public ConcurrentDictionary Folders { get; set; } - // = new(StringComparer.InvariantCultureIgnoreCase); - - /// /// Gets folder name. /// @@ -116,7 +97,7 @@ public ConcurrentDictionary PublicLinks public IEnumerable GetPublicLinks(Cloud cloud) { return PublicLinks.IsEmpty - ? cloud.GetSharedLinks(FullPath) + ? cloud.GetSharedLinks(FullPath) : PublicLinks.Values; } @@ -148,28 +129,5 @@ public PublishInfo ToPublishInfo() info.Items.Add(new PublishInfoItem { Path = FullPath, Urls = PublicLinks.Select(pli => pli.Value.Uri).ToList() }); return info; } - - //public List> GetLinearChildren() - //{ - - //} - //public void Forget(object whomKey) - //{ - // string key = whomKey?.ToString(); - - // if (string.IsNullOrEmpty(key)) - // return; - - // // Удалять начинаем с директорий, т.к. их обычно меньше, - // // а значит поиск должен завершиться в среднем быстрее. - - // if (!Folders.TryRemove(key, out _)) - // { - // // Если по ключу в виде полного пути не удалось удалить директорию, - // // пытаемся по этому же ключу удалить файл, если он есть. - // // Если ничего не удалилось, значит и удалять нечего. - // _ = Files.TryRemove( key, out _); - // } - //} } } diff --git a/MailRuCloud/MailRuCloudApi/Base/HashMatchException.cs b/MailRuCloud/MailRuCloudApi/Base/HashMatchException.cs index 1af35e67..2bfc034d 100644 --- a/MailRuCloud/MailRuCloudApi/Base/HashMatchException.cs +++ b/MailRuCloud/MailRuCloudApi/Base/HashMatchException.cs @@ -13,6 +13,6 @@ public HashMatchException(string localHash, string remoteHash) RemoteHash = remoteHash; } - public override string Message => $"Local and remote hashes doen not match, local = {LocalHash}, remote = {RemoteHash}"; + public override string Message => $"Local and remote hashes does not match, local = {LocalHash}, remote = {RemoteHash}"; } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/HeaderFileContent.cs b/MailRuCloud/MailRuCloudApi/Base/HeaderFileContent.cs index 74c9ff91..de1d724a 100644 --- a/MailRuCloud/MailRuCloudApi/Base/HeaderFileContent.cs +++ b/MailRuCloud/MailRuCloudApi/Base/HeaderFileContent.cs @@ -9,4 +9,4 @@ public class HeaderFileContent public CryptoKeyInfo PublicKey { get; set; } public DateTime CreationDate { get; set; } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/IBasicCredentials.cs b/MailRuCloud/MailRuCloudApi/Base/IBasicCredentials.cs index ef89cc29..2fd017e4 100644 --- a/MailRuCloud/MailRuCloudApi/Base/IBasicCredentials.cs +++ b/MailRuCloud/MailRuCloudApi/Base/IBasicCredentials.cs @@ -7,4 +7,4 @@ internal interface IBasicCredentials bool IsAnonymous { get; } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Protocol.cs b/MailRuCloud/MailRuCloudApi/Base/Protocol.cs index ece03ca5..ce76ce51 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Protocol.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Protocol.cs @@ -4,19 +4,22 @@ public enum Protocol { Autodetect = 0, /// - /// (Cloud.Mail.Ru) mix of mobile and DiskO protocols + /// (Cloud.Mail.Ru) mix of mobile and DiskO protocols /// WebM1Bin, /// - /// (Cloud.Mail.Ru) [deprecated] desktop browser protocol + /// (Cloud.Mail.Ru) [deprecated] desktop browser protocol /// WebV2, /// - /// (Yandex.Disk) desktop browser protocol + /// (Yandex.Disk) desktop browser protocol + /// Протокол работает в двух вариантах: + /// + /// С аутентификацией через логин и пароль + /// - это исходный вариант протокола YadWeb; + /// С аутентификацией через браузер и с использованием его куки + /// - это модифицированный вариант протокола, который раньше был отдельным протоколом YadWebV2. + /// /// - YadWeb, - /// - /// (Yandex.Disk) desktop browser protocol with browser authentication - /// - YadWebV2 + YadWeb } diff --git a/MailRuCloud/MailRuCloudApi/Base/PublicLinkInfo.cs b/MailRuCloud/MailRuCloudApi/Base/PublicLinkInfo.cs index 314a32f7..235dd355 100644 --- a/MailRuCloud/MailRuCloudApi/Base/PublicLinkInfo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/PublicLinkInfo.cs @@ -16,7 +16,7 @@ public class PublicLinkInfo public PublicLinkInfo(string urlstring):this("", new Uri(urlstring)) { } - + public PublicLinkInfo(string type, Uri url) { Init(type,url); @@ -24,7 +24,7 @@ public PublicLinkInfo(string type, Uri url) public PublicLinkInfo(Uri url):this("", url) { - } + } private void Init(string type, string baseurl, string urlstring) { @@ -41,7 +41,7 @@ private void Init(string type, Uri url) Type = type; Uri = url; } - + public string Type { get; set; } public Uri Uri { get; private set; } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/DtoImportExtensions.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/DtoImportExtensions.cs index 7093dbb8..e7d8e9c5 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/DtoImportExtensions.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/DtoImportExtensions.cs @@ -162,4 +162,4 @@ internal static CreateFolderResult ToCreateFolderResult(this CreateFolderRequest return res; } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/IAuth.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/IAuth.cs index 3891265f..5a945542 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/IAuth.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/IAuth.cs @@ -15,4 +15,4 @@ public interface IAuth void ExpireDownloadToken(); } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/ICloudHasher.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/ICloudHasher.cs index d136c9a1..9a489a38 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/ICloudHasher.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/ICloudHasher.cs @@ -13,4 +13,4 @@ public interface ICloudHasher : IDisposable IFileHash Hash { get; } long Length { get; } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/IRequestRepo.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/IRequestRepo.cs index 61a6a1d0..5ae6631a 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/IRequestRepo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/IRequestRepo.cs @@ -12,11 +12,11 @@ namespace YaR.Clouds.Base.Repos { public interface IRequestRepo { - IAuth Authenticator { get; } + IAuth Auth { get; } HttpCommonSettings HttpSettings { get; } - Task ActiveOperationsAsync(); + Task DetectOutsideChanges(); Stream GetDownloadStream(File file, long? start = null, long? end = null); diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/DtoImport.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/DtoImport.cs index 067581a7..8f8dec3e 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/DtoImport.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/DtoImport.cs @@ -5,7 +5,6 @@ using YaR.Clouds.Base.Requests.Types; using YaR.Clouds.Extensions; using YaR.Clouds.Links; -using YaR.Clouds.Common; using System.Collections.Immutable; namespace YaR.Clouds.Base.Repos.MailRuCloud @@ -85,19 +84,19 @@ public static AddFileResult ToAddFileResult(this CommonOperationResult d return res; } - //TODO: move to repo + //TODO: move to repo public static UploadFileResult ToUploadPathResult(this HttpResponseMessage response) { var res = new UploadFileResult { HttpStatusCode = response.StatusCode, HasReturnedData = false }; - if (!response.IsSuccessStatusCode) + if (!response.IsSuccessStatusCode) return res; var strres = response.Content.ReadAsStringAsync().Result; if (string.IsNullOrEmpty(strres)) return res; - + res.HasReturnedData = true; var resp = strres.Split(';'); @@ -162,7 +161,7 @@ public static Dictionary ToShardInfo(this ShardInfoRequest { var dict = new Dictionary { - {ShardType.Video, new ShardInfo{Type = ShardType.Video, Url = webdata.Body.Video[0].Url} }, + {ShardType.Video, new ShardInfo{Type = ShardType.Video, Url = webdata.Body.Video[0].Url} }, {ShardType.ViewDirect, new ShardInfo{Type = ShardType.ViewDirect, Url = webdata.Body.ViewDirect[0].Url} }, {ShardType.WeblinkView, new ShardInfo{Type = ShardType.WeblinkView, Url = webdata.Body.WeblinkView[0].Url} }, {ShardType.WeblinkVideo, new ShardInfo{Type = ShardType.WeblinkVideo, Url = webdata.Body.WeblinkVideo[0].Url} }, @@ -297,7 +296,7 @@ public static File ToFile(this FolderInfoResult data, string publicBaseUrl, var groupedFile = z?.ToGroupedFiles(); - var res = groupedFile?.First(it => string.IsNullOrEmpty(cmpName) || it.Name == cmpName); + var res = groupedFile?.FirstOrDefault(it => string.IsNullOrEmpty(cmpName) || it.Name == cmpName); return res; } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/MailRuBaseRepo.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/MailRuBaseRepo.cs index cb4824bd..a984d984 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/MailRuBaseRepo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/MailRuBaseRepo.cs @@ -15,13 +15,13 @@ abstract class MailRuBaseRepo { private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(MailRuBaseRepo)); - public static readonly string[] AvailDomains = {"mail", "inbox", "bk", "list"}; + public static readonly string[] AvailDomains = {"mail", "inbox", "bk", "list", "vk", "internet"}; protected MailRuBaseRepo(IBasicCredentials credentials) { Credentials = credentials; - if (AvailDomains.Any(d => credentials.Login.Contains($"@{d}."))) + if (AvailDomains.Any(d => credentials.Login.Contains($"@{d}."))) return; string domains = AvailDomains.Aggregate((c, n) => c + ", @" + n); @@ -30,7 +30,7 @@ protected MailRuBaseRepo(IBasicCredentials credentials) protected readonly IBasicCredentials Credentials; - public IAuth Authenticator { get; protected set; } + public IAuth Auth { get; protected set; } public abstract HttpCommonSettings HttpSettings { get; } public abstract Task GetShardInfo(ShardType shardType); @@ -56,7 +56,7 @@ public ICloudHasher GetHasher() private HttpRequestMessage UploadClientRequest(PushStreamContent content, File file) { var shard = GetShardInfo(ShardType.Upload).Result; - var url = new Uri($"{shard.Url}?token={Authenticator.AccessToken}"); //cloud_domain=2&x-email={Authenticator.Login.Replace("@", "%40")}& + var url = new Uri($"{shard.Url}?token={Auth.AccessToken}"); //cloud_domain=2&x-email={Authenticator.Login.Replace("@", "%40")}& var request = new HttpRequestMessage { @@ -66,7 +66,7 @@ private HttpRequestMessage UploadClientRequest(PushStreamContent content, File f request.Headers.TryAddWithoutValidation("User-Agent", HttpSettings.UserAgent); //request.Headers.Add("Host", url.Host); - //request.Headers.Add("Connection", "keep-alive"); + //request.Headers.Add("Connection", "keep-alive"); //request.Headers.Add("X-Requested-With", "XMLHttpRequest"); //request.Headers.Add("Accept", "*/*"); //request.Headers.Add("Origin", "https://cloud.mail.ru"); @@ -105,6 +105,6 @@ public async Task DoUpload(HttpClient client, PushStreamConten "https:/cloud.mail.ru/public" //TODO: may be obsolete? }; - public string PublicBaseUrlDefault => PublicBaseUrls.First(); + public string PublicBaseUrlDefault => PublicBaseUrls.FirstOrDefault(); } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/MailRuSha1Hash.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/MailRuSha1Hash.cs index 0e245663..95a2a794 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/MailRuSha1Hash.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/MailRuSha1Hash.cs @@ -51,7 +51,7 @@ public IFileHash Hash { get { - if (null != _hash) + if (null != _hash) return new FileHashMrc(_hash); if (_length <= 20) diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/MobileRequestRepo.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/MobileRequestRepo.cs index cb0c441a..d43afd41 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/MobileRequestRepo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/MobileRequestRepo.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.IO; using System.Net; using System.Threading; @@ -30,7 +29,7 @@ class MobileRequestRepo : MailRuBaseRepo, IRequestRepo }; public MobileRequestRepo(CloudSettings settings, IWebProxy proxy, IAuth auth, int listDepth) - : base(new Credentials(auth.Login, auth.Password)) + : base(new Credentials(settings, auth.Login, auth.Password)) { _connectionLimiter = new SemaphoreSlim(settings.MaxConnectionCount); _listDepth = listDepth; @@ -38,7 +37,7 @@ public MobileRequestRepo(CloudSettings settings, IWebProxy proxy, IAuth auth, in HttpSettings.CloudSettings = settings; HttpSettings.Proxy = proxy; - Authenticator = auth; + Auth = auth; _metaServer = new Cached(_ => { @@ -73,7 +72,7 @@ public MobileRequestRepo(CloudSettings settings, IWebProxy proxy, IAuth auth, in //private const int DownloadServerExpiresSec = 20 * 60; - + //public HttpWebRequest UploadRequest(File file, UploadMultipartBoundary boundary) //{ @@ -146,7 +145,7 @@ public async Task FolderInfo(RemotePath path, int offset = 0, int limit if (path.IsLink) throw new NotImplementedException(nameof(FolderInfo)); - var req = new ListRequest(HttpSettings, Authenticator, _metaServer.Value.Url, path.Path, _listDepth); + var req = new ListRequest(HttpSettings, Auth, _metaServer.Value.Url, path.Path, _listDepth); var res = await req.MakeRequestAsync(_connectionLimiter); switch (res.Item) @@ -206,7 +205,7 @@ public Task ItemInfo(RemotePath path, int offset = 0, int limi public async Task AccountInfo() { - var req = await new AccountInfoRequest(HttpSettings, Authenticator).MakeRequestAsync(_connectionLimiter); + var req = await new AccountInfoRequest(HttpSettings, Auth).MakeRequestAsync(_connectionLimiter); var res = req.ToAccountInfo(); return res; } @@ -230,7 +229,7 @@ public async Task Rename(string fullPath, string newName) { string target = WebDavPath.Combine(WebDavPath.Parent(fullPath), newName); - await new MoveRequest(HttpSettings, Authenticator, _metaServer.Value.Url, fullPath, target) + await new MoveRequest(HttpSettings, Auth, _metaServer.Value.Url, fullPath, target) .MakeRequestAsync(_connectionLimiter); var res = new RenameResult { IsSuccess = true }; return res; @@ -253,20 +252,20 @@ public void CleanTrash() public async Task CreateFolder(string path) { - var folerRequest = await new CreateFolderRequest(HttpSettings, Authenticator, _metaServer.Value.Url, path) + var folerRequest = await new CreateFolderRequest(HttpSettings, Auth, _metaServer.Value.Url, path) .MakeRequestAsync(_connectionLimiter); return folerRequest.ToCreateFolderResult(); } public async Task AddFile(string fileFullPath, IFileHash fileHash, FileSize fileSize, DateTime dateTime, ConflictResolver? conflictResolver) { - var res = await new MobAddFileRequest(HttpSettings, Authenticator, _metaServer.Value.Url, + var res = await new MobAddFileRequest(HttpSettings, Auth, _metaServer.Value.Url, fileFullPath, fileHash.Hash.Value, fileSize, dateTime, conflictResolver) .MakeRequestAsync(_connectionLimiter); return res.ToAddFileResult(); } - public async Task ActiveOperationsAsync() => await Task.FromResult(null); + public async Task DetectOutsideChanges() => await Task.FromResult(null); } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/AccountInfoRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/AccountInfoRequest.cs index e2b7c5d2..10eedd24 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/AccountInfoRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/AccountInfoRequest.cs @@ -9,6 +9,6 @@ public AccountInfoRequest(HttpCommonSettings settings, IAuth auth) : base(settin { } - protected override string RelationalUri => $"{ConstSettings.CloudDomain}/api/m1/user?access_token={Auth.AccessToken}"; + protected override string RelationalUri => $"{ConstSettings.CloudDomain}/api/m1/user?access_token={_auth.AccessToken}"; } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/BaseRequestMobile.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/BaseRequestMobile.cs index 043cb8fa..759cea93 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/BaseRequestMobile.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/BaseRequestMobile.cs @@ -4,7 +4,7 @@ namespace YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests { - abstract class BaseRequestMobile : BaseRequest where T : class + abstract class BaseRequestMobile : BaseRequest where T : class { private readonly string _metaServer; @@ -13,7 +13,7 @@ protected BaseRequestMobile(HttpCommonSettings settings, IAuth auth, string meta _metaServer = metaServer; } - protected override string RelationalUri => $"{_metaServer}?token={Auth.AccessToken}&client_id={Settings.ClientId}"; + protected override string RelationalUri => $"{_metaServer}?token={_auth.AccessToken}&client_id={_settings.ClientId}"; protected override ResponseBodyStream Transport(Stream stream) { diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/CreateFolderRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/CreateFolderRequest.cs index 25b070cd..66875a9c 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/CreateFolderRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/CreateFolderRequest.cs @@ -63,4 +63,4 @@ private enum OpResult Failed254 = 254 } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/GetServerRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/GetServerRequest.cs index cc5220d9..1ca53e44 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/GetServerRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/GetServerRequest.cs @@ -7,7 +7,7 @@ internal class GetServerRequest : ServerRequest public GetServerRequest(HttpCommonSettings settings) : base(settings) { } - + protected override string RelationalUri => "https://dispatcher.cloud.mail.ru/d"; } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/GetUploadServerRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/GetUploadServerRequest.cs index a529f5db..0d651105 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/GetUploadServerRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/GetUploadServerRequest.cs @@ -7,7 +7,7 @@ internal class GetUploadServerRequest : ServerRequest public GetUploadServerRequest(HttpCommonSettings settings) : base(settings) { } - + protected override string RelationalUri => "https://dispatcher.cloud.mail.ru/u"; } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/ListRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/ListRequest.cs index 2f5c9d2f..5061fc75 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/ListRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/ListRequest.cs @@ -23,7 +23,7 @@ public ListRequest(HttpCommonSettings settings, IAuth auth, string metaServer, s public long Depth { get; set; } public Option Options { get; set; } = Option.Unknown128 | Option.Unknown256 | Option.FolderSize | Option.TotalSpace | Option.UsedSpace; - + [Flags] internal enum Option { @@ -109,7 +109,7 @@ private enum ParseOp PinUpper = 3, Unknown15 = 15 } - + private FsItem Deserialize(ResponseBodyStream data, string fullPath) { fullPath = WebDavPath.Clean(fullPath); @@ -183,7 +183,7 @@ private FsItem GetItem(ResponseBodyStream data, FsFolder folder) int head = data.ReadIntSpl(); if ((head & 4096) != 0) { - data.ReadNBytes(16); // var nodeId = + data.ReadNBytes(16); // var nodeId = } string name = data.ReadNBytesAsString(data.ReadShort()); @@ -194,7 +194,7 @@ private FsItem GetItem(ResponseBodyStream data, FsFolder folder) : null; void ProcessDelete() { - if ((Options & Option.Delete) == 0) + if ((Options & Option.Delete) == 0) return; data.ReadPu32(); // dunno @@ -255,4 +255,4 @@ public class Result : RevisionResponseResult public FsItem Item { get; set; } } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/MobAddFileRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/MobAddFileRequest.cs index 05ee6afa..c5cb68ac 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/MobAddFileRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/MobAddFileRequest.cs @@ -15,7 +15,7 @@ class MobAddFileRequest : BaseRequestMobile private readonly ConflictResolver _conflictResolver; private readonly DateTime _dateTime; - public MobAddFileRequest(HttpCommonSettings settings, IAuth auth, string metaServer, string fullPath, byte[] hash, long size, DateTime? dateTime, ConflictResolver? conflict) + public MobAddFileRequest(HttpCommonSettings settings, IAuth auth, string metaServer, string fullPath, byte[] hash, long size, DateTime? dateTime, ConflictResolver? conflict) : base(settings, auth, metaServer) { _fullPath = fullPath; @@ -25,7 +25,7 @@ public MobAddFileRequest(HttpCommonSettings settings, IAuth auth, string metaSer _dateTime = (dateTime ?? DateTime.Now).ToUniversalTime(); } - public MobAddFileRequest(HttpCommonSettings settings, IAuth auth, string metaServer, string fullPath, string hash, long size, DateTime? dateTime, ConflictResolver? conflict) + public MobAddFileRequest(HttpCommonSettings settings, IAuth auth, string metaServer, string fullPath, string hash, long size, DateTime? dateTime, ConflictResolver? conflict) : this(settings, auth, metaServer, fullPath, hash?.HexStringToByteArray(), size, dateTime, conflict) { } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/MobMetaServerRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/MobMetaServerRequest.cs index 32bbed3b..1180e049 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/MobMetaServerRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/MobMetaServerRequest.cs @@ -10,4 +10,4 @@ public MobMetaServerRequest(HttpCommonSettings settings) : base(settings) protected override string RelationalUri => "https://dispatcher.cloud.mail.ru/m"; } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/MoveRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/MoveRequest.cs index 60bb76dd..3ba8d697 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/MoveRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/MoveRequest.cs @@ -70,4 +70,4 @@ private enum OpResult } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/OAuthRefreshRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/OAuthRefreshRequest.cs index 1836dc6d..cddbc35f 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/OAuthRefreshRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/OAuthRefreshRequest.cs @@ -18,7 +18,7 @@ public OAuthRefreshRequest(HttpCommonSettings settings, string refreshToken) : b protected override byte[] CreateHttpContent() { - var data = $"client_id={Settings.ClientId}&grant_type=refresh_token&refresh_token={_refreshToken}"; + var data = $"client_id={_settings.ClientId}&grant_type=refresh_token&refresh_token={_refreshToken}"; return Encoding.UTF8.GetBytes(data); } @@ -26,7 +26,7 @@ protected override HttpWebRequest CreateRequest(string baseDomain = null) { var request = base.CreateRequest(baseDomain); request.Host = request.RequestUri.Host; - request.UserAgent = Settings.UserAgent; + request.UserAgent = _settings.UserAgent; request.Accept = "*/*"; request.ServicePoint.Expect100Continue = false; @@ -49,4 +49,4 @@ public class Result public string ErrorDescription { get; set; } } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/OAuthRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/OAuthRequest.cs index e2b49a8f..0705e8f7 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/OAuthRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/OAuthRequest.cs @@ -24,7 +24,7 @@ protected override byte[] CreateHttpContent() { new("username", _login), new("password", _password), - new("client_id", Settings.ClientId), + new("client_id", _settings.ClientId), new("grant_type", "password") }; return new FormUrlEncodedContent(keyValues).ReadAsByteArrayAsync().Result; diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/OAuthSecondStepRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/OAuthSecondStepRequest.cs index ac7682a6..2b35646c 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/OAuthSecondStepRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/OAuthSecondStepRequest.cs @@ -10,7 +10,8 @@ class OAuthSecondStepRequest : BaseRequestJson private readonly string _tsaToken; private readonly string _authCode; - public OAuthSecondStepRequest(HttpCommonSettings settings, string login, string tsaToken, string authCode) : base(settings, null) + public OAuthSecondStepRequest(HttpCommonSettings settings, string login, string tsaToken, string authCode) + : base(settings, null) { _login = login; _tsaToken = tsaToken; @@ -22,9 +23,9 @@ public OAuthSecondStepRequest(HttpCommonSettings settings, string login, string protected override byte[] CreateHttpContent() { #pragma warning disable SYSLIB0013 // Type or member is obsolete - var data = $"client_id={Settings.ClientId}&grant_type=password&username={Uri.EscapeUriString(_login)}&tsa_token={_tsaToken}&auth_code={_authCode}"; + var data = $"client_id={_settings.ClientId}&grant_type=password&username={Uri.EscapeUriString(_login)}&tsa_token={_tsaToken}&auth_code={_authCode}"; #pragma warning restore SYSLIB0013 // Type or member is obsolete return Encoding.UTF8.GetBytes(data); } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/ServerRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/ServerRequest.cs index 1d260b92..d1952d4d 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/ServerRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/ServerRequest.cs @@ -27,4 +27,4 @@ protected override RequestResponse DeserializeMessage(NameV return msg; } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/SharedFoldersListRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/SharedFoldersListRequest.cs index 60d37f3c..b13f6f4b 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/SharedFoldersListRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/SharedFoldersListRequest.cs @@ -72,4 +72,4 @@ internal class Result public Dictionary Container; } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/Types/CloudFolderType.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/Types/CloudFolderType.cs index 46835b89..ec44b4da 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/Types/CloudFolderType.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/Types/CloudFolderType.cs @@ -8,4 +8,4 @@ enum CloudFolderType MountPointChild, SharedChild } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/Types/FsFile.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/Types/FsFile.cs index bd82370d..af403847 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/Types/FsFile.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/Types/FsFile.cs @@ -18,4 +18,4 @@ public FsFile(string fullPath, DateTime modifDate, byte[] sha1, ulong size) Size = size; } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/Types/FsFolder.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/Types/FsFolder.cs index 7459fb31..ab9a8bcb 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/Types/FsFolder.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/Types/FsFolder.cs @@ -4,7 +4,7 @@ internal class FsFolder : FsItem { public string FullPath { get; } public CloudFolderType Type { get; } - + private TreeId _treeId; public FsFolder Parent { get; } @@ -21,4 +21,4 @@ public FsFolder(string fullPath, TreeId treeId, CloudFolderType cloudFolderType, public bool IsChildrenLoaded { get; set; } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/Types/FsItem.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/Types/FsItem.cs index 988b02ca..0f4d9a1b 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/Types/FsItem.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/Types/FsItem.cs @@ -6,4 +6,4 @@ public class FsItem { public List Items { get; } = new(); } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/Types/Operation.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/Types/Operation.cs index 4e2b3936..6a176d73 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/Types/Operation.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/Types/Operation.cs @@ -9,4 +9,4 @@ internal enum Operation : byte SharedFoldersList = 121 //ItemInfo = 154, } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/Types/RequestBodyStream.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/Types/RequestBodyStream.cs index a471545e..5d1e2bd1 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/Types/RequestBodyStream.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/Types/RequestBodyStream.cs @@ -49,7 +49,7 @@ public void WritePu64(long value) int high = (int)value & 127; value >>= 7; intValue = (int)value; - if (intValue != 0) + if (intValue != 0) high |= 128; _stream.WriteByte((byte)high); } while (intValue != 0); @@ -78,4 +78,4 @@ public void Dispose() _stream?.Dispose(); } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/Types/Revision.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/Types/Revision.cs index 904c0347..ab491bcc 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/Types/Revision.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/Types/Revision.cs @@ -47,4 +47,4 @@ public static Revision FromStream(ResponseBodyStream stream) }; } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/Types/RevisionResponseResult.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/Types/RevisionResponseResult.cs index 1db802b2..77bdec47 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/Types/RevisionResponseResult.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/Types/RevisionResponseResult.cs @@ -4,4 +4,4 @@ class RevisionResponseResult : BaseResponseResult { public Revision Revision { get; set; } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/Types/TreeId.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/Types/TreeId.cs index fdceb394..29663886 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/Types/TreeId.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/Types/TreeId.cs @@ -24,4 +24,4 @@ public static TreeId FromStream(ResponseBodyStream stream) } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/WeblinkGetServerRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/WeblinkGetServerRequest.cs index 06ca1ba1..0560339e 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/WeblinkGetServerRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/WeblinkGetServerRequest.cs @@ -28,4 +28,4 @@ protected override string RelationalUri } } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/OAuth.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/OAuth.cs index 58cbcaa5..7a6b93fe 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/OAuth.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/OAuth.cs @@ -8,7 +8,6 @@ using YaR.Clouds.Common; using static YaR.Clouds.Cloud; - namespace YaR.Clouds.Base.Repos.MailRuCloud { internal class OAuth : IAuth @@ -32,9 +31,11 @@ public OAuth(SemaphoreSlim connectionLimiter, _authToken = new Cached(old => { - Logger.Debug(null == old ? "OAuth: authorizing." : "OAuth: AuthToken expired, refreshing."); + Logger.Debug(old is null + ? "OAuth: authorizing." + : "OAuth: AuthToken expired, refreshing."); - var token = null == old || string.IsNullOrEmpty(old.RefreshToken) + var token = string.IsNullOrEmpty(old?.RefreshToken) ? Auth().Result : Refresh(old.RefreshToken).Result; @@ -68,7 +69,7 @@ private async Task Auth() var req = await new OAuthRequest(_settings, _creds).MakeRequestAsync(_connectionLimiter); var res = req.ToAuthTokenResult(); - if (!res.IsSecondStepRequired) + if (!res.IsSecondStepRequired) return res; if (null == _onAuthCodeRequired) @@ -93,4 +94,4 @@ private async Task Refresh(string refreshToken) return res; } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/ShardManager.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/ShardManager.cs index 5f83475d..46b7b05e 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/ShardManager.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/ShardManager.cs @@ -53,7 +53,7 @@ public ShardManager(SemaphoreSlim connectionLimiter, IRequestRepo repo) _ => TimeSpan.FromSeconds(ShardsExpiresInSec)); - WeblinkDownloadServersPending = new Pending>(8, + WebLinkDownloadServersPending = new Pending>(8, () => new Cached(_ => { var data = new WeblinkGetServerRequest(httpsettings).MakeRequestAsync(connectionLimiter).Result; @@ -68,7 +68,7 @@ public ShardManager(SemaphoreSlim connectionLimiter, IRequestRepo repo) public Pending> DownloadServersPending { get; } - public Pending> WeblinkDownloadServersPending { get; } + public Pending> WebLinkDownloadServersPending { get; } public ShardInfo MetaServer => new() {Url = _metaServer.Value.Url, Count = _metaServer.Value.Unknown}; private readonly Cached _metaServer; diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebBin/Requests/DownloadRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebBin/Requests/DownloadRequest.cs index fb428c0e..175c2bde 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebBin/Requests/DownloadRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebBin/Requests/DownloadRequest.cs @@ -2,24 +2,23 @@ using System.Linq; using System.Net; using System.Collections.Generic; - using YaR.Clouds.Base.Requests; namespace YaR.Clouds.Base.Repos.MailRuCloud.WebBin.Requests { class DownloadRequest { - public DownloadRequest(HttpCommonSettings settings, IAuth authenticator, - File file, long instart, long inend, string downServerUrl, IEnumerable publicBaseUrls) + public DownloadRequest(HttpCommonSettings settings, IAuth auth, + File file, long inStart, long inEnd, string downServerUrl, IEnumerable publicBaseUrls) { - Request = CreateRequest(settings, authenticator, file, instart, inend, downServerUrl, publicBaseUrls); + Request = CreateRequest(settings, auth, file, inStart, inEnd, downServerUrl, publicBaseUrls); } public HttpWebRequest Request { get; } private static HttpWebRequest CreateRequest(HttpCommonSettings settings, - IAuth authenticator, File file, long instart, long inend, string downServerUrl, IEnumerable publicBaseUrls) - //(IAuth authenticator, IWebProxy proxy, string url, long instart, long inend, string userAgent) + IAuth auth, File file, long instart, long inend, string downServerUrl, IEnumerable publicBaseUrls) + //(IAuth authenticator, IWebProxy proxy, string url, long inStart, long inEnd, string userAgent) { bool isLinked = !file.PublicLinks.IsEmpty; @@ -31,7 +30,7 @@ private static HttpWebRequest CreateRequest(HttpCommonSettings settings, var uriistr = urii?.OriginalString; var baseura = uriistr == null ? null - : publicBaseUrls.First(pbu => uriistr.StartsWith(pbu, StringComparison.InvariantCulture)); + : publicBaseUrls.FirstOrDefault(pbu => uriistr.StartsWith(pbu, StringComparison.InvariantCulture)); if (string.IsNullOrEmpty(baseura)) throw new ArgumentException("URL does not starts with base URL"); @@ -40,7 +39,7 @@ private static HttpWebRequest CreateRequest(HttpCommonSettings settings, else { url = $"{downServerUrl}{Uri.EscapeDataString(file.FullPath.TrimStart('/'))}"; - url += $"?client_id={settings.ClientId}&token={authenticator.AccessToken}"; + url += $"?client_id={settings.ClientId}&token={auth.AccessToken}"; } var uri = new Uri(url); diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebBin/WebBinRequestRepo.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebBin/WebBinRequestRepo.cs index 19c406ff..85ea333c 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebBin/WebBinRequestRepo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebBin/WebBinRequestRepo.cs @@ -43,7 +43,8 @@ class WebBinRequestRepo : MailRuBaseRepo, IRequestRepo public sealed override HttpCommonSettings HttpSettings { get; } = new() { - ClientId = "cloud-android" + //ClientId = "cloud-android" + ClientId = "cloud-win" }; public WebBinRequestRepo(CloudSettings settings, IBasicCredentials credentials, AuthCodeRequiredDelegate onAuthCodeRequired) @@ -56,7 +57,7 @@ public WebBinRequestRepo(CloudSettings settings, IBasicCredentials credentials, HttpSettings.Proxy = settings.Proxy; _onAuthCodeRequired = onAuthCodeRequired; - Authenticator = new OAuth(_connectionLimiter, HttpSettings, credentials, onAuthCodeRequired); + Auth = new OAuth(_connectionLimiter, HttpSettings, credentials, onAuthCodeRequired); ShardManager = new ShardManager(_connectionLimiter, this); @@ -81,7 +82,7 @@ private DownloadStream GetDownloadStreamInternal(File file, long? start = null, Cached downServer = null; var pendingServers = isLinked - ? ShardManager.WeblinkDownloadServersPending + ? ShardManager.WebLinkDownloadServersPending : ShardManager.DownloadServersPending; Stopwatch watch = new Stopwatch(); @@ -92,7 +93,7 @@ CustomDisposable ResponseGenerator(long instart, long inend, Fi { downServer = pendingServers.Next(downServer); - request = new DownloadRequest(HttpSettings, Authenticator, file, instart, inend, downServer.Value.Url, PublicBaseUrls); + request = new DownloadRequest(HttpSettings, Auth, file, instart, inend, downServer.Value.Url, PublicBaseUrls); watch.Start(); var response = (HttpWebResponse)request.GetResponse(); @@ -142,7 +143,7 @@ public override async Task GetShardInfo(ShardType shardType) var banned = ShardManager.BannedShards.Value; if (banned.All(bsh => bsh.Url != ishard.Url)) { - if (refreshed) Authenticator.ExpireDownloadToken(); + if (refreshed) Auth.ExpireDownloadToken(); return ishard; } ShardManager.CachedShards.Expire(); @@ -158,14 +159,14 @@ public override async Task GetShardInfo(ShardType shardType) public async Task CloneItem(string fromUrl, string toPath) { - var req = await new CloneItemRequest(HttpSettings, Authenticator, fromUrl, toPath).MakeRequestAsync(_connectionLimiter); + var req = await new CloneItemRequest(HttpSettings, Auth, fromUrl, toPath).MakeRequestAsync(_connectionLimiter); var res = req.ToCloneItemResult(); return res; } public async Task Copy(string sourceFullPath, string destinationPath, ConflictResolver? conflictResolver = null) { - var req = await new CopyRequest(HttpSettings, Authenticator, sourceFullPath, destinationPath, conflictResolver).MakeRequestAsync(_connectionLimiter); + var req = await new CopyRequest(HttpSettings, Auth, sourceFullPath, destinationPath, conflictResolver).MakeRequestAsync(_connectionLimiter); var res = req.ToCopyResult(); return res; } @@ -176,7 +177,7 @@ public async Task Move(string sourceFullPath, string destinationPath //var res = req.ToCopyResult(); //return res; - var req = await new MoveRequest(HttpSettings, Authenticator, ShardManager.MetaServer.Url, sourceFullPath, destinationPath) + var req = await new MoveRequest(HttpSettings, Auth, ShardManager.MetaServer.Url, sourceFullPath, destinationPath) .MakeRequestAsync(_connectionLimiter); var res = req.ToCopyResult(WebDavPath.Name(destinationPath)); @@ -189,7 +190,7 @@ private async Task FolderInfo(string path, int depth = 1) try { ListRequest.Result dataRes = - await new ListRequest(HttpSettings, Authenticator, ShardManager.MetaServer.Url, path, depth) + await new ListRequest(HttpSettings, Auth, ShardManager.MetaServer.Url, path, depth) .MakeRequestAsync(_connectionLimiter); // если файл разбит или зашифрован - то надо взять все куски @@ -202,7 +203,7 @@ private async Task FolderInfo(string path, int depth = 1) string name = WebDavPath.Name(path); path = WebDavPath.Parent(path); - dataRes = await new ListRequest(HttpSettings, Authenticator, ShardManager.MetaServer.Url, path, 1) + dataRes = await new ListRequest(HttpSettings, Auth, ShardManager.MetaServer.Url, path, 1) .MakeRequestAsync(_connectionLimiter); var folder = dataRes.ToFolder(); @@ -232,7 +233,7 @@ public async Task FolderInfo(RemotePath path, int offset = 0, int limit FolderInfoResult datares; try { - datares = await new FolderInfoRequest(HttpSettings, Authenticator, path, offset, limit) + datares = await new FolderInfoRequest(HttpSettings, Auth, path, offset, limit) .MakeRequestAsync(_connectionLimiter); } catch (WebException e) when (e.Response is HttpWebResponse { StatusCode: HttpStatusCode.NotFound }) @@ -261,6 +262,9 @@ public async Task FolderInfo(RemotePath path, int offset = 0, int limit nameReplacement: path.Link?.IsLinkedToFileSystem ?? true ? WebDavPath.Name(path.Path) : path.Link.Name) : (IEntry)datares.ToFolder(PublicBaseUrlDefault, path.Path, path.Link); + if (limit == int.MaxValue && entry is Folder fld) + fld.IsChildrenLoaded = limit == int.MaxValue; + return entry; } @@ -270,20 +274,20 @@ public async Task FolderInfo(RemotePath path, int offset = 0, int limit public async Task ItemInfo(RemotePath path, int offset = 0, int limit = int.MaxValue) { - var req = await new ItemInfoRequest(HttpSettings, Authenticator, path, offset, limit).MakeRequestAsync(_connectionLimiter); + var req = await new ItemInfoRequest(HttpSettings, Auth, path, offset, limit).MakeRequestAsync(_connectionLimiter); return req; } public async Task AccountInfo() { - var req = await new AccountInfoRequest(HttpSettings, Authenticator).MakeRequestAsync(_connectionLimiter); + var req = await new AccountInfoRequest(HttpSettings, Auth).MakeRequestAsync(_connectionLimiter); var res = req.ToAccountInfo(); return res; } public async Task Publish(string fullPath) { - var req = await new PublishRequest(HttpSettings, Authenticator, fullPath).MakeRequestAsync(_connectionLimiter); + var req = await new PublishRequest(HttpSettings, Auth, fullPath).MakeRequestAsync(_connectionLimiter); var res = req.ToPublishResult(); if (res.IsSuccess) @@ -302,14 +306,14 @@ public async Task Unpublish(Uri publicLink, string fullPath) CachedSharedList.Value.Remove(item.Key); } - var req = await new UnpublishRequest(this, HttpSettings, Authenticator, publicLink.OriginalString).MakeRequestAsync(_connectionLimiter); + var req = await new UnpublishRequest(this, HttpSettings, Auth, publicLink.OriginalString).MakeRequestAsync(_connectionLimiter); var res = req.ToUnpublishResult(); return res; } public async Task Remove(string fullPath) { - var req = await new RemoveRequest(HttpSettings, Authenticator, fullPath).MakeRequestAsync(_connectionLimiter); + var req = await new RemoveRequest(HttpSettings, Auth, fullPath).MakeRequestAsync(_connectionLimiter); var res = req.ToRemoveResult(); return res; } @@ -321,7 +325,7 @@ public async Task Rename(string fullPath, string newName) //return res; string newFullPath = WebDavPath.Combine(WebDavPath.Parent(fullPath), newName); - var req = await new MoveRequest(HttpSettings, Authenticator, ShardManager.MetaServer.Url, fullPath, newFullPath) + var req = await new MoveRequest(HttpSettings, Auth, ShardManager.MetaServer.Url, fullPath, newFullPath) .MakeRequestAsync(_connectionLimiter); var res = req.ToRenameResult(); @@ -330,10 +334,10 @@ public async Task Rename(string fullPath, string newName) public Dictionary GetShardInfo1() { - return Authenticator.IsAnonymous + return Auth.IsAnonymous ? new WebV2.Requests - .ShardInfoRequest(HttpSettings, Authenticator).MakeRequestAsync(_connectionLimiter).Result.ToShardInfo() - : new ShardInfoRequest(HttpSettings, Authenticator).MakeRequestAsync(_connectionLimiter).Result.ToShardInfo(); + .ShardInfoRequest(HttpSettings, Auth).MakeRequestAsync(_connectionLimiter).Result.ToShardInfo() + : new ShardInfoRequest(HttpSettings, Auth).MakeRequestAsync(_connectionLimiter).Result.ToShardInfo(); } @@ -360,7 +364,7 @@ public Cached>> CachedSharedList private async Task GetShareListInner() { - var res = await new SharedListRequest(HttpSettings, Authenticator) + var res = await new SharedListRequest(HttpSettings, Auth) .MakeRequestAsync(_connectionLimiter); return res; @@ -385,7 +389,7 @@ public async Task CreateFolder(string path) //return (await new CreateFolderRequest(HttpSettings, Authenticator, path).MakeRequestAsync()) // .ToCreateFolderResult(); - return (await new CreateFolderRequest(HttpSettings, Authenticator, ShardManager.MetaServer.Url, path).MakeRequestAsync(_connectionLimiter)) + return (await new CreateFolderRequest(HttpSettings, Auth, ShardManager.MetaServer.Url, path).MakeRequestAsync(_connectionLimiter)) .ToCreateFolderResult(); } @@ -398,13 +402,13 @@ public async Task AddFile(string fileFullPath, IFileHash fileHash //using Mobile request because of supporting file modified time //TODO: refact, make mixed repo - var req = await new MobAddFileRequest(HttpSettings, Authenticator, ShardManager.MetaServer.Url, fileFullPath, fileHash.Hash.Value, fileSize, dateTime, conflictResolver) + var req = await new MobAddFileRequest(HttpSettings, Auth, ShardManager.MetaServer.Url, fileFullPath, fileHash.Hash.Value, fileSize, dateTime, conflictResolver) .MakeRequestAsync(_connectionLimiter); var res = req.ToAddFileResult(); return res; } - public async Task ActiveOperationsAsync() => await Task.FromResult(null); + public async Task DetectOutsideChanges() => await Task.FromResult(null); } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/AccountInfoRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/AccountInfoRequest.cs index 3f122b4a..baa104be 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/AccountInfoRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/AccountInfoRequest.cs @@ -9,6 +9,6 @@ public AccountInfoRequest(HttpCommonSettings settings, IAuth auth) : base(settin { } - protected override string RelationalUri => $"{ConstSettings.CloudDomain}/api/m1/user?access_token={Auth.AccessToken}"; + protected override string RelationalUri => $"{ConstSettings.CloudDomain}/api/m1/user?access_token={_auth.AccessToken}"; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/CloneItemRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/CloneItemRequest.cs index 709a8655..b4dc853d 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/CloneItemRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/CloneItemRequest.cs @@ -9,7 +9,7 @@ class CloneItemRequest : BaseRequestJson> private readonly string _fromUrl; private readonly string _toPath; - public CloneItemRequest(HttpCommonSettings settings, IAuth auth, string fromUrl, string toPath) + public CloneItemRequest(HttpCommonSettings settings, IAuth auth, string fromUrl, string toPath) : base(settings, auth) { _fromUrl = fromUrl; @@ -20,7 +20,7 @@ protected override string RelationalUri { get { - var uri = $"{ConstSettings.CloudDomain}/api/m1/clone?conflict=rename&folder={Uri.EscapeDataString(_toPath)}&weblink={Uri.EscapeDataString(_fromUrl)}&access_token={Auth.AccessToken}"; + var uri = $"{ConstSettings.CloudDomain}/api/m1/clone?conflict=rename&folder={Uri.EscapeDataString(_toPath)}&weblink={Uri.EscapeDataString(_fromUrl)}&access_token={_auth.AccessToken}"; return uri; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/CopyRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/CopyRequest.cs index 384e5b65..a6c1f3cf 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/CopyRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/CopyRequest.cs @@ -12,14 +12,14 @@ class CopyRequest : BaseRequestJson> private readonly ConflictResolver _conflictResolver; /// - /// + /// /// /// /// /// /// (without item name) /// - public CopyRequest(HttpCommonSettings settings, IAuth auth, string sourceFullPath, string destinationPath, ConflictResolver? conflictResolver = null) + public CopyRequest(HttpCommonSettings settings, IAuth auth, string sourceFullPath, string destinationPath, ConflictResolver? conflictResolver = null) : base(settings, auth) { _sourceFullPath = sourceFullPath; @@ -27,12 +27,12 @@ public CopyRequest(HttpCommonSettings settings, IAuth auth, string sourceFullPat _conflictResolver = conflictResolver ?? ConflictResolver.Rename; } - protected override string RelationalUri => $"/api/m1/file/copy?access_token={Auth.AccessToken}"; + protected override string RelationalUri => $"/api/m1/file/copy?access_token={_auth.AccessToken}"; protected override byte[] CreateHttpContent() { - var data = $"home={Uri.EscapeDataString(_sourceFullPath)}&email={Auth.Login}&x-email={Auth.Login}&conflict={_conflictResolver}&folder={Uri.EscapeDataString(_destinationPath)}"; + var data = $"home={Uri.EscapeDataString(_sourceFullPath)}&email={_auth.Login}&x-email={_auth.Login}&conflict={_conflictResolver}&folder={Uri.EscapeDataString(_destinationPath)}"; return Encoding.UTF8.GetBytes(data); } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/CreateFileRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/CreateFileRequest.cs index 74431a90..1de0701a 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/CreateFileRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/CreateFileRequest.cs @@ -12,7 +12,7 @@ class CreateFileRequest : BaseRequestJson> private readonly long _size; private readonly ConflictResolver _conflictResolver; - public CreateFileRequest(HttpCommonSettings settings, IAuth auth, string fullPath, string hash, long size, ConflictResolver? conflictResolver) + public CreateFileRequest(HttpCommonSettings settings, IAuth auth, string fullPath, string hash, long size, ConflictResolver? conflictResolver) : base(settings, auth) { _fullPath = fullPath; @@ -21,7 +21,7 @@ public CreateFileRequest(HttpCommonSettings settings, IAuth auth, string fullPat _conflictResolver = conflictResolver ?? ConflictResolver.Rename; } - protected override string RelationalUri => $"/api/m1/file/add?access_token={Auth.AccessToken}"; + protected override string RelationalUri => $"/api/m1/file/add?access_token={_auth.AccessToken}"; protected override byte[] CreateHttpContent() { diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/CreateFolderRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/CreateFolderRequest.cs index 57ef16a1..99f41ed8 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/CreateFolderRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/CreateFolderRequest.cs @@ -9,13 +9,13 @@ class CreateFolderRequest : BaseRequestJson> { private readonly string _fullPath; - public CreateFolderRequest(HttpCommonSettings settings, IAuth auth, string fullPath) + public CreateFolderRequest(HttpCommonSettings settings, IAuth auth, string fullPath) : base(settings, auth) { _fullPath = fullPath; } - protected override string RelationalUri => $"/api/m1/folder/add?access_token={Auth.AccessToken}"; + protected override string RelationalUri => $"/api/m1/folder/add?access_token={_auth.AccessToken}"; protected override byte[] CreateHttpContent() { diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/FolderInfoRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/FolderInfoRequest.cs index cba10611..c864806a 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/FolderInfoRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/FolderInfoRequest.cs @@ -12,8 +12,8 @@ internal class FolderInfoRequest : BaseRequestJson private readonly int _offset; private readonly int _limit; - - public FolderInfoRequest(HttpCommonSettings settings, IAuth auth, RemotePath path, int offset = 0, int limit = int.MaxValue) + + public FolderInfoRequest(HttpCommonSettings settings, IAuth auth, RemotePath path, int offset = 0, int limit = int.MaxValue) : base(settings, auth) { _isWebLink = path.IsLink; @@ -25,12 +25,12 @@ public FolderInfoRequest(HttpCommonSettings settings, IAuth auth, RemotePath pat } else _path = path.Path; - + _offset = offset; _limit = limit; } - protected override string RelationalUri => $"/api/m1/folder?access_token={Auth.AccessToken}&offset={_offset}&limit={_limit}"; + protected override string RelationalUri => $"/api/m1/folder?access_token={_auth.AccessToken}&offset={_offset}&limit={_limit}"; protected override byte[] CreateHttpContent() diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/ItemInfoRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/ItemInfoRequest.cs index 353d7e54..9ceb8226 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/ItemInfoRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/ItemInfoRequest.cs @@ -2,7 +2,7 @@ using YaR.Clouds.Base.Requests; using YaR.Clouds.Base.Requests.Types; -namespace YaR.Clouds.Base.Repos.MailRuCloud.WebM1.Requests +namespace YaR.Clouds.Base.Repos.MailRuCloud.WebM1.Requests { class ItemInfoRequest : BaseRequestJson { @@ -12,7 +12,7 @@ class ItemInfoRequest : BaseRequestJson private readonly int _limit; - public ItemInfoRequest(HttpCommonSettings settings, IAuth auth, RemotePath path, int offset = 0, int limit = int.MaxValue) + public ItemInfoRequest(HttpCommonSettings settings, IAuth auth, RemotePath path, int offset = 0, int limit = int.MaxValue) : base(settings, auth) { _isWebLink = path.IsLink; @@ -34,10 +34,10 @@ protected override string RelationalUri get { var uri = _isWebLink - ? $"/api/m1/file?access_token={Auth.AccessToken}&weblink={_path}&offset={_offset}&limit={_limit}" - : $"/api/m1/file?access_token={Auth.AccessToken}&home={Uri.EscapeDataString(_path)}&offset={_offset}&limit={_limit}"; + ? $"/api/m1/file?access_token={_auth.AccessToken}&weblink={_path}&offset={_offset}&limit={_limit}" + : $"/api/m1/file?access_token={_auth.AccessToken}&home={Uri.EscapeDataString(_path)}&offset={_offset}&limit={_limit}"; return uri; } } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/MoveRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/MoveRequest.cs index 2757f2c4..7d711f81 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/MoveRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/MoveRequest.cs @@ -10,18 +10,18 @@ class MoveRequest : BaseRequestJson> private readonly string _sourceFullPath; private readonly string _destinationPath; - public MoveRequest(HttpCommonSettings settings, IAuth auth, string sourceFullPath, string destinationPath) + public MoveRequest(HttpCommonSettings settings, IAuth auth, string sourceFullPath, string destinationPath) : base(settings, auth) { _sourceFullPath = sourceFullPath; _destinationPath = destinationPath; } - protected override string RelationalUri => $"/api/m1/file/move?access_token={Auth.AccessToken}"; + protected override string RelationalUri => $"/api/m1/file/move?access_token={_auth.AccessToken}"; protected override byte[] CreateHttpContent() { - var data = $"home={Uri.EscapeDataString(_sourceFullPath)}&email={Auth.Login}&conflict=rename&folder={Uri.EscapeDataString(_destinationPath)}"; + var data = $"home={Uri.EscapeDataString(_sourceFullPath)}&email={_auth.Login}&conflict=rename&folder={Uri.EscapeDataString(_destinationPath)}"; return Encoding.UTF8.GetBytes(data); } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/PublishRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/PublishRequest.cs index 95c61faf..bd3ac53e 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/PublishRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/PublishRequest.cs @@ -9,18 +9,18 @@ class PublishRequest : BaseRequestJson> { private readonly string _fullPath; - public PublishRequest(HttpCommonSettings settings, IAuth auth, string fullPath) + public PublishRequest(HttpCommonSettings settings, IAuth auth, string fullPath) : base(settings, auth) { _fullPath = fullPath; } - protected override string RelationalUri => $"/api/m1/file/publish?access_token={Auth.AccessToken}"; + protected override string RelationalUri => $"/api/m1/file/publish?access_token={_auth.AccessToken}"; protected override byte[] CreateHttpContent() { - var data = $"home={Uri.EscapeDataString(_fullPath)}&email={Auth.Login}&x-email={Auth.Login}"; + var data = $"home={Uri.EscapeDataString(_fullPath)}&email={_auth.Login}&x-email={_auth.Login}"; return Encoding.UTF8.GetBytes(data); } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/RemoveRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/RemoveRequest.cs index b02c658d..2ab7d369 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/RemoveRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/RemoveRequest.cs @@ -5,18 +5,17 @@ namespace YaR.Clouds.Base.Repos.MailRuCloud.WebM1.Requests { - class RemoveRequest : BaseRequestJson> { private readonly string _fullPath; - public RemoveRequest(HttpCommonSettings settings, IAuth auth, string fullPath) + public RemoveRequest(HttpCommonSettings settings, IAuth auth, string fullPath) : base(settings, auth) { _fullPath = fullPath; } - protected override string RelationalUri => $"/api/m1/file/remove?access_token={Auth.AccessToken}"; + protected override string RelationalUri => $"/api/m1/file/remove?access_token={_auth.AccessToken}"; protected override byte[] CreateHttpContent() { diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/RenameRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/RenameRequest.cs index 560b9255..819fa33d 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/RenameRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/RenameRequest.cs @@ -10,18 +10,18 @@ class RenameRequest : BaseRequestJson> private readonly string _fullPath; private readonly string _newName; - public RenameRequest(HttpCommonSettings settings, IAuth auth, string fullPath, string newName) + public RenameRequest(HttpCommonSettings settings, IAuth auth, string fullPath, string newName) : base(settings, auth) { _fullPath = fullPath; _newName = newName; } - protected override string RelationalUri => $"/api/m1/file/rename?access_token={Auth.AccessToken}"; + protected override string RelationalUri => $"/api/m1/file/rename?access_token={_auth.AccessToken}"; protected override byte[] CreateHttpContent() { - var data = $"home={Uri.EscapeDataString(_fullPath)}&email={Auth.Login}&conflict=rename&name={Uri.EscapeDataString(_newName)}"; + var data = $"home={Uri.EscapeDataString(_fullPath)}&email={_auth.Login}&conflict=rename&name={Uri.EscapeDataString(_newName)}"; return Encoding.UTF8.GetBytes(data); } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/ShardInfoRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/ShardInfoRequest.cs index ca7b1554..e2fbc5dc 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/ShardInfoRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/ShardInfoRequest.cs @@ -13,9 +13,9 @@ protected override string RelationalUri { get { - var uri = $"{ConstSettings.CloudDomain}/api/m1/dispatcher?client_id={Settings.ClientId}"; - if (!string.IsNullOrEmpty(Auth.AccessToken)) - uri += $"&access_token={Auth.AccessToken}"; + var uri = $"{ConstSettings.CloudDomain}/api/m1/dispatcher?client_id={_settings.ClientId}"; + if (!string.IsNullOrEmpty(_auth.AccessToken)) + uri += $"&access_token={_auth.AccessToken}"; return uri; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/SharedListRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/SharedListRequest.cs index c9a42bfd..79a90d53 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/SharedListRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/SharedListRequest.cs @@ -15,9 +15,9 @@ protected override string RelationalUri { get { - var uri = $"/api/m1/folder/shared/links?access_token={Auth.AccessToken}"; + var uri = $"/api/m1/folder/shared/links?access_token={_auth.AccessToken}"; return uri; } } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/UnpublishRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/UnpublishRequest.cs index e8e836c8..4450e0ea 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/UnpublishRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/UnpublishRequest.cs @@ -9,22 +9,22 @@ class UnpublishRequest : BaseRequestJson> { private readonly string _publicLink; - public UnpublishRequest(IRequestRepo repo, HttpCommonSettings settings, IAuth auth, string publicLink) + public UnpublishRequest(IRequestRepo repo, HttpCommonSettings settings, IAuth auth, string publicLink) : base(settings, auth) { _publicLink = publicLink; - if (repo.PublicBaseUrlDefault.Length > 0 && + if (repo.PublicBaseUrlDefault.Length > 0 && _publicLink.StartsWith(repo.PublicBaseUrlDefault, StringComparison.InvariantCultureIgnoreCase)) _publicLink = _publicLink.Remove(0, repo.PublicBaseUrlDefault.Length); } - protected override string RelationalUri => $"/api/m1/file/unpublish?access_token={Auth.AccessToken}"; + protected override string RelationalUri => $"/api/m1/file/unpublish?access_token={_auth.AccessToken}"; protected override byte[] CreateHttpContent() { - var data = $"weblink={Uri.EscapeDataString(_publicLink)}&email={Auth.Login}&x-email={Auth.Login}"; + var data = $"weblink={Uri.EscapeDataString(_publicLink)}&email={_auth.Login}&x-email={_auth.Login}"; return Encoding.UTF8.GetBytes(data); } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/WebM1RequestRepo.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/WebM1RequestRepo.cs index 51a355ad..aa538614 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/WebM1RequestRepo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/WebM1RequestRepo.cs @@ -54,7 +54,7 @@ protected WebM1RequestRepo(CloudSettings settings, IWebProxy proxy, // required for Windows 7 breaking connection ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13 | SecurityProtocolType.Tls12; - Authenticator = new OAuth(_connectionLimiter, HttpSettings, credentials, onAuthCodeRequired); + Auth = new OAuth(_connectionLimiter, HttpSettings, credentials, onAuthCodeRequired); CachedSharedList = new Cached>>(_ => { @@ -62,15 +62,15 @@ protected WebM1RequestRepo(CloudSettings settings, IWebProxy proxy, var res = z.Body.List .ToDictionary( - fik => fik.Home, + fik => fik.Home, fiv => Enumerable.Repeat(new PublicLinkInfo(PublicBaseUrlDefault + fiv.Weblink), 1) ); return res; - }, + }, _ => TimeSpan.FromSeconds(30)); } - + public Stream GetDownloadStream(File file, long? start = null, long? end = null) { @@ -84,7 +84,7 @@ private DownloadStream GetDownloadStreamInternal(File afile, long? start = null, Cached downServer = null; var pendingServers = isLinked - ? ShardManager.WeblinkDownloadServersPending + ? ShardManager.WebLinkDownloadServersPending : ShardManager.DownloadServersPending; Stopwatch watch = new Stopwatch(); @@ -98,7 +98,7 @@ CustomDisposable ResponseGenerator(long instart, long inend, Fi string url =(isLinked ? $"{downServer.Value.Url}{WebDavPath.EscapeDataString(file.PublicLinks.Values.FirstOrDefault()?.Uri.PathAndQuery)}" : $"{downServer.Value.Url}{Uri.EscapeDataString(file.FullPath.TrimStart('/'))}") + - $"?client_id={HttpSettings.ClientId}&token={Authenticator.AccessToken}"; + $"?client_id={HttpSettings.ClientId}&token={Auth.AccessToken}"; var uri = new Uri(url); #pragma warning disable SYSLIB0014 // Type or member is obsolete @@ -107,7 +107,7 @@ CustomDisposable ResponseGenerator(long instart, long inend, Fi request.AddRange(instart, inend); request.Proxy = HttpSettings.Proxy; - request.CookieContainer = Authenticator.Cookies; + request.CookieContainer = Auth.Cookies; request.Method = "GET"; request.Accept = "*/*"; request.UserAgent = HttpSettings.UserAgent; @@ -138,7 +138,7 @@ CustomDisposable ResponseGenerator(long instart, long inend, Fi } }; }, - exception => + exception => exception is WebException { Response: HttpWebResponse { StatusCode: HttpStatusCode.NotFound } }, exception => { @@ -192,7 +192,7 @@ public override async Task GetShardInfo(ShardType shardType) var banned = ShardManager.BannedShards.Value; if (banned.All(bsh => bsh.Url != ishard.Url)) { - if (refreshed) Authenticator.ExpireDownloadToken(); + if (refreshed) Auth.ExpireDownloadToken(); return ishard; } ShardManager.CachedShards.Expire(); @@ -208,14 +208,14 @@ public override async Task GetShardInfo(ShardType shardType) public async Task CloneItem(string fromUrl, string toPath) { - var req = await new CloneItemRequest(HttpSettings, Authenticator, fromUrl, toPath).MakeRequestAsync(_connectionLimiter); + var req = await new CloneItemRequest(HttpSettings, Auth, fromUrl, toPath).MakeRequestAsync(_connectionLimiter); var res = req.ToCloneItemResult(); return res; } public async Task Copy(string sourceFullPath, string destinationPath, ConflictResolver? conflictResolver = null) { - var req = await new CopyRequest(HttpSettings, Authenticator, sourceFullPath, destinationPath, conflictResolver).MakeRequestAsync(_connectionLimiter); + var req = await new CopyRequest(HttpSettings, Auth, sourceFullPath, destinationPath, conflictResolver).MakeRequestAsync(_connectionLimiter); var res = req.ToCopyResult(); return res; } @@ -226,7 +226,7 @@ public async Task Move(string sourceFullPath, string destinationPath //var res = req.ToCopyResult(); //return res; - var req = await new MoveRequest(HttpSettings, Authenticator, ShardManager.MetaServer.Url, sourceFullPath, destinationPath) + var req = await new MoveRequest(HttpSettings, Auth, ShardManager.MetaServer.Url, sourceFullPath, destinationPath) .MakeRequestAsync(_connectionLimiter); var res = req.ToCopyResult(WebDavPath.Name(destinationPath)); @@ -245,7 +245,7 @@ public async Task FolderInfo(RemotePath path, int offset = 0, int limit FolderInfoResult datares; try { - datares = await new FolderInfoRequest(HttpSettings, Authenticator, path, offset, limit) + datares = await new FolderInfoRequest(HttpSettings, Auth, path, offset, limit) .MakeRequestAsync(_connectionLimiter); } catch (WebException e) when (e.Response is HttpWebResponse { StatusCode: HttpStatusCode.NotFound }) @@ -279,20 +279,20 @@ public async Task FolderInfo(RemotePath path, int offset = 0, int limit public async Task ItemInfo(RemotePath path, int offset = 0, int limit = int.MaxValue) { - var req = await new ItemInfoRequest(HttpSettings, Authenticator, path, offset, limit).MakeRequestAsync(_connectionLimiter); + var req = await new ItemInfoRequest(HttpSettings, Auth, path, offset, limit).MakeRequestAsync(_connectionLimiter); return req; } public async Task AccountInfo() { - var req = await new AccountInfoRequest(HttpSettings, Authenticator).MakeRequestAsync(_connectionLimiter); + var req = await new AccountInfoRequest(HttpSettings, Auth).MakeRequestAsync(_connectionLimiter); var res = req.ToAccountInfo(); return res; } public async Task Publish(string fullPath) { - var req = await new PublishRequest(HttpSettings, Authenticator, fullPath).MakeRequestAsync(_connectionLimiter); + var req = await new PublishRequest(HttpSettings, Auth, fullPath).MakeRequestAsync(_connectionLimiter); var res = req.ToPublishResult(); if (res.IsSuccess) @@ -311,14 +311,14 @@ public async Task Unpublish(Uri publicLink, string fullPath = n CachedSharedList.Value.Remove(item.Key); } - var req = await new UnpublishRequest(this, HttpSettings, Authenticator, publicLink.OriginalString).MakeRequestAsync(_connectionLimiter); + var req = await new UnpublishRequest(this, HttpSettings, Auth, publicLink.OriginalString).MakeRequestAsync(_connectionLimiter); var res = req.ToUnpublishResult(); return res; } public async Task Remove(string fullPath) { - var req = await new RemoveRequest(HttpSettings, Authenticator, fullPath).MakeRequestAsync(_connectionLimiter); + var req = await new RemoveRequest(HttpSettings, Auth, fullPath).MakeRequestAsync(_connectionLimiter); var res = req.ToRemoveResult(); return res; } @@ -330,7 +330,7 @@ public async Task Rename(string fullPath, string newName) //return res; string newFullPath = WebDavPath.Combine(WebDavPath.Parent(fullPath), newName); - var req = await new MoveRequest(HttpSettings, Authenticator, ShardManager.MetaServer.Url, fullPath, newFullPath) + var req = await new MoveRequest(HttpSettings, Auth, ShardManager.MetaServer.Url, fullPath, newFullPath) .MakeRequestAsync(_connectionLimiter); var res = req.ToRenameResult(); @@ -339,10 +339,10 @@ public async Task Rename(string fullPath, string newName) public Dictionary GetShardInfo1() { - return Authenticator.IsAnonymous + return Auth.IsAnonymous ? new WebV2.Requests - .ShardInfoRequest(HttpSettings, Authenticator).MakeRequestAsync(_connectionLimiter).Result.ToShardInfo() - : new ShardInfoRequest(HttpSettings, Authenticator).MakeRequestAsync(_connectionLimiter).Result.ToShardInfo(); + .ShardInfoRequest(HttpSettings, Auth).MakeRequestAsync(_connectionLimiter).Result.ToShardInfo() + : new ShardInfoRequest(HttpSettings, Auth).MakeRequestAsync(_connectionLimiter).Result.ToShardInfo(); } @@ -350,7 +350,7 @@ public Dictionary GetShardInfo1() private async Task GetShareListInner() { - var res = await new SharedListRequest(HttpSettings, Authenticator) + var res = await new SharedListRequest(HttpSettings, Auth) .MakeRequestAsync(_connectionLimiter); return res; @@ -358,7 +358,7 @@ private async Task GetShareListInner() public IEnumerable GetShareLinks(string path) { - if (!CachedSharedList.Value.TryGetValue(path, out var links)) + if (!CachedSharedList.Value.TryGetValue(path, out var links)) yield break; foreach (var link in links) @@ -375,7 +375,7 @@ public async Task CreateFolder(string path) //return (await new CreateFolderRequest(HttpSettings, Authenticator, path).MakeRequestAsync()) // .ToCreateFolderResult(); - var folderReqest = await new CreateFolderRequest(HttpSettings, Authenticator, ShardManager.MetaServer.Url, path) + var folderReqest = await new CreateFolderRequest(HttpSettings, Auth, ShardManager.MetaServer.Url, path) .MakeRequestAsync(_connectionLimiter); return folderReqest.ToCreateFolderResult(); @@ -386,7 +386,6 @@ public Task AddFile(string fileFullPath, IFileHash fileHash, File throw new NotImplementedException(); } - public async Task ActiveOperationsAsync() => await Task.FromResult(null); + public async Task DetectOutsideChanges() => await Task.FromResult(null); } } - diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/AccountInfoRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/AccountInfoRequest.cs index 061402fa..c4b10403 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/AccountInfoRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/AccountInfoRequest.cs @@ -9,6 +9,6 @@ public AccountInfoRequest(HttpCommonSettings settings, IAuth auth) : base(settin { } - protected override string RelationalUri => $"{ConstSettings.CloudDomain}/api/v2/user?token={Auth.AccessToken}"; + protected override string RelationalUri => $"{ConstSettings.CloudDomain}/api/v2/user?token={_auth.AccessToken}"; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/CloneItemRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/CloneItemRequest.cs index 751c4285..7347288d 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/CloneItemRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/CloneItemRequest.cs @@ -9,7 +9,7 @@ class CloneItemRequest : BaseRequestJson> private readonly string _fromUrl; private readonly string _toPath; - public CloneItemRequest(HttpCommonSettings settings, IAuth auth, string fromUrl, string toPath) + public CloneItemRequest(HttpCommonSettings settings, IAuth auth, string fromUrl, string toPath) : base(settings, auth) { _fromUrl = fromUrl; @@ -20,7 +20,7 @@ protected override string RelationalUri { get { - var uri = $"{ConstSettings.CloudDomain}/api/v2/clone?conflict=rename&folder={Uri.EscapeDataString(_toPath)}&weblink={Uri.EscapeDataString(_fromUrl)}&token={Auth.AccessToken}"; + var uri = $"{ConstSettings.CloudDomain}/api/v2/clone?conflict=rename&folder={Uri.EscapeDataString(_toPath)}&weblink={Uri.EscapeDataString(_fromUrl)}&token={_auth.AccessToken}"; return uri; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/CommonSettings.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/CommonSettings.cs index 14b524fb..fc001f68 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/CommonSettings.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/CommonSettings.cs @@ -4,7 +4,7 @@ internal static class CommonSettings { public const string Domain = "mail.ru"; public const string AuthDomain = "https://auth.mail.ru"; - + public const string DefaultAcceptType = "text / html,application / xhtml + xml,application / xml; q = 0.9,*/*;q=0.8"; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/CopyRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/CopyRequest.cs index 959fff95..d958dfa7 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/CopyRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/CopyRequest.cs @@ -12,14 +12,14 @@ class CopyRequest : BaseRequestJson> private readonly ConflictResolver _conflictResolver; /// - /// + /// /// /// /// /// (without item name) /// /// - public CopyRequest(HttpCommonSettings settings, IAuth auth, string sourceFullPath, string destinationPath, ConflictResolver? conflictResolver = null) + public CopyRequest(HttpCommonSettings settings, IAuth auth, string sourceFullPath, string destinationPath, ConflictResolver? conflictResolver = null) : base(settings, auth) { _sourceFullPath = sourceFullPath; @@ -32,11 +32,11 @@ public CopyRequest(HttpCommonSettings settings, IAuth auth, string sourceFullPat protected override byte[] CreateHttpContent() { var data = Encoding.UTF8.GetBytes(string.Format("home={0}&api={1}&token={2}&email={3}&x-email={3}&conflict={4}&folder={5}", - Uri.EscapeDataString(_sourceFullPath), 2, Auth.AccessToken, Auth.Login, + Uri.EscapeDataString(_sourceFullPath), 2, _auth.AccessToken, _auth.Login, _conflictResolver, Uri.EscapeDataString(_destinationPath))); return data; } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/CreateFileRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/CreateFileRequest.cs index 3f84aea3..09bf4d4d 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/CreateFileRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/CreateFileRequest.cs @@ -12,7 +12,7 @@ class CreateFileRequest : BaseRequestJson> private readonly long _size; private readonly ConflictResolver _conflictResolver; - public CreateFileRequest(HttpCommonSettings settings, IAuth auth, string fullPath, string hash, long size, ConflictResolver? conflictResolver) + public CreateFileRequest(HttpCommonSettings settings, IAuth auth, string fullPath, string hash, long size, ConflictResolver? conflictResolver) : base(settings, auth) { _fullPath = fullPath; @@ -26,7 +26,7 @@ public CreateFileRequest(HttpCommonSettings settings, IAuth auth, string fullPat protected override byte[] CreateHttpContent() { string filePart = $"&hash={_hash}&size={_size}"; - string data = $"home={Uri.EscapeDataString(_fullPath)}&conflict={_conflictResolver}&api=2&token={Auth.AccessToken}" + filePart; + string data = $"home={Uri.EscapeDataString(_fullPath)}&conflict={_conflictResolver}&api=2&token={_auth.AccessToken}" + filePart; return Encoding.UTF8.GetBytes(data); } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/CreateFolderRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/CreateFolderRequest.cs index 8890922f..cdce99a3 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/CreateFolderRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/CreateFolderRequest.cs @@ -18,7 +18,7 @@ public CreateFolderRequest(HttpCommonSettings settings, IAuth auth, string fullP protected override byte[] CreateHttpContent() { - var data = $"home={Uri.EscapeDataString(_fullPath)}&conflict=rename&api={2}&token={Auth.AccessToken}"; + var data = $"home={Uri.EscapeDataString(_fullPath)}&conflict=rename&api={2}&token={_auth.AccessToken}"; return Encoding.UTF8.GetBytes(data); } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/DownloadRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/DownloadRequest.cs index a7ecc709..767e2d09 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/DownloadRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/DownloadRequest.cs @@ -11,20 +11,20 @@ namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2.Requests { class DownloadRequest { - public DownloadRequest(File file, long instart, long inend, IAuth authenticator, HttpCommonSettings settings, Cached> shards) + public DownloadRequest(File file, long inStart, long inEnd, IAuth auth, HttpCommonSettings settings, Cached> shards) { - Request = CreateRequest(authenticator, settings.Proxy, file, instart, inend, settings.UserAgent, shards); + Request = CreateRequest(auth, settings.Proxy, file, inStart, inEnd, settings.UserAgent, shards); } public HttpWebRequest Request { get; } - private static HttpWebRequest CreateRequest(IAuth authenticator, IWebProxy proxy, File file, long instart, long inend, string userAgent, Cached> shards) + private static HttpWebRequest CreateRequest(IAuth auth, IWebProxy proxy, File file, long instart, long inend, string userAgent, Cached> shards) { bool isLinked = !file.PublicLinks.IsEmpty; string downloadkey = isLinked - ? authenticator.DownloadToken - : authenticator.AccessToken; + ? auth.DownloadToken + : auth.AccessToken; var shard = isLinked ? shards.Value[ShardType.WeblinkGet] @@ -41,7 +41,7 @@ private static HttpWebRequest CreateRequest(IAuth authenticator, IWebProxy proxy request.Headers.Add("Accept-Ranges", "bytes"); request.AddRange(instart, inend); request.Proxy = proxy; - request.CookieContainer = authenticator.Cookies; + request.CookieContainer = auth.Cookies; request.Method = "GET"; request.ContentType = MediaTypeNames.Application.Octet; request.Accept = "*/*"; @@ -56,4 +56,4 @@ public static implicit operator HttpWebRequest(DownloadRequest v) return v.Request; } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/DownloadTokenRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/DownloadTokenRequest.cs index d9bd2167..1d813a1d 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/DownloadTokenRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/DownloadTokenRequest.cs @@ -13,7 +13,7 @@ protected override string RelationalUri { get { - var uri = $"/api/v2/tokens/download?token={Auth.AccessToken}"; + var uri = $"/api/v2/tokens/download?token={_auth.AccessToken}"; return uri; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/EnsureSdcCookieRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/EnsureSdcCookieRequest.cs index 7f8dc4ca..7b7f7115 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/EnsureSdcCookieRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/EnsureSdcCookieRequest.cs @@ -5,7 +5,7 @@ namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2.Requests { class EnsureSdcCookieRequest : BaseRequestString { - public EnsureSdcCookieRequest(HttpCommonSettings settings, IAuth auth) + public EnsureSdcCookieRequest(HttpCommonSettings settings, IAuth auth) : base(settings, auth) { } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/FolderInfoRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/FolderInfoRequest.cs index 31d8b3b9..5bc86a24 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/FolderInfoRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/FolderInfoRequest.cs @@ -11,7 +11,7 @@ internal class FolderInfoRequest : BaseRequestJson private readonly int _offset; private readonly int _limit; - public FolderInfoRequest(HttpCommonSettings settings, IAuth auth, RemotePath path, int offset = 0, int limit = int.MaxValue) + public FolderInfoRequest(HttpCommonSettings settings, IAuth auth, RemotePath path, int offset = 0, int limit = int.MaxValue) : base(settings, auth) { _isWebLink = path.IsLink; @@ -23,7 +23,7 @@ public FolderInfoRequest(HttpCommonSettings settings, IAuth auth, RemotePath pat } else _path = path.Path; - + _offset = offset; _limit = limit; } @@ -36,8 +36,8 @@ protected override string RelationalUri ? $"/api/v2/folder?weblink={Uri.EscapeDataString(_path)}&offset={_offset}&limit={_limit}" : $"/api/v2/folder?home={Uri.EscapeDataString(_path)}&offset={_offset}&limit={_limit}"; - if (!Auth.IsAnonymous) - uri += $"&token={Auth.AccessToken}"; + if (!_auth.IsAnonymous) + uri += $"&token={_auth.AccessToken}"; return uri; } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/ItemInfoRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/ItemInfoRequest.cs index 46475c59..3daa253e 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/ItemInfoRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/ItemInfoRequest.cs @@ -12,7 +12,7 @@ class ItemInfoRequest : BaseRequestJson private readonly int _limit; - public ItemInfoRequest(HttpCommonSettings settings, IAuth auth, RemotePath path, int offset = 0, int limit = int.MaxValue) + public ItemInfoRequest(HttpCommonSettings settings, IAuth auth, RemotePath path, int offset = 0, int limit = int.MaxValue) : base(settings, auth) { _isWebLink = path.IsLink; @@ -34,10 +34,10 @@ protected override string RelationalUri get { var uri = _isWebLink - ? $"/api/v2/file?token={Auth.AccessToken}&weblink={Uri.EscapeDataString(_path)}&offset={_offset}&limit={_limit}" - : $"/api/v2/file?token={Auth.AccessToken}&home={Uri.EscapeDataString(_path)}&offset={_offset}&limit={_limit}"; + ? $"/api/v2/file?token={_auth.AccessToken}&weblink={Uri.EscapeDataString(_path)}&offset={_offset}&limit={_limit}" + : $"/api/v2/file?token={_auth.AccessToken}&home={Uri.EscapeDataString(_path)}&offset={_offset}&limit={_limit}"; return uri; } } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/LoginRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/LoginRequest.cs index 4872d670..ae7d7dee 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/LoginRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/LoginRequest.cs @@ -10,7 +10,7 @@ namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2.Requests { class LoginRequest : BaseRequestString { - public LoginRequest(HttpCommonSettings settings, IAuth auth) + public LoginRequest(HttpCommonSettings settings, IAuth auth) : base(settings, auth) { } @@ -27,7 +27,7 @@ protected override HttpWebRequest CreateRequest(string baseDomain = null) protected override byte[] CreateHttpContent() { #pragma warning disable SYSLIB0013 // Type or member is obsolete - string data = $"Login={Uri.EscapeUriString(Auth.Login)}&Domain={CommonSettings.Domain}&Password={Uri.EscapeUriString(Auth.Password)}"; + string data = $"Login={Uri.EscapeUriString(_auth.Login)}&Domain={CommonSettings.Domain}&Password={Uri.EscapeUriString(_auth.Password)}"; #pragma warning restore SYSLIB0013 // Type or member is obsolete return Encoding.UTF8.GetBytes(data); diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/MoveRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/MoveRequest.cs index 2cac57cb..0c2d900b 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/MoveRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/MoveRequest.cs @@ -10,7 +10,7 @@ class MoveRequest : BaseRequestJson> private readonly string _sourceFullPath; private readonly string _destinationPath; - public MoveRequest(HttpCommonSettings settings, IAuth auth, string sourceFullPath, string destinationPath) + public MoveRequest(HttpCommonSettings settings, IAuth auth, string sourceFullPath, string destinationPath) : base(settings, auth) { _sourceFullPath = sourceFullPath; @@ -22,7 +22,7 @@ public MoveRequest(HttpCommonSettings settings, IAuth auth, string sourceFullPat protected override byte[] CreateHttpContent() { var data = Encoding.UTF8.GetBytes(string.Format("home={0}&api={1}&token={2}&email={3}&x-email={3}&conflict=rename&folder={4}", - Uri.EscapeDataString(_sourceFullPath), 2, Auth.AccessToken, Auth.Login, Uri.EscapeDataString(_destinationPath))); + Uri.EscapeDataString(_sourceFullPath), 2, _auth.AccessToken, _auth.Login, Uri.EscapeDataString(_destinationPath))); return data; } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/PublishRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/PublishRequest.cs index fcf84006..1eff18a6 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/PublishRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/PublishRequest.cs @@ -19,8 +19,8 @@ public PublishRequest(HttpCommonSettings settings, IAuth auth, string fullPath) protected override byte[] CreateHttpContent() { var data = string.Format("home={0}&api={1}&token={2}&email={3}&x-email={3}", Uri.EscapeDataString(_fullPath), - 2, Auth.AccessToken, Auth.Login); + 2, _auth.AccessToken, _auth.Login); return Encoding.UTF8.GetBytes(data); } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/RemoveRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/RemoveRequest.cs index 561206c6..735835bb 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/RemoveRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/RemoveRequest.cs @@ -20,7 +20,7 @@ public RemoveRequest(HttpCommonSettings settings, IAuth auth, string fullPath) : protected override byte[] CreateHttpContent() { var data = string.Format("home={0}&api={1}&token={2}&email={3}&x-email={3}", Uri.EscapeDataString(_fullPath), - 2, Auth.AccessToken, Auth.Login); + 2, _auth.AccessToken, _auth.Login); return Encoding.UTF8.GetBytes(data); } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/RenameRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/RenameRequest.cs index a55a8322..f568a04f 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/RenameRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/RenameRequest.cs @@ -5,7 +5,7 @@ namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2.Requests { - + class RenameRequest : BaseRequestJson> { private readonly string _fullPath; @@ -22,7 +22,7 @@ public RenameRequest(HttpCommonSettings settings, IAuth auth, string fullPath, s protected override byte[] CreateHttpContent() { var data = string.Format("home={0}&api={1}&token={2}&email={3}&x-email={3}&conflict=rename&name={4}", Uri.EscapeDataString(_fullPath), - 2, Auth.AccessToken, Auth.Login, Uri.EscapeDataString(_newName)); + 2, _auth.AccessToken, _auth.Login, Uri.EscapeDataString(_newName)); return Encoding.UTF8.GetBytes(data); } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/SecondStepAuthRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/SecondStepAuthRequest.cs index f616e631..8337a9c9 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/SecondStepAuthRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/SecondStepAuthRequest.cs @@ -20,12 +20,10 @@ public SecondStepAuthRequest(HttpCommonSettings settings, string csrf, string au protected override byte[] CreateHttpContent() { #pragma warning disable SYSLIB0013 // Type or member is obsolete - string data = $"csrf={_csrf}&Login={Uri.EscapeUriString(Auth.Login)}&AuthCode={_authCode}"; + string data = $"csrf={_csrf}&Login={Uri.EscapeUriString(_auth.Login)}&AuthCode={_authCode}"; #pragma warning restore SYSLIB0013 // Type or member is obsolete return Encoding.UTF8.GetBytes(data); } } } - - diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/ShardInfoRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/ShardInfoRequest.cs index b64b811c..43d490ba 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/ShardInfoRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/ShardInfoRequest.cs @@ -5,7 +5,7 @@ namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2.Requests { class ShardInfoRequest : BaseRequestJson { - public ShardInfoRequest(HttpCommonSettings settings, IAuth auth) + public ShardInfoRequest(HttpCommonSettings settings, IAuth auth) : base(settings, auth) { } @@ -14,9 +14,9 @@ protected override string RelationalUri { get { - var uri = $"{ConstSettings.CloudDomain}/api/v2/dispatcher?client_id={Settings.ClientId}"; - if (!Auth.IsAnonymous) - uri += $"&access_token={Auth.AccessToken}"; + var uri = $"{ConstSettings.CloudDomain}/api/v2/dispatcher?client_id={_settings.ClientId}"; + if (!_auth.IsAnonymous) + uri += $"&access_token={_auth.AccessToken}"; else { uri += "&email=anonym&x-email=anonym"; diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/UnpublishRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/UnpublishRequest.cs index 3ae1bcba..aaf20d61 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/UnpublishRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/UnpublishRequest.cs @@ -19,8 +19,8 @@ public UnpublishRequest(HttpCommonSettings settings, IAuth auth, string publicLi protected override byte[] CreateHttpContent() { var data = string.Format("weblink={0}&api={1}&token={2}&email={3}&x-email={3}", Uri.EscapeDataString(_publicLink), - 2, Auth.AccessToken, Auth.Login); + 2, _auth.AccessToken, _auth.Login); return Encoding.UTF8.GetBytes(data); } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/UploadRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/UploadRequest.cs index b3d9c509..de0c2dfb 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/UploadRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/UploadRequest.cs @@ -6,22 +6,22 @@ namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2.Requests { class UploadRequest { - public UploadRequest(string shardUrl, File file, IAuth authenticator, HttpCommonSettings settings) + public UploadRequest(string shardUrl, File file, IAuth auth, HttpCommonSettings settings) { - Request = CreateRequest(shardUrl, authenticator, settings.Proxy, file, settings.UserAgent); + Request = CreateRequest(shardUrl, auth, settings.Proxy, file, settings.UserAgent); } public HttpWebRequest Request { get; } - private static HttpWebRequest CreateRequest(string shardUrl, IAuth authenticator, IWebProxy proxy, File file, string userAgent) + private static HttpWebRequest CreateRequest(string shardUrl, IAuth auth, IWebProxy proxy, File file, string userAgent) { - var url = new Uri($"{shardUrl}?cloud_domain=2&{authenticator.Login}"); + var url = new Uri($"{shardUrl}?cloud_domain=2&{auth.Login}"); #pragma warning disable SYSLIB0014 // Type or member is obsolete var request = (HttpWebRequest)WebRequest.Create(url.OriginalString); #pragma warning restore SYSLIB0014 // Type or member is obsolete request.Proxy = proxy; - request.CookieContainer = authenticator.Cookies; + request.CookieContainer = auth.Cookies; request.Method = "PUT"; request.ContentLength = file.OriginalSize; // + boundary.Start.LongLength + boundary.End.LongLength; request.Referer = $"{ConstSettings.CloudDomain}/home/{Uri.EscapeDataString(file.Path)}"; @@ -39,4 +39,4 @@ public static implicit operator HttpWebRequest(UploadRequest v) return v.Request; } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/WebAuth.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/WebAuth.cs index b8f28f0e..b200c435 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/WebAuth.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/WebAuth.cs @@ -37,7 +37,7 @@ public WebAuth(SemaphoreSlim connectionLimiter, HttpCommonSettings settings, _authToken = new Cached(_ => { Logger.Debug("AuthToken expired, refreshing."); - if (credentials.IsAnonymous) + if (credentials.IsAnonymous) return null; var token = Auth(connectionLimiter).Result; @@ -103,4 +103,4 @@ public void ExpireDownloadToken() _cachedDownloadToken.Expire(); } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/WebV2RequestRepo.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/WebV2RequestRepo.cs index 725bcc44..596382e5 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/WebV2RequestRepo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/WebV2RequestRepo.cs @@ -33,11 +33,13 @@ public WebV2RequestRepo(CloudSettings settings, IBasicCredentials credentials, A HttpSettings.CloudSettings = settings; HttpSettings.Proxy = settings.Proxy; + Auth = new WebAuth(_connectionLimiter, HttpSettings, credentials, onAuthCodeRequired); + _bannedShards = new Cached>(_ => new List(), _ => TimeSpan.FromMinutes(2)); _cachedShards = new Cached>( - _ => new ShardInfoRequest(HttpSettings, Authenticator).MakeRequestAsync(_connectionLimiter).Result.ToShardInfo(), + _ => new ShardInfoRequest(HttpSettings, Auth).MakeRequestAsync(_connectionLimiter).Result.ToShardInfo(), _ => TimeSpan.FromSeconds(ShardsExpiresInSec)); ServicePointManager.DefaultConnectionLimit = int.MaxValue; @@ -45,7 +47,6 @@ public WebV2RequestRepo(CloudSettings settings, IBasicCredentials credentials, A // required for Windows 7 breaking connection ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13 | SecurityProtocolType.Tls12; - Authenticator = new WebAuth(_connectionLimiter, HttpSettings, credentials, onAuthCodeRequired); } @@ -72,7 +73,7 @@ public Stream GetDownloadStream(File file, long? start = null, long? end = null) CustomDisposable ResponseGenerator(long instart, long inend, File file) { - HttpWebRequest request = new DownloadRequest(file, instart, inend, Authenticator, HttpSettings, _cachedShards); + HttpWebRequest request = new DownloadRequest(file, instart, inend, Auth, HttpSettings, _cachedShards); var response = (HttpWebResponse)request.GetResponse(); return new CustomDisposable @@ -145,7 +146,7 @@ public override async Task GetShardInfo(ShardType shardType) var banned = _bannedShards.Value; if (banned.All(bsh => bsh.Url != ishard.Url)) { - if (refreshed) Authenticator.ExpireDownloadToken(); + if (refreshed) Auth.ExpireDownloadToken(); return ishard; } _cachedShards.Expire(); @@ -161,27 +162,27 @@ public override async Task GetShardInfo(ShardType shardType) public async Task CloneItem(string fromUrl, string toPath) { - var req = await new CloneItemRequest(HttpSettings, Authenticator, fromUrl, toPath).MakeRequestAsync(_connectionLimiter); + var req = await new CloneItemRequest(HttpSettings, Auth, fromUrl, toPath).MakeRequestAsync(_connectionLimiter); var res = req.ToCloneItemResult(); return res; } public async Task Copy(string sourceFullPath, string destinationPath, ConflictResolver? conflictResolver = null) { - var req = await new CopyRequest(HttpSettings, Authenticator, sourceFullPath, destinationPath, conflictResolver).MakeRequestAsync(_connectionLimiter); + var req = await new CopyRequest(HttpSettings, Auth, sourceFullPath, destinationPath, conflictResolver).MakeRequestAsync(_connectionLimiter); var res = req.ToCopyResult(); return res; } public async Task Move(string sourceFullPath, string destinationPath, ConflictResolver? conflictResolver = null) { - var req = await new MoveRequest(HttpSettings, Authenticator, sourceFullPath, destinationPath).MakeRequestAsync(_connectionLimiter); + var req = await new MoveRequest(HttpSettings, Auth, sourceFullPath, destinationPath).MakeRequestAsync(_connectionLimiter); var res = req.ToCopyResult(); return res; } /// - /// + /// /// /// /// @@ -194,7 +195,7 @@ public async Task FolderInfo(RemotePath path, int offset = 0, int limit FolderInfoResult dataRes; try { - dataRes = await new FolderInfoRequest(HttpSettings, Authenticator, path, offset, limit) + dataRes = await new FolderInfoRequest(HttpSettings, Auth, path, offset, limit) .MakeRequestAsync(_connectionLimiter); } catch (WebException e) when (e.Response is HttpWebResponse { StatusCode: HttpStatusCode.NotFound }) @@ -227,7 +228,7 @@ public async Task FolderInfo(RemotePath path, int offset = 0, int limit public async Task ItemInfo(RemotePath path, int offset = 0, int limit = int.MaxValue) { - var req = await new ItemInfoRequest(HttpSettings, Authenticator, path, offset, limit) + var req = await new ItemInfoRequest(HttpSettings, Auth, path, offset, limit) .MakeRequestAsync(_connectionLimiter); return req; } @@ -235,42 +236,42 @@ public async Task ItemInfo(RemotePath path, int offset = 0, in public async Task AccountInfo() { - var req = await new AccountInfoRequest(HttpSettings, Authenticator).MakeRequestAsync(_connectionLimiter); + var req = await new AccountInfoRequest(HttpSettings, Auth).MakeRequestAsync(_connectionLimiter); var res = req.ToAccountInfo(); return res; } public async Task Publish(string fullPath) { - var req = await new PublishRequest(HttpSettings, Authenticator, fullPath).MakeRequestAsync(_connectionLimiter); + var req = await new PublishRequest(HttpSettings, Auth, fullPath).MakeRequestAsync(_connectionLimiter); var res = req.ToPublishResult(); return res; } public async Task Unpublish(Uri publicLink, string fullPath = null) { - var req = await new UnpublishRequest(HttpSettings, Authenticator, publicLink.OriginalString).MakeRequestAsync(_connectionLimiter); + var req = await new UnpublishRequest(HttpSettings, Auth, publicLink.OriginalString).MakeRequestAsync(_connectionLimiter); var res = req.ToUnpublishResult(); return res; } public async Task Remove(string fullPath) { - var req = await new RemoveRequest(HttpSettings, Authenticator, fullPath).MakeRequestAsync(_connectionLimiter); + var req = await new RemoveRequest(HttpSettings, Auth, fullPath).MakeRequestAsync(_connectionLimiter); var res = req.ToRemoveResult(); return res; } public async Task Rename(string fullPath, string newName) { - var req = await new RenameRequest(HttpSettings, Authenticator, fullPath, newName).MakeRequestAsync(_connectionLimiter); + var req = await new RenameRequest(HttpSettings, Auth, fullPath, newName).MakeRequestAsync(_connectionLimiter); var res = req.ToRenameResult(); return res; } public Dictionary GetShardInfo1() { - return new ShardInfoRequest(HttpSettings, Authenticator).MakeRequestAsync(_connectionLimiter).Result.ToShardInfo(); + return new ShardInfoRequest(HttpSettings, Auth).MakeRequestAsync(_connectionLimiter).Result.ToShardInfo(); } public IEnumerable GetShareLinks(string fullPath) @@ -285,7 +286,7 @@ public void CleanTrash() public async Task CreateFolder(string path) { - return (await new CreateFolderRequest(HttpSettings, Authenticator, path).MakeRequestAsync(_connectionLimiter)) + return (await new CreateFolderRequest(HttpSettings, Auth, path).MakeRequestAsync(_connectionLimiter)) .ToCreateFolderResult(); } @@ -293,12 +294,12 @@ public async Task AddFile(string fileFullPath, IFileHash fileHash { var hash = fileHash.Hash.Value; - var res = await new CreateFileRequest(HttpSettings, Authenticator, fileFullPath, hash, fileSize, conflictResolver) + var res = await new CreateFileRequest(HttpSettings, Auth, fileFullPath, hash, fileSize, conflictResolver) .MakeRequestAsync(_connectionLimiter); return res.ToAddFileResult(); } - public async Task ActiveOperationsAsync() => await Task.FromResult(null); + public async Task DetectOutsideChanges() => await Task.FromResult(null); } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/RemotePath.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/RemotePath.cs index 36634db9..edf164a1 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/RemotePath.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/RemotePath.cs @@ -15,7 +15,7 @@ private RemotePath() public static async Task Get(string path, LinkManager lm) { var z = new RemotePath {Path = path}; - if (lm == null) + if (lm == null) return z; z.Link = await lm.GetItemLink(path); @@ -27,4 +27,4 @@ public static async Task Get(string path, LinkManager lm) public bool IsLink => Link != null; } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/RepoFabric.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/RepoFabric.cs index b7f06fe3..6a60e270 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/RepoFabric.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/RepoFabric.cs @@ -29,8 +29,9 @@ string TwoFaHandler(string login, bool isAutoRelogin) IRequestRepo repo = _credentials.Protocol switch { - Protocol.YadWebV2 => new YandexDisk.YadWebV2.YadWebRequestRepo(_settings, _settings.Proxy, _credentials), - Protocol.YadWeb => new YandexDisk.YadWeb.YadWebRequestRepo(_settings, _settings.Proxy, _credentials), + Protocol.YadWeb => _credentials.AuthenticationUsingBrowser + ? new YandexDisk.YadWeb.YadWebRequestRepo2(_settings, _settings.Proxy, _credentials) + : new YandexDisk.YadWeb.YadWebRequestRepo(_settings, _settings.Proxy, _credentials), Protocol.WebM1Bin => new WebBinRequestRepo(_settings, _credentials, TwoFaHandler), Protocol.WebV2 => new WebV2RequestRepo(_settings, _credentials, TwoFaHandler), _ => throw new Exception("Unknown protocol") diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/DtoImportYadWeb.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/DtoImportYadWeb.cs index 3999aaed..c06e705c 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/DtoImportYadWeb.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/DtoImportYadWeb.cs @@ -1,10 +1,9 @@ using System; using System.Collections.Generic; -using System.Collections.Immutable; using System.Linq; +using System.Security.Authentication; using YaR.Clouds.Base.Repos.YandexDisk.YadWeb.Models; using YaR.Clouds.Base.Requests.Types; -using YaR.Clouds.Common; namespace YaR.Clouds.Base.Repos.YandexDisk.YadWeb { @@ -14,11 +13,17 @@ static class DtoImportYadWeb public static AccountInfoResult ToAccountInfo(this YadResponseModel data) { + if (data?.Data == null || data.Error != null) + { + throw new AuthenticationException(string.Concat( + "The cloud server rejected the credentials provided: ", data?.Error?.Message)); + } + var info = data.Data; var res = new AccountInfoResult { - - FileSizeLimit = info.FilesizeLimit, + + FileSizeLimit = info.FileSizeLimit, DiskUsage = new DiskUsage { @@ -31,12 +36,35 @@ public static AccountInfoResult ToAccountInfo(this YadResponseModel activeOps) { if (folderData is null) throw new ArgumentNullException(nameof(folderData)); + List paths = new List(); + if (activeOps is not null) + { + foreach (var op in activeOps) + { + string p = GetOpPath(op.Data.Target); + if (!string.IsNullOrEmpty(p)) + paths.Add(p); + p = GetOpPath(op.Data.Source); + if (!string.IsNullOrEmpty(p)) + paths.Add(p); + } + } + if (path.StartsWith("/disk")) path = path.Remove(0, "/disk".Length); @@ -46,6 +74,11 @@ public static Folder ToFolder(this YadFolderInfoRequestData folderData, PublicLinkInfo item = new PublicLinkInfo("short", entryData.Meta.UrlShort); folder.PublicLinks.TryAdd(item.Uri.AbsoluteUri, item); } + if (paths.Any(x => WebDavPath.IsParentOrSame(x, folder.FullPath))) + { + // Признак операции на сервере, под которую подпадает папка + folder.Attributes |= System.IO.FileAttributes.Offline; + } string diskPath = WebDavPath.Combine("/disk", path); var fi = folderData.Resources; @@ -64,6 +97,17 @@ public static Folder ToFolder(this YadFolderInfoRequestData folderData, .Select(f => f.ToFolder())); folder.Descendants = folder.Descendants.AddRange(children); + folder.Descendants.ForEach(entry => + { + if (paths.Any(x => WebDavPath.IsParentOrSame(x, entry.FullPath))) + { + // Признак операции на сервере, под которую подпадает папка + folder.Attributes |= System.IO.FileAttributes.Offline; + } + }); + + folder.ServerFilesCount ??= folder.Descendants.Count(f => f.IsFile); + folder.ServerFoldersCount ??= folder.Descendants.Count(f => !f.IsFile); return folder; } @@ -88,14 +132,14 @@ public static File ToFile(this FolderInfoDataResource data, string publicBaseUrl public static File ToFile(this YadItemInfoRequestData data, string publicBaseUrl) { - var path = data.Path.Remove(0, 5); // remove "/disk" + var path = data.Path.Remove(0, "/disk".Length); var res = new File(path, data.Meta.Size) { CreationTimeUtc = UnixTimeStampToDateTime(data.Ctime, DateTime.MinValue), LastAccessTimeUtc = UnixTimeStampToDateTime(data.Utime, DateTime.MinValue), LastWriteTimeUtc = UnixTimeStampToDateTime(data.Mtime, DateTime.MinValue) - //PublicLink = data.Meta.UrlShort.StartsWith(publicBaseUrl) + //PublicLink = data.Meta.UrlShort.StartsWith(publicBaseUrl) // ? data.Meta.UrlShort.Remove(0, publicBaseUrl.Length) // : data.Meta.UrlShort }; @@ -121,7 +165,7 @@ public static RenameResult ToRenameResult(this YadResponseModel> ToKvp(int index) @@ -55,7 +17,6 @@ public override IEnumerable> ToKvp(int index) } } - internal class YadActiveOperationsData : YadModelDataBase { [JsonProperty("ycrid")] @@ -74,7 +35,7 @@ internal class YadActiveOperationsData : YadModelDataBase /// Это идентификатор для передачи параметром в метод /// [JsonProperty("id")] - public string Oid { get; set; } + public string OpId { get; set; } /// /// ID пользователя, запустившего операцию @@ -193,6 +154,6 @@ internal struct YadOperation /// /// Идентификатор операции, который можно передавать параметром в метод . /// - public string Oid { get; set; } + public string OpId { get; set; } } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/CleanTrash.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/CleanTrash.cs index 1fcce16f..bb84a9cb 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/CleanTrash.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/CleanTrash.cs @@ -16,7 +16,7 @@ internal class YadCleanTrashData : YadModelDataBase public long AtVersion { get; set; } [JsonProperty("oid")] - public string Oid { get; set; } + public string OpId { get; set; } [JsonProperty("type")] public string Type { get; set; } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/Copy.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/Copy.cs index 8621f86e..0b5ef3a5 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/Copy.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/Copy.cs @@ -21,7 +21,7 @@ public override IEnumerable> ToKvp(int index) { foreach (var pair in base.ToKvp(index)) yield return pair; - + yield return new KeyValuePair($"src.{index}", WebDavPath.Combine("/disk", Source)); yield return new KeyValuePair($"dst.{index}", WebDavPath.Combine("/disk", Destination)); yield return new KeyValuePair($"force.{index}", Force ? "1" : "0"); @@ -34,7 +34,7 @@ class YadCopyRequestData : YadModelDataBase public long AtVersion { get; set; } [JsonProperty("oid")] - public string Oid { get; set; } + public string OpId { get; set; } [JsonProperty("type")] public string Type { get; set; } @@ -51,4 +51,4 @@ class YadCopyRequestParams [JsonProperty("force")] public long Force { get; set; } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/CreateFolder.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/CreateFolder.cs index c955e8b0..806bd787 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/CreateFolder.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/CreateFolder.cs @@ -19,7 +19,7 @@ public override IEnumerable> ToKvp(int index) { foreach (var pair in base.ToKvp(index)) yield return pair; - + yield return new KeyValuePair($"id.{index}", WebDavPath.Combine("/disk", Path)); yield return new KeyValuePair($"force.{index}", Force ? "1" : "0"); } @@ -37,4 +37,4 @@ class YadCreateFolderRequestParams [JsonProperty("force")] public long Force { get; set; } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/Delete.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/Delete.cs index 4e1a4365..9ea82cbe 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/Delete.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/Delete.cs @@ -17,7 +17,7 @@ public override IEnumerable> ToKvp(int index) { foreach (var pair in base.ToKvp(index)) yield return pair; - + yield return new KeyValuePair($"id.{index}", WebDavPath.Combine("/disk", Path)); } } @@ -28,7 +28,7 @@ public class YadDeleteRequestData : YadModelDataBase public long AtVersion { get; set; } [JsonProperty("oid")] - public string Oid { get; set; } + public string OpId { get; set; } [JsonProperty("type")] public string Type { get; set; } @@ -39,4 +39,4 @@ public class YadDeleteRequestParams [JsonProperty("id")] public string Id { get; set; } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/FolderInfo.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/FolderInfo.cs index 8db29b84..41fa18b4 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/FolderInfo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/FolderInfo.cs @@ -15,18 +15,30 @@ public YadFolderInfoPostModel(string path, string pathPrefix = "/disk") } public string Path { get; set; } + /// + /// 0 - сортировка возвращаемого результата по убыванию. + /// 1 - сортировка возвращаемого результата по возрастанию. + /// public int Order { get; set; } = 1; + /// + /// Поле сортировки возвращаемого результата. + /// name - по названию файла. + /// mtime - по времени изменения. + /// size - по размеру. + /// type - по типу. + /// public string SortBy { get; set; } = "name"; public int Offset { get; set; } = 0; public int Amount { get; set; } = int.MaxValue; + public bool WithParent { get; set; } = false; public override IEnumerable> ToKvp(int index) { foreach (var pair in base.ToKvp(index)) yield return pair; - + yield return new KeyValuePair($"idContext.{index}", WebDavPath.Combine(_pathPrefix, Path)); - + //if (Path == "/Camera") //{ // yield return new KeyValuePair($"idContext.{index}", "/photounlim/"); @@ -40,6 +52,8 @@ public override IEnumerable> ToKvp(int index) yield return new KeyValuePair($"sort.{index}", SortBy); yield return new KeyValuePair($"offset.{index}", Offset.ToString()); yield return new KeyValuePair($"amount.{index}", Amount.ToString()); + if (WithParent) + yield return new KeyValuePair($"withParent.{index}", "1"); } } @@ -91,13 +105,13 @@ class Meta public string Mimetype { get; set; } [JsonProperty("drweb", NullValueHandling = NullValueHandling.Ignore)] - public long? Drweb { get; set; } + public long? DrWeb { get; set; } [JsonProperty("sizes", NullValueHandling = NullValueHandling.Ignore)] public List Sizes { get; set; } [JsonProperty("mediatype", NullValueHandling = NullValueHandling.Ignore)] - public string Mediatype { get; set; } + public string MediaType { get; set; } [JsonProperty("etime", NullValueHandling = NullValueHandling.Ignore)] public long? Etime { get; set; } @@ -113,6 +127,9 @@ class Meta [JsonProperty("short_url")] public string UrlShort { get; set; } + + [JsonProperty("total_results_count")] + public int? TotalEntityCount { get; set; } } class Size @@ -213,4 +230,4 @@ internal class YadFolderInfoRequestParams [JsonProperty("amount")] public long Amount { get; set; } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/GetResourceUploadUrl.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/GetResourceUploadUrl.cs index c1dd1afd..ad04fef5 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/GetResourceUploadUrl.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/GetResourceUploadUrl.cs @@ -25,7 +25,7 @@ public override IEnumerable> ToKvp(int index) { foreach (var pair in base.ToKvp(index)) yield return pair; - + yield return new KeyValuePair($"dst.{index}", WebDavPath.Combine("/disk", Destination)); yield return new KeyValuePair($"force.{index}", Force ? "1" : "0"); yield return new KeyValuePair($"size.{index}", Size.ToString()); @@ -47,7 +47,7 @@ internal class ResourceUploadUrlData : YadModelDataBase public string Type { get; set; } [JsonProperty("oid")] - public string Oid { get; set; } + public string OpId { get; set; } /// /// or file exists in cloud, generally = "hardlinked" @@ -73,4 +73,4 @@ internal class ResourceUploadUrlParams [JsonProperty("sha256")] public string Sha256 { get; set; } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/GetResourceUrl.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/GetResourceUrl.cs index a484b25f..834867f7 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/GetResourceUrl.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/GetResourceUrl.cs @@ -35,7 +35,9 @@ internal class ResourceUrlData : YadModelDataBase internal class ResourceUrlParams { - [JsonProperty("id")] + //[JsonProperty("id")] + // 08.07.2023 в браузере при скачивании: "idResource" + [JsonProperty("idResource")] public string Id { get; set; } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/ItemInfo.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/ItemInfo.cs index 15f49e16..28bf336e 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/ItemInfo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/ItemInfo.cs @@ -67,13 +67,13 @@ class YadItemInfoRequestMeta public string Mimetype { get; set; } [JsonProperty("drweb")] - public long Drweb { get; set; } + public long DrWeb { get; set; } [JsonProperty("resource_id")] public string ResourceId { get; set; } [JsonProperty("mediatype")] - public string Mediatype { get; set; } + public string MediaType { get; set; } [JsonProperty("file_id")] public string FileId { get; set; } @@ -93,4 +93,4 @@ class YadItemInfoRequestParams [JsonProperty("id")] public string Id { get; set; } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/KnownYadModelConverter.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/KnownYadModelConverter.cs index ec4194ac..5fd599da 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/KnownYadModelConverter.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/KnownYadModelConverter.cs @@ -15,7 +15,8 @@ public KnownYadModelConverter(List createdModels) _createdModels = createdModels; } - public override List ReadJson(JsonReader reader, Type objectType, List existingValue, bool hasExistingValue, JsonSerializer serializer) + public override List ReadJson(JsonReader reader, Type objectType, + List existingValue, bool hasExistingValue, JsonSerializer serializer) { var token = JToken.Load(reader); @@ -37,4 +38,4 @@ public override void WriteJson(JsonWriter writer, List value, throw new NotImplementedException(); } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/Media/Albums.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/Media/Albums.cs index d5ba8ee2..795c139b 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/Media/Albums.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/Media/Albums.cs @@ -19,7 +19,7 @@ public YadAlbumsPostModel() internal class YadAlbumsRequestParams { } - + internal class YadAlbumsRequestData { [JsonProperty("album_type")] @@ -62,7 +62,7 @@ internal class YadAlbumsRequestData public bool IsBlocked { get; set; } [JsonProperty("fotki_album_id")] - public object FotkiAlbumId { get; set; } + public object PhotoAlbumId { get; set; } [JsonProperty("ctime")] public long Ctime { get; set; } @@ -74,7 +74,7 @@ internal class YadAlbumsRequestData public bool IsDescSorting { get; set; } [JsonProperty("social_cover_stid")] - public string SocialCoverStid { get; set; } + public string SocialCoverStId { get; set; } } internal class Cover @@ -137,7 +137,7 @@ internal class FMeta public FSize[] Sizes { get; set; } [JsonProperty("mediatype")] - public string Mediatype { get; set; } + public string MediaType { get; set; } [JsonProperty("etime")] public long Etime { get; set; } @@ -179,7 +179,7 @@ internal class FPublic internal class FUser { [JsonProperty("username")] - public string Username { get; set; } + public string UserName { get; set; } [JsonProperty("public_name")] public string PublicName { get; set; } @@ -202,4 +202,4 @@ internal class FUser [JsonProperty("advertising_enabled")] public long AdvertisingEnabled { get; set; } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/Media/GetAlbumsSlices.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/Media/GetAlbumsSlices.cs index 9003ff3f..aaa150d5 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/Media/GetAlbumsSlices.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/Media/GetAlbumsSlices.cs @@ -50,4 +50,4 @@ internal class GAlbum [JsonProperty("preview")] public string Preview { get; set; } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/Media/GetClustersWithResources.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/Media/GetClustersWithResources.cs index 1e252360..813e8261 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/Media/GetClustersWithResources.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/Media/GetClustersWithResources.cs @@ -153,7 +153,7 @@ internal class Zmeta public string Mimetype { get; set; } [JsonProperty("drweb")] - public long Drweb { get; set; } + public long DrWeb { get; set; } [JsonProperty("sizes")] public Size[] Sizes { get; set; } @@ -162,7 +162,7 @@ internal class Zmeta public string ResourceId { get; set; } [JsonProperty("mediatype")] - public string Mediatype { get; set; } + public string MediaType { get; set; } [JsonProperty("etime")] public long Etime { get; set; } @@ -174,7 +174,7 @@ internal class Zmeta public string FileId { get; set; } [JsonProperty("photoslice_time")] - public long PhotosliceTime { get; set; } + public long PhotoSliceTime { get; set; } [JsonProperty("size")] public long Size { get; set; } @@ -193,7 +193,7 @@ internal class Zmeta internal class YadGetClustersWithResourcesRequestParams { [JsonProperty("photosliceId")] - public string PhotosliceId { get; set; } + public string PhotoSliceId { get; set; } [JsonProperty("clusters")] public string Clusters { get; set; } @@ -213,7 +213,7 @@ public override object ReadJson(JsonReader reader, Type t, object existingValue, { if (reader.TokenType == JsonToken.Null) return null; var value = serializer.Deserialize(reader); - + return value switch { "camera" => YadMediaFilter.Camera, @@ -249,7 +249,4 @@ public override void WriteJson(JsonWriter writer, object untypedValue, JsonSeria //public static readonly FilterConverter Singleton = new FilterConverter(); } //------------------------------ - - - -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/Media/InitSnapshot.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/Media/InitSnapshot.cs index da184e9e..47790e5c 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/Media/InitSnapshot.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/Media/InitSnapshot.cs @@ -19,7 +19,7 @@ public YadInitSnapshotPostModel() internal class YadInitSnapshotRequestData : YadModelDataBase { [JsonProperty("photoslice_id")] - public string PhotosliceId { get; set; } + public string PhotoSliceId { get; set; } [JsonProperty("href")] public string Href { get; set; } @@ -37,4 +37,4 @@ internal class YadInitSnapshotRequestData : YadModelDataBase internal class YadInitSnapshotRequestParams { } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/Move.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/Move.cs index 75b4a969..50aa7cba 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/Move.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/Move.cs @@ -21,7 +21,7 @@ public override IEnumerable> ToKvp(int index) { foreach (var pair in base.ToKvp(index)) yield return pair; - + yield return new KeyValuePair($"src.{index}", WebDavPath.Combine("/disk", Source)); yield return new KeyValuePair($"dst.{index}", WebDavPath.Combine("/disk", Destination)); yield return new KeyValuePair($"force.{index}", Force ? "1" : "0"); @@ -34,7 +34,7 @@ internal class YadMoveRequestData : YadModelDataBase public long AtVersion { get; set; } [JsonProperty("oid")] - public string Oid { get; set; } + public string OpId { get; set; } [JsonProperty("type")] public string Type { get; set; } @@ -51,4 +51,4 @@ internal class YadMoveRequestParams [JsonProperty("force")] public long Force { get; set; } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/OperationStatus.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/OperationStatus.cs index e9be3955..7a3a90ec 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/OperationStatus.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/OperationStatus.cs @@ -5,20 +5,20 @@ namespace YaR.Clouds.Base.Repos.YandexDisk.YadWeb.Models { internal class YadOperationStatusPostModel : YadPostModel { - public YadOperationStatusPostModel(string oid) + public YadOperationStatusPostModel(string opId) { Name = "do-status-operation"; - Oid = oid; + _opId = opId; } - public string Oid { get; set; } + public string _opId { get; set; } public override IEnumerable> ToKvp(int index) { foreach (var pair in base.ToKvp(index)) yield return pair; - - yield return new KeyValuePair($"oid.{index}", Oid); + + yield return new KeyValuePair($"oid.{index}", _opId); } } @@ -41,6 +41,6 @@ internal class YadOperationStatusData : YadModelDataBase internal class YadOperationStatusParams { [JsonProperty("oid")] - public string Oid { get; set; } + public string OpId { get; set; } } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/Publish.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/Publish.cs index 4add99f6..d065bf65 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/Publish.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/Publish.cs @@ -20,7 +20,7 @@ public override IEnumerable> ToKvp(int index) { foreach (var pair in base.ToKvp(index)) yield return pair; - + yield return new KeyValuePair($"id.{index}", WebDavPath.Combine("/disk/", Path)); yield return new KeyValuePair($"reverse.{index}", Reverse ? "true" : "false"); } @@ -52,4 +52,4 @@ public class YadPublishRequestParams [JsonProperty("type")] public string Type { get; set; } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/YadResourceStatsPostModel.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/YadResourceStatsPostModel.cs index 5264ac4a..e2cf011d 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/YadResourceStatsPostModel.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/YadResourceStatsPostModel.cs @@ -28,6 +28,9 @@ public override IEnumerable> ToKvp(int index) class YadResourceStatsRequestData : YadModelDataBase { + /// + /// Здесь количество файлов (не директорий), на всех уровнях вложенности. + /// [JsonProperty("files_count")] public long FilesCount { get; set; } @@ -43,5 +46,4 @@ class YadResourceStatsRequestParams [JsonProperty("path")] public string Path { get; set; } } - -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YaDCommonRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YaDCommonRequest.cs index f0912363..cf9b352a 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YaDCommonRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YaDCommonRequest.cs @@ -11,7 +11,7 @@ namespace YaR.Clouds.Base.Repos.YandexDisk.YadWeb.Requests { class YaDCommonRequest : BaseRequestJson { - //private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(YaDCommonRequest)); + private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(YaDCommonRequest)); private readonly YadPostData _postData = new(); @@ -40,7 +40,7 @@ protected override byte[] CreateHttpContent() } public YaDCommonRequest With(T model, out TOut resOUt) - where T : YadPostModel + where T : YadPostModel where TOut : YadResponseModel, new() { _postData.Models.Add(model); @@ -49,11 +49,13 @@ public YaDCommonRequest With(T model, out TOut resOUt) return this; } - protected override string RelationalUri => "/models/?_m=" + _postData.Models - .Select(m => m.Name) - .Aggregate((current, next) => current + "," + next); + protected override string RelationalUri + => string.Concat("/models/?_m=", _postData.Models + .Select(m => m.Name) + .Aggregate((current, next) => current + "," + next)); - protected override RequestResponse DeserializeMessage(NameValueCollection responseHeaders, System.IO.Stream stream) + protected override RequestResponse DeserializeMessage( + NameValueCollection responseHeaders, System.IO.Stream stream) { using var sr = new StreamReader(stream); @@ -63,9 +65,35 @@ protected override RequestResponse DeserializeMessage(NameVal var msg = new RequestResponse { Ok = true, - Result = JsonConvert.DeserializeObject(text, new KnownYadModelConverter(_outData)) + Result = JsonConvert.DeserializeObject( + text, new KnownYadModelConverter(_outData)) }; + + if (YadAuth.Credentials.AuthenticationUsingBrowser) + { + //Logger.Debug($"_postData.Sk={_postData?.Sk} | Result.sk={msg.Result?.Sk}"); + /* + * Строка sk выглядит так: "sk": "cdc3dee74a379c1adc792ef087cf8c9ba19ca9f5:1693681795" + * Правая часть содержит время после двоеточия - количество секунд, начиная с 01.01.1970. + * Обновляем sk полученным значением sk. + */ + if (!string.IsNullOrWhiteSpace(msg.Result?.Sk)) + YadAuth.DiskSk = msg.Result.Sk; + + if (msg.Result.Models != null && + msg.Result.Models.Any(m => m.Error != null)) + { + Logger.Debug(text); + } + if (_postData.Models != null && + _postData.Models.Count > 0 && + _postData.Models[0].Name == "space") + { + Logger.Warn($"Yandex has API version {msg.Result.Version}"); + } + } + return msg; } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadAuthAccountsRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadAuthAccountsRequest.cs index cd728cd2..5a20a30e 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadAuthAccountsRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadAuthAccountsRequest.cs @@ -10,7 +10,7 @@ class YadAuthAccountsRequest : BaseRequestJson { private readonly string _csrf; - public YadAuthAccountsRequest(HttpCommonSettings settings, IAuth auth, string csrf) + public YadAuthAccountsRequest(HttpCommonSettings settings, IAuth auth, string csrf) : base(settings, auth) { _csrf = csrf; @@ -57,4 +57,4 @@ class YadAccounts [JsonProperty("authorizedAccountsDefaultUid")] public string AuthorizedAccountsDefaultUid { get; set; } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadAuthAskV2Request.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadAuthAskV2Request.cs index ef525814..ada83483 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadAuthAskV2Request.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadAuthAskV2Request.cs @@ -53,4 +53,4 @@ class YadAuthAskV2RequestResult [JsonProperty("errors")] public List Errors { get; set; } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadAuthDiskSkRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadAuthDiskSkRequest.cs index 47c48d85..a663da63 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadAuthDiskSkRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadAuthDiskSkRequest.cs @@ -66,5 +66,4 @@ class YadAuthDiskSkRequestResult public string HtmlResponse { get; set; } } - -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadAuthLoginRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadAuthLoginRequest.cs index 7927a088..6717825d 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadAuthLoginRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadAuthLoginRequest.cs @@ -8,14 +8,14 @@ namespace YaR.Clouds.Base.Repos.YandexDisk.YadWeb.Requests { class YadAuthLoginRequest : BaseRequestJson { - private readonly IAuth _auth; + //private readonly IAuth _auth; private readonly string _csrf; private readonly string _uuid; - public YadAuthLoginRequest(HttpCommonSettings settings, IAuth auth, string csrf, string uuid) + public YadAuthLoginRequest(HttpCommonSettings settings, IAuth auth, string csrf, string uuid) : base(settings, auth) { - _auth = auth; + //_auth = auth; _csrf = csrf; _uuid = uuid; } @@ -49,7 +49,7 @@ protected override byte[] CreateHttpContent() } } - + class YadAuthLoginRequestResult { @@ -67,4 +67,4 @@ class YadAuthLoginRequestResult [JsonProperty("errors")] public List Errors { get; set; } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadAuthPasswordRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadAuthPasswordRequest.cs index fd7e2a2f..d8dd2856 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadAuthPasswordRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadAuthPasswordRequest.cs @@ -12,14 +12,14 @@ namespace YaR.Clouds.Base.Repos.YandexDisk.YadWeb.Requests { class YadAuthPasswordRequest : BaseRequestJson { - private readonly IAuth _auth; + //private readonly IAuth _auth; private readonly string _csrf; private readonly string _trackId; - public YadAuthPasswordRequest(HttpCommonSettings settings, IAuth auth, string csrf, string trackId) + public YadAuthPasswordRequest(HttpCommonSettings settings, IAuth auth, string csrf, string trackId) : base(settings, auth) { - _auth = auth; + //_auth = auth; _csrf = csrf; _trackId = trackId; } @@ -99,4 +99,4 @@ class YadAuthPasswordRequestResult [JsonProperty("errors")] public List Errors { get; set; } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadDownloadRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadDownloadRequest.cs index 4807547f..8be31bbc 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadDownloadRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadDownloadRequest.cs @@ -6,14 +6,14 @@ namespace YaR.Clouds.Base.Repos.YandexDisk.YadWeb.Requests { class YadDownloadRequest { - public YadDownloadRequest(HttpCommonSettings settings, IAuth authenticator, string url, long instart, long inend) + public YadDownloadRequest(HttpCommonSettings settings, IAuth auth, string url, long instart, long inend) { - Request = CreateRequest(authenticator, settings.Proxy, url, instart, inend, settings.UserAgent); + Request = CreateRequest(auth, settings.Proxy, url, instart, inend, settings.UserAgent); } public HttpWebRequest Request { get; } - private static HttpWebRequest CreateRequest(IAuth authenticator, IWebProxy proxy, string url, long instart, long inend, string userAgent) + private static HttpWebRequest CreateRequest(IAuth auth, IWebProxy proxy, string url, long instart, long inend, string userAgent) { #pragma warning disable SYSLIB0014 // Type or member is obsolete var request = (HttpWebRequest)WebRequest.Create(url); @@ -26,7 +26,7 @@ private static HttpWebRequest CreateRequest(IAuth authenticator, IWebProxy proxy request.AddRange(instart, inend); request.Proxy = proxy; - request.CookieContainer = authenticator.Cookies; + request.CookieContainer = auth.Cookies; request.Method = "GET"; request.ContentType = MediaTypeNames.Application.Octet; request.Accept = "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3"; @@ -43,4 +43,4 @@ public static implicit operator HttpWebRequest(YadDownloadRequest v) return v.Request; } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadUploadRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadUploadRequest.cs index 397607b8..c3496895 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadUploadRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadUploadRequest.cs @@ -35,4 +35,4 @@ public static implicit operator HttpWebRequest(YadUploadRequest v) return v.Request; } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadHasher.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadHasher.cs index 343b0c6d..268ea463 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadHasher.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadHasher.cs @@ -48,7 +48,7 @@ public IFileHash Hash { get { - if (null != _hashSha256 && null != _hashMd5) + if (null != _hashSha256 && null != _hashMd5) return new FileHashYad(_hashSha256, _hashMd5); AppendFinalBuffer(); diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebAuth.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebAuth.cs index dc24ff72..4e5fe7f8 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebAuth.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebAuth.cs @@ -11,16 +11,31 @@ namespace YaR.Clouds.Base.Repos.YandexDisk.YadWeb { class YadWebAuth : IAuth { - public YadWebAuth(SemaphoreSlim connectionLimiter, HttpCommonSettings settings, IBasicCredentials credentials) + private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(YadWebAuth)); + + public YadWebAuth(SemaphoreSlim connectionLimiter, HttpCommonSettings settings, Credentials credentials) { _settings = settings; - _creds = credentials; - Cookies = new CookieContainer(); + Credentials = credentials; + + if (credentials?.Cookies?.Count > 0 && + !string.IsNullOrEmpty(credentials.Sk) && + !string.IsNullOrEmpty(credentials.Uuid)) + { + Cookies = credentials.Cookies; + Uuid = credentials.Uuid; + DiskSk = credentials.Sk; + } + else + { + Cookies = new CookieContainer(); - var _ = MakeLogin(connectionLimiter).Result; + var _ = MakeLogin(connectionLimiter).Result; + } } - private readonly IBasicCredentials _creds; + public Credentials Credentials { get; } + private readonly HttpCommonSettings _settings; public async Task MakeLogin(SemaphoreSlim connectionLimiter) @@ -64,8 +79,8 @@ public async Task MakeLogin(SemaphoreSlim connectionLimiter) return true; } - public string Login => _creds.Login; - public string Password => _creds.Password; + public string Login => Credentials.Login; + public string Password => Credentials.Password; public string DiskSk { get; set; } public string Uuid { get; set; } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebRequestRepo.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebRequestRepo.cs index 1f81e7ba..f6bff3d8 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebRequestRepo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebRequestRepo.cs @@ -19,18 +19,36 @@ namespace YaR.Clouds.Base.Repos.YandexDisk.YadWeb { class YadWebRequestRepo : IRequestRepo { + #region Константы и внутренние классы + + + + + + + + + + + + + + + + #endregion + private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(YadWebRequestRepo)); - private readonly SemaphoreSlim _connectionLimiter; + protected readonly SemaphoreSlim _connectionLimiter; private ItemOperation _lastRemoveOperation; - - private const int OperationStatusCheckIntervalMs = 300; - private const int OperationStatusCheckRetryCount = 8; - private readonly IBasicCredentials _creds; + protected const int OperationStatusCheckIntervalMs = 300; + protected const int OperationStatusCheckRetryCount = 8; + + protected readonly Credentials _credentials; - public YadWebRequestRepo(CloudSettings settings, IWebProxy proxy, IBasicCredentials credentials) + public YadWebRequestRepo(CloudSettings settings, IWebProxy proxy, Credentials credentials) { _connectionLimiter = new SemaphoreSlim(settings.MaxConnectionCount); @@ -41,7 +59,7 @@ public YadWebRequestRepo(CloudSettings settings, IWebProxy proxy, IBasicCredenti Proxy = proxy, }; - _creds = credentials; + _credentials = credentials; ServicePointManager.DefaultConnectionLimit = int.MaxValue; @@ -49,9 +67,9 @@ public YadWebRequestRepo(CloudSettings settings, IWebProxy proxy, IBasicCredenti ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13 | SecurityProtocolType.Tls12; } - private async Task>> GetShareListInner() + protected async Task>> GetShareListInner() { - await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) + await new YaDCommonRequest(HttpSettings, (YadWebAuth)Auth) .With(new YadFolderInfoPostModel("/", "/published"), out YadResponseModel folderInfo) .MakeRequestAsync(_connectionLimiter); @@ -59,17 +77,18 @@ private async Task>> GetShareList var res = folderInfo.Data.Resources .Where(it => !string.IsNullOrEmpty(it.Meta?.UrlShort)) .ToDictionary( - it => it.Path.Remove(0, "/disk".Length), + it => it.Path.Remove(0, "/disk".Length), it => Enumerable.Repeat(new PublicLinkInfo("short", it.Meta.UrlShort), 1)); return res; } - public IAuth Authenticator => CachedAuth.Value; + public IAuth Auth => CachedAuth.Value; - private Cached CachedAuth => _cachedAuth ??= - new Cached(_ => new YadWebAuth(_connectionLimiter, HttpSettings, _creds), _ => TimeSpan.FromHours(23)); - private Cached _cachedAuth; + protected Cached CachedAuth + => _cachedAuth ??= new Cached(_ => new YadWebAuth(_connectionLimiter, HttpSettings, _credentials), + _ => TimeSpan.FromHours(23)); + protected Cached _cachedAuth; public Cached>> CachedSharedList => _cachedSharedList ??= new Cached>>( @@ -77,14 +96,14 @@ public Cached>> CachedSharedList { var res = GetShareListInner().Result; return res; - }, + }, _ => TimeSpan.FromSeconds(30)); - private Cached>> _cachedSharedList; + protected Cached>> _cachedSharedList; public HttpCommonSettings HttpSettings { get; private set; } - public Stream GetDownloadStream(File afile, long? start = null, long? end = null) + public virtual Stream GetDownloadStream(File aFile, long? start = null, long? end = null) { CustomDisposable ResponseGenerator(long instart, long inend, File file) { @@ -92,29 +111,29 @@ CustomDisposable ResponseGenerator(long instart, long inend, Fi // .MakeRequestAsync(_connectionLimiter) // .Result; - var _ = new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) + var _ = new YaDCommonRequest(HttpSettings, (YadWebAuth)Auth) .With(new YadGetResourceUrlPostModel(file.FullPath), out YadResponseModel itemInfo) .MakeRequestAsync(_connectionLimiter).Result; var url = "https:" + itemInfo.Data.File; - HttpWebRequest request = new YadDownloadRequest(HttpSettings, (YadWebAuth)Authenticator, url, instart, inend); + HttpWebRequest request = new YadDownloadRequest(HttpSettings, (YadWebAuth)Auth, url, instart, inend); var response = (HttpWebResponse)request.GetResponse(); return new CustomDisposable { Value = response, - OnDispose = () => {} + OnDispose = () => { } }; } - var stream = new DownloadStream(ResponseGenerator, afile, start, end); + var stream = new DownloadStream(ResponseGenerator, aFile, start, end); return stream; } //public HttpWebRequest UploadRequest(File file, UploadMultipartBoundary boundary) //{ - // var urldata = + // var urldata = // new YadGetResourceUploadUrlRequest(HttpSettings, (YadWebAuth)Authenticator, file.FullPath, file.OriginalSize) // .MakeRequestAsync(_connectionLimiter) // .Result; @@ -132,12 +151,12 @@ public ICloudHasher GetHasher() public bool SupportsAddSmallFileByHash => false; public bool SupportsDeduplicate => true; - private HttpRequestMessage CreateUploadClientRequest(PushStreamContent content, File file) + protected (HttpRequestMessage, string opId) CreateUploadClientRequest(PushStreamContent content, File file) { - var hash = (FileHashYad?) file.Hash; - var _ = new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) - .With(new YadGetResourceUploadUrlPostModel(file.FullPath, file.OriginalSize, - hash?.HashSha256.Value ?? string.Empty, + var hash = (FileHashYad?)file.Hash; + var _ = new YaDCommonRequest(HttpSettings, (YadWebAuth)Auth) + .With(new YadGetResourceUploadUrlPostModel(file.FullPath, file.OriginalSize, + hash?.HashSha256.Value ?? string.Empty, hash?.HashMd5.Value ?? string.Empty), out YadResponseModel itemInfo) .MakeRequestAsync(_connectionLimiter).Result; @@ -155,13 +174,12 @@ private HttpRequestMessage CreateUploadClientRequest(PushStreamContent content, request.Content = content; request.Content.Headers.ContentLength = file.OriginalSize; - - return request; + return (request, itemInfo?.Data?.OpId); } - public async Task DoUpload(HttpClient client, PushStreamContent content, File file) + public virtual async Task DoUpload(HttpClient client, PushStreamContent content, File file) { - var request = CreateUploadClientRequest(content, file); + (var request, string opId) = CreateUploadClientRequest(content, file); var responseMessage = await client.SendAsync(request); var ures = responseMessage.ToUploadPathResult(); @@ -171,9 +189,9 @@ public async Task DoUpload(HttpClient client, PushStreamConten return ures; } - private const string YadMediaPath = "/Media.wdyad"; + protected const string YadMediaPath = "/Media.wdyad"; - public async Task FolderInfo(RemotePath path, int offset = 0, int limit = int.MaxValue, int depth = 1) + public virtual async Task FolderInfo(RemotePath path, int offset = 0, int limit = int.MaxValue, int depth = 1) { if (path.IsLink) throw new NotImplementedException(nameof(FolderInfo)); @@ -186,6 +204,8 @@ public async Task FolderInfo(RemotePath path, int offset = 0, int limit YadResponseModel folderInfo = null; YadResponseModel resourceStats = null; + + bool hasRemoveOp = _lastRemoveOperation != null && WebDavPath.IsParentOrSame(path.Path, _lastRemoveOperation.Path) && (DateTime.Now - _lastRemoveOperation.DateTime).TotalMilliseconds < 1_000; @@ -199,7 +219,7 @@ public async Task FolderInfo(RemotePath path, int offset = 0, int limit Logger.Debug("Has remove op, sleep before"); return doPreSleep; }, - () => new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) + () => new YaDCommonRequest(HttpSettings, (YadWebAuth)Auth) .With(new YadItemInfoPostModel(path.Path), out itemInfo) .With(new YadFolderInfoPostModel(path.Path) { Amount = limit }, out folderInfo) .With(new YadResourceStatsPostModel(path.Path), out resourceStats) @@ -213,26 +233,26 @@ public async Task FolderInfo(RemotePath path, int offset = 0, int limit if (doAgain) Logger.Debug("Remove op still not finished, let's try again"); return doAgain; - }, + }, TimeSpan.FromMilliseconds(OperationStatusCheckIntervalMs), OperationStatusCheckRetryCount); - - var itdata = itemInfo?.Data; - switch (itdata?.Type) + + var itemData = itemInfo?.Data; + switch (itemData?.Type) { case null: return null; case "file": - return itdata.ToFile(PublicBaseUrlDefault); + return itemData.ToFile(PublicBaseUrlDefault); default: - Folder folder = folderInfo.Data.ToFolder(itemInfo.Data, resourceStats.Data, path.Path, PublicBaseUrlDefault); + Folder folder = folderInfo.Data.ToFolder(itemInfo.Data, resourceStats.Data, path.Path, PublicBaseUrlDefault, null); folder.IsChildrenLoaded = true; return folder; } } - private async Task MediaFolderInfo(string path) + protected async Task MediaFolderInfo(string path) { var entry = await MediaFolderRootInfo(); @@ -253,13 +273,13 @@ private async Task MediaFolderInfo(string path) if (key == null) return null; - _ = new YaDCommonRequest(HttpSettings, (YadWebAuth)Authenticator) + _ = new YaDCommonRequest(HttpSettings, (YadWebAuth)Auth) .With(new YadFolderInfoPostModel(key, "/album"), out YadResponseModel folderInfo) .MakeRequestAsync(_connectionLimiter) .Result; - Folder folder = folderInfo.Data.ToFolder(null, null, path, PublicBaseUrlDefault); + Folder folder = folderInfo.Data.ToFolder(null, null, path, PublicBaseUrlDefault, null); folder.IsChildrenLoaded = true; return folder; @@ -269,7 +289,7 @@ private async Task MediaFolderRootInfo() { Folder res = new Folder(YadMediaPath); - _ = await new YaDCommonRequest(HttpSettings, (YadWebAuth)Authenticator) + _ = await new YaDCommonRequest(HttpSettings, (YadWebAuth)Auth) .With(new YadGetAlbumsSlicesPostModel(), out YadResponseModel slices) .With(new YadAlbumsPostModel(), @@ -329,7 +349,7 @@ public async Task AccountInfo() { //var req = await new YadAccountInfoRequest(HttpSettings, (YadWebAuth)Authenticator).MakeRequestAsync(_connectionLimiter); - await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) + await new YaDCommonRequest(HttpSettings, (YadWebAuth)Auth) .With(new YadAccountInfoPostModel(), out YadResponseModel itemInfo) .MakeRequestAsync(_connectionLimiter); @@ -343,7 +363,7 @@ public async Task CreateFolder(string path) //var req = await new YadCreateFolderRequest(HttpSettings, (YadWebAuth)Authenticator, path) // .MakeRequestAsync(_connectionLimiter); - await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) + await new YaDCommonRequest(HttpSettings, (YadWebAuth)Auth) .With(new YadCreateFolderPostModel(path), out YadResponseModel itemInfo) .MakeRequestAsync(_connectionLimiter); @@ -357,7 +377,7 @@ public async Task AddFile(string fileFullPath, IFileHash fileHash { var hash = (FileHashYad?)fileHash; - var _ = new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) + var _ = new YaDCommonRequest(HttpSettings, (YadWebAuth)Auth) .With(new YadGetResourceUploadUrlPostModel(fileFullPath, fileSize, hash?.HashSha256.Value, hash?.HashMd5.Value), out YadResponseModel itemInfo) .MakeRequestAsync(_connectionLimiter).Result; @@ -383,63 +403,49 @@ public async Task Copy(string sourceFullPath, string destinationPath //var req = await new YadCopyRequest(HttpSettings, (YadWebAuth)Authenticator, sourceFullPath, destFullPath) // .MakeRequestAsync(_connectionLimiter); - await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) + await new YaDCommonRequest(HttpSettings, (YadWebAuth)Auth) .With(new YadCopyPostModel(sourceFullPath, destFullPath), out YadResponseModel itemInfo) .MakeRequestAsync(_connectionLimiter); var res = itemInfo.ToCopyResult(); + + OnCopyCompleted(res, itemInfo?.Data?.OpId); + return res; } + protected virtual void OnCopyCompleted(CopyResult res, string operationOpId) + { + } + public async Task Move(string sourceFullPath, string destinationPath, ConflictResolver? conflictResolver = null) { string destFullPath = WebDavPath.Combine(destinationPath, WebDavPath.Name(sourceFullPath)); - await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) + await new YaDCommonRequest(HttpSettings, (YadWebAuth)Auth) .With(new YadMovePostModel(sourceFullPath, destFullPath), out YadResponseModel itemInfo) .MakeRequestAsync(_connectionLimiter); var res = itemInfo.ToMoveResult(); - WaitForOperation(itemInfo.Data.Oid); + OnMoveCompleted(res, itemInfo?.Data?.OpId); return res; } - - private void WaitForOperation(string operationOid) - { - if (string.IsNullOrWhiteSpace(operationOid)) - return; - - YadResponseModel itemInfo = null; - Retry.Do( - () => TimeSpan.Zero, - () => new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) - .With(new YadOperationStatusPostModel(operationOid), out itemInfo) - .MakeRequestAsync(_connectionLimiter) - .Result, - _ => - { - var doAgain = null == itemInfo.Data.Error && itemInfo.Data.State != "COMPLETED"; - //if (doAgain) - // Logger.Debug("Move op still not finished, let's try again"); - return doAgain; - }, - TimeSpan.FromMilliseconds(OperationStatusCheckIntervalMs), OperationStatusCheckRetryCount); - } + protected virtual void OnMoveCompleted(CopyResult res, string operationOpId) => WaitForOperation(operationOpId); public async Task Publish(string fullPath) { - await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) + await new YaDCommonRequest(HttpSettings, (YadWebAuth)Auth) .With(new YadPublishPostModel(fullPath, false), out YadResponseModel itemInfo) .MakeRequestAsync(_connectionLimiter); var res = itemInfo.ToPublishResult(); if (res.IsSuccess) - CachedSharedList.Value[fullPath] = new List {new(res.Url)}; + CachedSharedList.Value[fullPath] = new List { new(res.Url) }; return res; } @@ -452,7 +458,7 @@ public async Task Unpublish(Uri publicLink, string fullPath) CachedSharedList.Value.Remove(item.Key); } - await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) + await new YaDCommonRequest(HttpSettings, (YadWebAuth)Auth) .With(new YadPublishPostModel(fullPath, true), out YadResponseModel itemInfo) .MakeRequestAsync(_connectionLimiter); @@ -466,19 +472,24 @@ public async Task Remove(string fullPath) //var req = await new YadDeleteRequest(HttpSettings, (YadWebAuth)Authenticator, fullPath) // .MakeRequestAsync(_connectionLimiter); - await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) + await new YaDCommonRequest(HttpSettings, (YadWebAuth)Auth) .With(new YadDeletePostModel(fullPath), out YadResponseModel itemInfo) .MakeRequestAsync(_connectionLimiter); var res = itemInfo.ToRemoveResult(); - - if (res.IsSuccess) - _lastRemoveOperation = res.ToItemOperation(); + + OnRemoveCompleted(res, itemInfo?.Data?.OpId); return res; } + protected virtual void OnRemoveCompleted(RemoveResult res, string operationOpId) + { + if (res.IsSuccess) + _lastRemoveOperation = res.ToItemOperation(); + } + public async Task Rename(string fullPath, string newName) { string destPath = WebDavPath.Parent(fullPath); @@ -486,21 +497,25 @@ public async Task Rename(string fullPath, string newName) //var req = await new YadMoveRequest(HttpSettings, (YadWebAuth)Authenticator, fullPath, destPath).MakeRequestAsync(_connectionLimiter); - await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) + await new YaDCommonRequest(HttpSettings, (YadWebAuth)Auth) .With(new YadMovePostModel(fullPath, destPath), out YadResponseModel itemInfo) .MakeRequestAsync(_connectionLimiter); var res = itemInfo.ToRenameResult(); + OnRenameCompleted(res, itemInfo?.Data?.OpId); + + return res; + } + + protected virtual void OnRenameCompleted(RenameResult res, string operationOpId) + { if (res.IsSuccess) - WaitForOperation(itemInfo.Data.Oid); + WaitForOperation(operationOpId); //if (res.IsSuccess) // _lastRemoveOperation = res.ToItemOperation(); - - - return res; } public Dictionary GetShardInfo1() @@ -511,35 +526,30 @@ public Dictionary GetShardInfo1() public IEnumerable GetShareLinks(string path) { - if (!CachedSharedList.Value.TryGetValue(path, out var links)) + if (!CachedSharedList.Value.TryGetValue(path, out var links)) yield break; foreach (var link in links) yield return link; } - + public async void CleanTrash() { - await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) - .With(new YadCleanTrashPostModel(), + await new YaDCommonRequest(HttpSettings, (YadWebAuth)Auth) + .With(new YadCleanTrashPostModel(), out YadResponseModel _) .MakeRequestAsync(_connectionLimiter); } - + public IEnumerable PublicBaseUrls { get; set; } = new[] { "https://yadi.sk" }; - public string PublicBaseUrlDefault => PublicBaseUrls.First(); - - - - - + public string PublicBaseUrlDefault => PublicBaseUrls.FirstOrDefault(); public string ConvertToVideoLink(Uri publicLink, SharedVideoResolution videoResolution) @@ -547,6 +557,28 @@ public string ConvertToVideoLink(Uri publicLink, SharedVideoResolution videoReso throw new NotImplementedException("Yad not implemented ConvertToVideoLink"); } - public async Task ActiveOperationsAsync() => await Task.FromResult(null); + protected virtual void WaitForOperation(string operationOpId) + { + if (string.IsNullOrWhiteSpace(operationOpId)) + return; + + YadResponseModel itemInfo = null; + Retry.Do( + () => TimeSpan.Zero, + () => new YaDCommonRequest(HttpSettings, (YadWebAuth)Auth) + .With(new YadOperationStatusPostModel(operationOpId), out itemInfo) + .MakeRequestAsync(_connectionLimiter) + .Result, + _ => + { + var doAgain = null == itemInfo.Data.Error && itemInfo.Data.State != "COMPLETED"; + //if (doAgain) + // Logger.Debug("Move op still not finished, let's try again"); + return doAgain; + }, + TimeSpan.FromMilliseconds(OperationStatusCheckIntervalMs), OperationStatusCheckRetryCount); + } + + public virtual async Task DetectOutsideChanges() => await Task.FromResult(null); } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebRequestRepo2.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebRequestRepo2.cs new file mode 100644 index 00000000..80f6db3d --- /dev/null +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebRequestRepo2.cs @@ -0,0 +1,376 @@ +using System; +using System.Collections.Generic; +using System.Collections.Immutable; +using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; +using YaR.Clouds.Base.Repos.MailRuCloud; +using YaR.Clouds.Base.Repos.YandexDisk.YadWeb.Models; +using YaR.Clouds.Base.Repos.YandexDisk.YadWeb.Requests; +using YaR.Clouds.Base.Requests.Types; +using YaR.Clouds.Base.Streams; +using YaR.Clouds.Common; +using Stream = System.IO.Stream; + +namespace YaR.Clouds.Base.Repos.YandexDisk.YadWeb +{ + class YadWebRequestRepo2 : YadWebRequestRepo + { + #region Константы и внутренние классы + + /// + /// Сколько записей папки читать в первом обращении, до параллельного чтения. + /// Яндекс читает по 40 записей, путь тоже будет 40. + /// + private const int FirstReadEntriesCount = 40; + + private const int OperationStatusCheckRetryTimeoutMinutes = 5; + + private struct ParallelFolderInfo + { + public int Offset; + public int Amount; + public YadFolderInfoRequestData Result; + } + + #endregion + + private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(YadWebRequestRepo2)); + + private readonly TimeSpan OperationStatusCheckRetryTimeout = TimeSpan.FromMinutes(OperationStatusCheckRetryTimeoutMinutes); + + + + + public YadWebRequestRepo2(CloudSettings settings, IWebProxy proxy, Credentials credentials) + : base(settings, proxy, credentials) + { + } + + public override Stream GetDownloadStream(File aFile, long? start = null, long? end = null) + { + CustomDisposable ResponseGenerator(long instart, long inend, File file) + { + //var urldata = new YadGetResourceUrlRequest(HttpSettings, (YadWebAuth)Authenticator, file.FullPath) + // .MakeRequestAsync(_connectionLimiter) + // .Result; + string url = null; + if (file.DownloadUrlCache == null || + file.DownloadUrlCacheExpirationTime <= DateTime.Now) + { + var _ = new YaDCommonRequest(HttpSettings, (YadWebAuth)Auth) + .With(new YadGetResourceUrlPostModel(file.FullPath), + out YadResponseModel itemInfo) + .MakeRequestAsync(_connectionLimiter).Result; + + if (itemInfo == null || + itemInfo.Error != null || + itemInfo.Data == null || + itemInfo.Data.Error != null || + itemInfo?.Data?.File == null) + { + throw new FileNotFoundException(string.Concat( + "File reading error ", itemInfo?.Error?.Message, + " ", + itemInfo?.Data?.Error?.Message, + " ", + itemInfo?.Data?.Error?.Body?.Title)); + } + url = "https:" + itemInfo.Data.File; + + file.DownloadUrlCache = url; + file.DownloadUrlCacheExpirationTime = DateTime.Now.AddMinutes(1); + } + else + { + url = file.DownloadUrlCache; + } + HttpWebRequest request = new YadDownloadRequest(HttpSettings, (YadWebAuth)Auth, url, instart, inend); + var response = (HttpWebResponse)request.GetResponse(); + + return new CustomDisposable + { + Value = response, + OnDispose = () => {} + }; + } + + if (start.HasValue || end.HasValue) + Logger.Debug($"Download: {aFile.FullPath} [{start}-{end}]"); + else + Logger.Debug($"Download: {aFile.FullPath}"); + + var stream = new DownloadStream(ResponseGenerator, aFile, start, end); + return stream; + } + + public override async Task DoUpload(HttpClient client, PushStreamContent content, File file) + { + (var request, string opId) = CreateUploadClientRequest(content, file); + var responseMessage = await client.SendAsync(request); + var ures = responseMessage.ToUploadPathResult(); + + ures.NeedToAddFile = false; + + if (!string.IsNullOrEmpty(opId)) + WaitForOperation(opId); + + return ures; + } + + public override async Task FolderInfo(RemotePath path, int offset = 0, int limit = int.MaxValue, int depth = 1) + { + if (path.IsLink) + throw new NotImplementedException(nameof(FolderInfo)); + + if (path.Path.StartsWith(YadMediaPath)) + return await MediaFolderInfo(path.Path); + + // YaD perform async deletion + YadResponseModel itemInfo = null; + YadResponseModel folderInfo = null; + YadResponseModel resourceStats = null; + YadResponseModel, YadActiveOperationsParams> activeOps = null; + + /* + * Не менее 1 параллельного потока, + * не более доступного по ограничителю за вычетом одного для соседних запросов, + * но не более 10, т.к. вряд ли сервер будет выдавать данные быстрее, а канал уже и так будет загружен. + */ + int maxParallel = Math.Max(Math.Max(_connectionLimiter.CurrentCount - 1, 1), 10); + // Если доступных подключений к серверу 2 или менее, то не делаем параллельного чтения + int firstReadLimit = maxParallel <= 2 ? int.MaxValue : FirstReadEntriesCount; + + Logger.Debug($"Listing path {path.Path}"); + + Retry.Do( + () => TimeSpan.Zero, + () => new YaDCommonRequest(HttpSettings, (YadWebAuth)Auth) + .With(new YadItemInfoPostModel(path.Path), out itemInfo) + .With(new YadFolderInfoPostModel(path.Path) { WithParent = true, Amount = firstReadLimit }, out folderInfo) + .With(new YadResourceStatsPostModel(path.Path), out resourceStats) + .With(new YadActiveOperationsPostModel(), out activeOps) + .MakeRequestAsync(_connectionLimiter) + .Result, + _ => false, + TimeSpan.FromMilliseconds(OperationStatusCheckIntervalMs), OperationStatusCheckRetryTimeout); + + + if (itemInfo?.Error != null || + (itemInfo?.Data?.Error?.Id ?? "HTTP_404") != "HTTP_404" || + resourceStats?.Error != null || + (resourceStats?.Data?.Error?.Id ?? "HTTP_404") != "HTTP_404" || + folderInfo?.Error != null || + (folderInfo?.Data?.Error?.Id ?? "HTTP_404") != "HTTP_404") + { + throw new IOException(string.Concat("Error reading file or directory information from server ", + itemInfo?.Error?.Message, + " ", + itemInfo?.Data?.Error?.Message, + " ", + resourceStats?.Error?.Message, + " ", + resourceStats?.Data?.Error?.Message)); + } + + var entryData = itemInfo?.Data; + if (entryData?.Type is null) + return null; + if (entryData.Type == "file") + return entryData.ToFile(PublicBaseUrlDefault); + + Folder folder = folderInfo.Data.ToFolder(entryData, resourceStats.Data, path.Path, PublicBaseUrlDefault, activeOps?.Data); + folder.IsChildrenLoaded = limit == int.MaxValue; + + int alreadyCount = folder.Descendants.Count; + // Если количество полученных элементов списка меньше максимального запрошенного числа элементов, + // даже с учетом, что в число элементов сверх запрошенного входит информация + // о папке-контейнере (папке, чей список элементов запросили), то считаем, + // что получен полный список содержимого папки и возвращает данные. + if (alreadyCount < firstReadLimit) + return folder; + // В противном случае делаем несколько параллельных выборок для ускорения чтения списков с сервера. + + int entryCount = folderInfo?.Data?.Resources.FirstOrDefault()?.Meta?.TotalEntityCount ?? 1; + + /* + * Не менее 1 параллельного потока, + * не более доступного по ограничителю за вычетом одного для соседних запросов, + * но не более 10, т.к. вряд ли сервер будет выдавать данные быстрее, а канал уже и так будет загружен. + * + * Здесь просто повторяем расчет, т.к. свободных потоков по ограничителю могло поменяться. + */ + maxParallel = Math.Max(Math.Max(_connectionLimiter.CurrentCount - 1, 1), 10); + var info = new ParallelFolderInfo[maxParallel]; + int restAmount = entryCount - alreadyCount; + + int amountParallel = 40 * maxParallel > restAmount + ? 40 + : (restAmount + maxParallel-1) / maxParallel; + + int startIndex = alreadyCount; + int lastIndex = 0; + for (int i = 0; startIndex < entryCount && i < info.Length; i++) + { + info[i] = new ParallelFolderInfo + { + Offset = startIndex, + Amount = amountParallel, + }; + startIndex += amountParallel; + lastIndex = i; + } + + if (lastIndex < info.Length - 1) + Array.Resize(ref info, lastIndex + 1); + + // Хвостовой кусок читаем без ограничения длины, на случай неправильных подсчетов + // или добавленных в параллели файлов. + info[info.Length - 1].Amount = int.MaxValue; + + Retry.Do( + () => TimeSpan.Zero, + () => + { + string diskPath = WebDavPath.Combine("/disk", entryData.Path); + + Parallel.For(0, info.Length, (int index) => + { + YadResponseResult noReturn = new YaDCommonRequest(HttpSettings, (YadWebAuth)Auth) + .With(new YadFolderInfoPostModel(path.Path) + { + Offset = info[index].Offset, + Amount = info[index].Amount, + WithParent = false + }, out YadResponseModel folderPartInfo) + .MakeRequestAsync(_connectionLimiter) + .Result; + + if (folderPartInfo?.Error != null || + folderPartInfo?.Data?.Error != null) + throw new IOException(string.Concat("Error reading file or directory information from server ", + folderPartInfo?.Error?.Message, + " ", + folderPartInfo?.Data?.Error?.Message)); + + if (folderPartInfo?.Data is not null && folderPartInfo.Error is null) + info[index].Result = folderPartInfo.Data; + }); + + return (YadResponseResult)null; + }, + _ => false, + TimeSpan.FromMilliseconds(OperationStatusCheckIntervalMs), OperationStatusCheckRetryTimeout); + + string diskPath = WebDavPath.Combine("/disk", path.Path); + var children = new List(folder.Descendants); + for (int i = 0; i < info.Length; i++) + { + var fi = info[i].Result.Resources; + children.AddRange( + fi.Where(it => it.Type == "file") + .Select(f => f.ToFile(PublicBaseUrlDefault)) + .ToGroupedFiles()); + children.AddRange( + fi.Where(it => it.Type == "dir" && + // Пропуск элемента с информацией папки о родительской папке, + // этот элемент добавляется в выборки, если читается + // не всё содержимое папки, а делается только вырезка + it.Path != diskPath) + .Select(f => f.ToFolder())); + } + folder.Descendants = ImmutableList.Create(children.Distinct().ToArray()); + + return folder; + } + + protected override void OnCopyCompleted(CopyResult res, string operationOpId) => WaitForOperation(operationOpId); + + protected override void OnMoveCompleted(CopyResult res, string operationOpId) => WaitForOperation(operationOpId); + + protected override void OnRemoveCompleted(RemoveResult res, string operationOpId) => WaitForOperation(operationOpId); + + protected override void OnRenameCompleted(RenameResult res, string operationOpId) => WaitForOperation(operationOpId); + + protected override void WaitForOperation(string operationOpId) + { + if (string.IsNullOrWhiteSpace(operationOpId)) + return; + + var flagWatch = Stopwatch.StartNew(); + + YadResponseModel itemInfo = null; + Retry.Do( + () => TimeSpan.Zero, + () => new YaDCommonRequest(HttpSettings, (YadWebAuth)Auth) + .With(new YadOperationStatusPostModel(operationOpId), out itemInfo) + .MakeRequestAsync(_connectionLimiter) + .Result, + _ => + { + /* + * Яндекс повторяет проверку при переносе папки каждый 9 секунд. + * Когда операция завершилась: "status": "DONE", "state": "COMPLETED", "type": "move" + * "params": { + * "source": "12-it's_uid-34:/disk/source-folder", + * "target": "12-it's_uid-34:/disk/destination-folder" + * }, + * Когда операция еще в процессе: "status": "EXECUTING", "state": "EXECUTING", "type": "move" + * "params": { + * "source": "12-it's_uid-34:/disk/source-folder", + * "target": "12-it's_uid-34:/disk/destination-folder" + * }, + */ + var doAgain = itemInfo.Data.Error is null && itemInfo.Data.State != "COMPLETED"; + if (doAgain) + { + if (flagWatch.Elapsed > TimeSpan.FromSeconds(30)) + { + Logger.Debug("Operation is still in progress, let's wait..."); + flagWatch.Restart(); + } + } + return doAgain; + }, + TimeSpan.FromMilliseconds(OperationStatusCheckIntervalMs), OperationStatusCheckRetryTimeout); + } + + public override async Task DetectOutsideChanges() + { + YadResponseModel, YadActiveOperationsParams> itemInfo = null; + + _ = await new YaDCommonRequest(HttpSettings, (YadWebAuth)Auth) + .With(new YadActiveOperationsPostModel(), out itemInfo) + .With(new YadAccountInfoPostModel(), + out YadResponseModel accountInfo) + .MakeRequestAsync(_connectionLimiter); + + var list = itemInfo?.Data? + .Select(x => new ActiveOperation + { + OpId = x.OpId, + Uid = x.Uid, + Type = x.Type, + SourcePath = DtoImportYadWeb.GetOpPath(x.Data.Source), + TargetPath = DtoImportYadWeb.GetOpPath(x.Data.Target), + })?.ToList(); + + var info = new CheckUpInfo + { + AccountInfo = new CheckUpInfo.CheckInfo + { + FilesCount = accountInfo?.Data?.FilesCount ?? 0, + Free = accountInfo?.Data?.Free ?? 0, + Trash = accountInfo?.Data?.Trash ?? 0, + }, + ActiveOperations = list, + }; + + return info; + } + + } +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/DtoImportYadWeb.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/DtoImportYadWeb.cs deleted file mode 100644 index c65b27d9..00000000 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/DtoImportYadWeb.cs +++ /dev/null @@ -1,300 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Security.Authentication; -using YaR.Clouds.Base.Repos.YandexDisk.YadWebV2.Models; -using YaR.Clouds.Base.Requests.Types; - -namespace YaR.Clouds.Base.Repos.YandexDisk.YadWebV2 -{ - static class DtoImportYadWeb - { - private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(DtoImportYadWeb)); - - public static AccountInfoResult ToAccountInfo(this YadResponseModel data) - { - if (data?.Data == null || data.Error != null) - throw new AuthenticationException(string.Concat("OAuth: Authentication using YandexAuthBrowser is failed! ", data?.Error?.Message)); - - var info = data.Data; - var res = new AccountInfoResult - { - FileSizeLimit = info.FilesizeLimit, - - DiskUsage = new DiskUsage - { - Total = info.Limit, - Used = info.Used, - OverQuota = info.Used > info.Limit - } - }; - - return res; - } - - public static Folder ToFolder(this YadFolderInfoRequestData folderData, - YadItemInfoRequestData entryData, YadResourceStatsRequestData entryStats, string path, - List activeOps) - { - if (folderData is null) - throw new ArgumentNullException(nameof(folderData)); - - List paths = new List(); - foreach (var op in activeOps) - { - string p = YadWebRequestRepo.GetOpPath(op.Data.Target); - if (!string.IsNullOrEmpty(p)) - paths.Add(p); - p = YadWebRequestRepo.GetOpPath(op.Data.Source); - if (!string.IsNullOrEmpty(p)) - paths.Add(p); - } - - if (path.StartsWith("/disk")) - path = path.Remove(0, "/disk".Length); - - var folder = new Folder(entryStats?.Size ?? entryData?.Meta?.Size ?? 0, path) { IsChildrenLoaded = false }; - if (!string.IsNullOrEmpty(entryData?.Meta?.UrlShort)) - { - PublicLinkInfo item = new PublicLinkInfo("short", entryData.Meta.UrlShort); - folder.PublicLinks.TryAdd(item.Uri.AbsoluteUri, item); - } - if (paths.Any(x => WebDavPath.IsParentOrSame(x, folder.FullPath))) - { - // Признак операции на сервере, под которую подпадает папка - folder.Attributes |= System.IO.FileAttributes.Offline; - } - - string diskPath = WebDavPath.Combine("/disk", path); - var fi = folderData.Resources; - var children = new List(); - - children.AddRange( - fi.Where(it => it.Type == "file") - .Select(f => f.ToFile()) - .ToGroupedFiles()); - children.AddRange( - fi.Where(it => it.Type == "dir" && - // Пропуск элемента с информацией папки о родительской папке, - // этот элемент добавляется в выборки, если читается - // не всё содержимое папки, а делается только вырезка - it.Path != diskPath) - .Select(f => f.ToFolder())); - - folder.Descendants = folder.Descendants.AddRange(children); - folder.Descendants.ForEach(entry => - { - if (paths.Any(x => WebDavPath.IsParentOrSame(x, entry.FullPath))) - { - // Признак операции на сервере, под которую подпадает папка - folder.Attributes |= System.IO.FileAttributes.Offline; - } - }); - - folder.ServerFilesCount ??= folder.Descendants.Count(f => f.IsFile); - folder.ServerFoldersCount ??= folder.Descendants.Count(f => !f.IsFile); - - return folder; - } - - //public static IEntry MergeData(this Folder folder, YadFolderInfoRequestData folderData, string path) - //{ - // if (folderData is null) - // throw new ArgumentNullException(nameof(folderData)); - // if (folder is null) - // throw new ArgumentNullException(nameof(folder)); - - // string diskPath = WebDavPath.Combine("/disk", path); - // var fi = folderData.Resources; - // var children = new List(); - - // children.AddRange( - // fi.Where(it => it.Type == "file") - // .Select(f => f.ToFile()) - // .ToGroupedFiles()); - // children.AddRange( - // fi.Where(it => it.Type == "dir" && - // // Пропуск элемента с информацией папки о родительской папке, - // // этот элемент добавляется в выборки, если читается - // // не всё содержимое папки, а делается только вырезка - // it.Path != diskPath) - // .Select(f => f.ToFolder())); - - // folder.Descendants = folder.Descendants.AddRange(children); - - // return folder; - //} - - public static File ToFile(this FolderInfoDataResource data) - { - var path = data.Path.Remove(0, "/disk".Length); - - var res = new File(path, data.Meta.Size ?? throw new Exception("File size is null")) - { - CreationTimeUtc = UnixTimeStampToDateTime(data.Ctime, DateTime.MinValue), - LastAccessTimeUtc = UnixTimeStampToDateTime(data.Utime, DateTime.MinValue), - LastWriteTimeUtc = UnixTimeStampToDateTime(data.Mtime, DateTime.MinValue) - }; - if (!string.IsNullOrEmpty(data.Meta.UrlShort)) - { - PublicLinkInfo item = new PublicLinkInfo("short", data.Meta.UrlShort); - res.PublicLinks.TryAdd(item.Uri.AbsoluteUri, item); - } - return res; - } - - public static File ToFile(this YadItemInfoRequestData data) - { - var path = data.Path.Remove(0, "/disk".Length); - - var res = new File(path, data.Meta.Size) - { - CreationTimeUtc = UnixTimeStampToDateTime(data.Ctime, DateTime.MinValue), - LastAccessTimeUtc = UnixTimeStampToDateTime(data.Utime, DateTime.MinValue), - LastWriteTimeUtc = UnixTimeStampToDateTime(data.Mtime, DateTime.MinValue) - //PublicLink = data.Meta.UrlShort.StartsWith(publicBaseUrl) - // ? data.Meta.UrlShort.Remove(0, publicBaseUrl.Length) - // : data.Meta.UrlShort - }; - if (!string.IsNullOrEmpty(data.Meta.UrlShort)) - { - PublicLinkInfo item = new PublicLinkInfo("short", data.Meta.UrlShort); - res.PublicLinks.TryAdd(item.Uri.AbsoluteUri, item); - } - - return res; - } - - public static Folder ToFolder(this FolderInfoDataResource resource) - { - var path = resource.Path.Remove(0, "/disk".Length); - - var res = new Folder(path) { IsChildrenLoaded = false }; - - return res; - } - - public static RenameResult ToRenameResult(this YadResponseModel data) - { - var res = new RenameResult - { - IsSuccess = data.Data.Error is null, - DateTime = DateTime.Now, - Path = data.Params.Src.Remove(0, "/disk".Length) - }; - return res; - } - - //public static ItemOperation ToItemOperation(this RenameResult data) - //{ - // var res = new ItemOperation - // { - // DateTime = data.DateTime, - // Path = data.Path - // }; - // return res; - //} - - public static RemoveResult ToRemoveResult(this YadResponseModel data) - { - var res = new RemoveResult - { - IsSuccess = true, - DateTime = DateTime.Now, - Path = data.Params.Id.Remove(0, "/disk".Length) - }; - return res; - } - - public static ItemOperation ToItemOperation(this RemoveResult data) - { - var res = new ItemOperation - { - DateTime = data.DateTime, - Path = data.Path - }; - return res; - } - - public static CreateFolderResult ToCreateFolderResult(this YadCreateFolderRequestParams data) - { - var res = new CreateFolderResult - { - IsSuccess = true, - Path = data.Id.Remove(0, "/disk".Length) - }; - return res; - } - - public static CopyResult ToCopyResult(this YadResponseModel data) - { - var res = new CopyResult - { - IsSuccess = true, - NewName = data.Params.Dst.Remove(0, "/disk".Length), - OldFullPath = data.Params.Src.Remove(0, "/disk".Length), - DateTime = DateTime.Now - }; - return res; - } - - public static CopyResult ToMoveResult(this YadResponseModel data) - { - var res = new CopyResult - { - IsSuccess = data.Data.Error is null, - NewName = data.Params.Dst.Remove(0, "/disk".Length), - OldFullPath = data.Params.Src.Remove(0, "/disk".Length), - DateTime = DateTime.Now - }; - return res; - } - - //public static ItemOperation ToItemOperation(this CopyResult data) - //{ - // var res = new ItemOperation - // { - // DateTime = data.DateTime, - // Path = data.OldFullPath - // }; - // return res; - //} - - public static PublishResult ToPublishResult(this YadResponseModel data) - { - var res = new PublishResult - { - IsSuccess = !string.IsNullOrEmpty(data.Data.ShortUrl), - Url = data.Data.ShortUrl - }; - return res; - } - - public static UnpublishResult ToUnpublishResult(this YadResponseModel data) - { - var res = new UnpublishResult - { - IsSuccess = true - }; - return res; - } - - - - private static DateTime UnixTimeStampToDateTime(double unixTimeStamp, DateTime defaultvalue) - { - try - { - // Unix timestamp is seconds past epoch - var dtDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); - dtDateTime = dtDateTime.AddSeconds(unixTimeStamp); //.ToLocalTime(); - doesn't need, clients usually convert to local time by itself - return dtDateTime; - } - catch (Exception e) - { - Logger.Error($"Error converting unixTimeStamp {unixTimeStamp} to DateTime, {e.Message}"); - return defaultvalue; - } - } - } -} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/AccountInfo.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/AccountInfo.cs deleted file mode 100644 index 7db6d595..00000000 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/AccountInfo.cs +++ /dev/null @@ -1,42 +0,0 @@ -using Newtonsoft.Json; - -namespace YaR.Clouds.Base.Repos.YandexDisk.YadWebV2.Models -{ - class YadAccountInfoPostModel : YadPostModel - { - public YadAccountInfoPostModel() - { - Name = "space"; - } - } - - public class YadAccountInfoRequestData : YadModelDataBase - { - [JsonProperty("used")] - public long Used { get; set; } - - [JsonProperty("uid")] - public long Uid { get; set; } - - [JsonProperty("filesize_limit")] - public long FilesizeLimit { get; set; } - - [JsonProperty("free")] - public long Free { get; set; } - - [JsonProperty("limit")] - public long Limit { get; set; } - - [JsonProperty("trash")] - public long Trash { get; set; } - - [JsonProperty("files_count")] - public long FilesCount { get; set; } - } - - public class YadAccountInfoRequestParams - { - [JsonProperty("locale")] - public string Locale { get; set; } - } -} \ No newline at end of file diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/Base.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/Base.cs deleted file mode 100644 index f022b7f1..00000000 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/Base.cs +++ /dev/null @@ -1,120 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using Newtonsoft.Json; - -namespace YaR.Clouds.Base.Repos.YandexDisk.YadWebV2.Models -{ - class YadPostData - { - public string Sk { get; set; } - public string IdClient { get; set; } - public List Models { get; set; } = new(); - - public byte[] CreateHttpContent() - { - var keyValues = new List> - { - new("sk", Sk), - new("idClient", IdClient) - }; - - keyValues.AddRange(Models.SelectMany((model, i) => model.ToKvp(i))); - - FormUrlEncodedContent z = new FormUrlEncodedContent(keyValues); - return z.ReadAsByteArrayAsync().Result; - } - } - - abstract class YadPostModel - { - public virtual IEnumerable> ToKvp(int index) - { - yield return new KeyValuePair($"_model.{index}", Name); - } - - public string Name { get; set; } - } - - - - - - - - public class YadResponseResult - { - [JsonProperty("uid")] - public long Uid { get; set; } - - [JsonProperty("login")] - public string Login { get; set; } - - [JsonProperty("sk")] - public string Sk { get; set; } - - [JsonProperty("version")] - public string Version { get; set; } - - [JsonProperty("models")] - public List Models { get; set; } - } - - public class YadResponseModel - { - [JsonProperty("model")] - public string ModelName { get; set; } - - [JsonProperty("error")] - public YadResponseError Error { get; set; } - } - - - public class YadResponseModel : YadResponseModel - //where TData : YadModelDataBase - { - [JsonProperty("params")] - public TParams Params { get; set; } - - [JsonProperty("data")] - public TData Data { get; set; } - } - - public class YadResponseError - { - [JsonProperty("id")] - public string Id { get; set; } - - [JsonProperty("message")] - public string Message { get; set; } - } - - - - public class YadModelDataBase - { - [JsonProperty("error")] - public YadModelDataError Error { get; set; } - } - - public class YadModelDataError - { - [JsonProperty("id")] - public string Id { get; set; } - - [JsonProperty("message")] - public string Message { get; set; } - - [JsonProperty("body")] - public YadModelDataErrorBody Body { get; set; } - } - - public class YadModelDataErrorBody - { - [JsonProperty("code")] - public long Code { get; set; } - - [JsonProperty("title")] - public string Title { get; set; } - } -} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/CleanTrash.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/CleanTrash.cs deleted file mode 100644 index 0f26dfb1..00000000 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/CleanTrash.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Newtonsoft.Json; - -namespace YaR.Clouds.Base.Repos.YandexDisk.YadWebV2.Models -{ - class YadCleanTrashPostModel : YadPostModel - { - public YadCleanTrashPostModel() - { - Name = "do-clean-trash"; - } - } - - internal class YadCleanTrashData : YadModelDataBase - { - [JsonProperty("at_version")] - public long AtVersion { get; set; } - - [JsonProperty("oid")] - public string Oid { get; set; } - - [JsonProperty("type")] - public string Type { get; set; } - } - - internal class YadCleanTrashParams - { - } -} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/Copy.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/Copy.cs deleted file mode 100644 index d3780752..00000000 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/Copy.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System.Collections.Generic; -using Newtonsoft.Json; - -namespace YaR.Clouds.Base.Repos.YandexDisk.YadWebV2.Models -{ - class YadCopyPostModel : YadPostModel - { - public YadCopyPostModel(string sourcePath, string destPath, bool force = true) - { - Name = "do-resource-copy"; - Source = sourcePath; - Destination = destPath; - Force = force; - } - - public string Source { get; set; } - public string Destination { get; set; } - public bool Force { get; set; } - - public override IEnumerable> ToKvp(int index) - { - foreach (var pair in base.ToKvp(index)) - yield return pair; - - yield return new KeyValuePair($"src.{index}", WebDavPath.Combine("/disk", Source)); - yield return new KeyValuePair($"dst.{index}", WebDavPath.Combine("/disk", Destination)); - yield return new KeyValuePair($"force.{index}", Force ? "1" : "0"); - } - } - - class YadCopyRequestData : YadModelDataBase - { - [JsonProperty("at_version")] - public long AtVersion { get; set; } - - [JsonProperty("oid")] - public string Oid { get; set; } - - [JsonProperty("type")] - public string Type { get; set; } - } - - class YadCopyRequestParams - { - [JsonProperty("src")] - public string Src { get; set; } - - [JsonProperty("dst")] - public string Dst { get; set; } - - [JsonProperty("force")] - public long Force { get; set; } - } -} \ No newline at end of file diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/CreateFolder.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/CreateFolder.cs deleted file mode 100644 index 5a180c22..00000000 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/CreateFolder.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System.Collections.Generic; -using Newtonsoft.Json; - -namespace YaR.Clouds.Base.Repos.YandexDisk.YadWebV2.Models -{ - class YadCreateFolderPostModel : YadPostModel - { - public YadCreateFolderPostModel(string path, bool force = true) - { - Name = "do-resource-create-folder"; - Path = path; - Force = force; - } - - public string Path { get; set; } - public bool Force { get; set; } - - public override IEnumerable> ToKvp(int index) - { - foreach (var pair in base.ToKvp(index)) - yield return pair; - - yield return new KeyValuePair($"id.{index}", WebDavPath.Combine("/disk", Path)); - yield return new KeyValuePair($"force.{index}", Force ? "1" : "0"); - } - } - - class YadCreateFolderRequestData : YadModelDataBase - { - } - - class YadCreateFolderRequestParams - { - [JsonProperty("id")] - public string Id { get; set; } - - [JsonProperty("force")] - public long Force { get; set; } - } -} \ No newline at end of file diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/Delete.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/Delete.cs deleted file mode 100644 index 75748a20..00000000 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/Delete.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System.Collections.Generic; -using Newtonsoft.Json; - -namespace YaR.Clouds.Base.Repos.YandexDisk.YadWebV2.Models -{ - class YadDeletePostModel : YadPostModel - { - public YadDeletePostModel(string path) - { - Name = "do-resource-delete"; - Path = path; - } - - public string Path { get; set; } - - public override IEnumerable> ToKvp(int index) - { - foreach (var pair in base.ToKvp(index)) - yield return pair; - - yield return new KeyValuePair($"id.{index}", WebDavPath.Combine("/disk", Path)); - } - } - - public class YadDeleteRequestData : YadModelDataBase - { - [JsonProperty("at_version")] - public long AtVersion { get; set; } - - [JsonProperty("oid")] - public string Oid { get; set; } - - [JsonProperty("type")] - public string Type { get; set; } - } - - public class YadDeleteRequestParams - { - [JsonProperty("id")] - public string Id { get; set; } - } -} \ No newline at end of file diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/FolderInfo.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/FolderInfo.cs deleted file mode 100644 index 94b8d690..00000000 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/FolderInfo.cs +++ /dev/null @@ -1,236 +0,0 @@ -using System.Collections.Generic; -using Newtonsoft.Json; - -namespace YaR.Clouds.Base.Repos.YandexDisk.YadWebV2.Models -{ - class YadFolderInfoPostModel : YadPostModel - { - private readonly string _pathPrefix; - - public YadFolderInfoPostModel(string path, string pathPrefix = "/disk") - { - _pathPrefix = pathPrefix; - Name = "resources"; - Path = path; - } - - public string Path { get; set; } - /// - /// 0 - сортировка возвращаемого результата по убыванию. - /// 1 - сортировка возвращаемого результата по возрастанию. - /// - public int Order { get; set; } = 1; - /// - /// Поле сортировки возвращаемого результата. - /// name - по названию файла. - /// mtime - по времени изменения. - /// size - по размеру. - /// type - по типу. - /// - public string SortBy { get; set; } = "name"; - public int Offset { get; set; } = 0; - public int Amount { get; set; } = int.MaxValue; - public bool WithParent { get; set; } = false; - - public override IEnumerable> ToKvp(int index) - { - foreach (var pair in base.ToKvp(index)) - yield return pair; - - yield return new KeyValuePair($"idContext.{index}", WebDavPath.Combine(_pathPrefix, Path)); - - //if (Path == "/Camera") - //{ - // yield return new KeyValuePair($"idContext.{index}", "/photounlim/"); - //} - //else - //{ - // yield return new KeyValuePair($"idContext.{index}", WebDavPath.Combine(_pathPrefix, Path)); - //} - - yield return new KeyValuePair($"order.{index}", Order.ToString()); - yield return new KeyValuePair($"sort.{index}", SortBy); - if (Offset > 0 || Amount < int.MaxValue) - { - yield return new KeyValuePair($"offset.{index}", Offset.ToString()); - yield return new KeyValuePair($"amount.{index}", Amount.ToString()); - } - if (WithParent) - yield return new KeyValuePair($"withParent.{index}", "1"); - } - } - - internal class YadFolderInfoRequestData : YadModelDataBase - { - [JsonProperty("resources")] - public List Resources { get; set; } - } - - internal class FolderInfoDataResource - { - [JsonProperty("ctime")] - public long Ctime { get; set; } - - [JsonProperty("meta")] - public Meta Meta { get; set; } - - [JsonProperty("mtime")] - public ulong Mtime { get; set; } - - [JsonProperty("path")] - public string Path { get; set; } - - [JsonProperty("utime")] - public long Utime { get; set; } - - [JsonProperty("type")] - public string Type { get; set; } - - [JsonProperty("id")] - public string Id { get; set; } - - [JsonProperty("name")] - public string Name { get; set; } - - [JsonProperty("etime", NullValueHandling = NullValueHandling.Ignore)] - public long? Etime { get; set; } - } - - class Meta - { - [JsonProperty("file_id")] - public string FileId { get; set; } - - [JsonProperty("resource_id")] - public string ResourceId { get; set; } - - [JsonProperty("mimetype", NullValueHandling = NullValueHandling.Ignore)] - public string Mimetype { get; set; } - - [JsonProperty("drweb", NullValueHandling = NullValueHandling.Ignore)] - public long? Drweb { get; set; } - - [JsonProperty("sizes", NullValueHandling = NullValueHandling.Ignore)] - public List Sizes { get; set; } - - [JsonProperty("mediatype", NullValueHandling = NullValueHandling.Ignore)] - public string MediaType { get; set; } - - [JsonProperty("etime", NullValueHandling = NullValueHandling.Ignore)] - public long? Etime { get; set; } - - [JsonProperty("versioning_status", NullValueHandling = NullValueHandling.Ignore)] - public string VersioningStatus { get; set; } - - [JsonProperty("size", NullValueHandling = NullValueHandling.Ignore)] - public long? Size { get; set; } - - [JsonProperty("video_info", NullValueHandling = NullValueHandling.Ignore)] - public VideoInfo VideoInfo { get; set; } - - [JsonProperty("short_url")] - public string UrlShort { get; set; } - - [JsonProperty("total_results_count")] - public int? TotalEntityCount { get; set; } - } - - class Size - { - [JsonProperty("url")] - public string Url { get; set; } - - [JsonProperty("name")] - public string Name { get; set; } - } - - class VideoInfo - { - [JsonProperty("format")] - public string Format { get; set; } - - [JsonProperty("creationTime")] - public long CreationTime { get; set; } - - [JsonProperty("streams")] - public List Streams { get; set; } - - [JsonProperty("startTime")] - public long StartTime { get; set; } - - [JsonProperty("duration")] - public long Duration { get; set; } - - [JsonProperty("bitRate")] - public long BitRate { get; set; } - } - - class Stream - { - [JsonProperty("type")] - public string Type { get; set; } - - [JsonProperty("frameRate", NullValueHandling = NullValueHandling.Ignore)] - public long? FrameRate { get; set; } - - [JsonProperty("displayAspectRatio", NullValueHandling = NullValueHandling.Ignore)] - public DisplayAspectRatio DisplayAspectRatio { get; set; } - - [JsonProperty("codec")] - public string Codec { get; set; } - - [JsonProperty("id")] - public long Id { get; set; } - - [JsonProperty("bitRate")] - public long BitRate { get; set; } - - [JsonProperty("dimension", NullValueHandling = NullValueHandling.Ignore)] - public Dimension Dimension { get; set; } - - [JsonProperty("channelsCount", NullValueHandling = NullValueHandling.Ignore)] - public long? ChannelsCount { get; set; } - - [JsonProperty("stereo", NullValueHandling = NullValueHandling.Ignore)] - public bool? Stereo { get; set; } - - [JsonProperty("sampleFrequency", NullValueHandling = NullValueHandling.Ignore)] - public long? SampleFrequency { get; set; } - } - - class Dimension - { - [JsonProperty("width")] - public long Width { get; set; } - - [JsonProperty("height")] - public long Height { get; set; } - } - - class DisplayAspectRatio - { - [JsonProperty("denom")] - public long Denom { get; set; } - - [JsonProperty("num")] - public long Num { get; set; } - } - - internal class YadFolderInfoRequestParams - { - [JsonProperty("idContext")] - public string IdContext { get; set; } - - [JsonProperty("order")] - public long Order { get; set; } - - [JsonProperty("sort")] - public string Sort { get; set; } - - [JsonProperty("offset")] - public long Offset { get; set; } - - [JsonProperty("amount")] - public long Amount { get; set; } - } -} \ No newline at end of file diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/GetResourceUploadUrl.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/GetResourceUploadUrl.cs deleted file mode 100644 index 6dcbb6d9..00000000 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/GetResourceUploadUrl.cs +++ /dev/null @@ -1,76 +0,0 @@ -using System.Collections.Generic; -using Newtonsoft.Json; - -namespace YaR.Clouds.Base.Repos.YandexDisk.YadWebV2.Models -{ - class YadGetResourceUploadUrlPostModel : YadPostModel - { - public YadGetResourceUploadUrlPostModel(string path, long size, string hashSha256, string hashMd5, bool force = true) - { - Name = "do-resource-upload-url"; - Destination = path; - Size = size; - Force = force; - Sha256 = hashSha256; - Md5 = hashMd5; - } - - public string Destination { get; set; } - public bool Force { get; set; } - public long Size { get; set; } - public string Md5 { get; set; } - public string Sha256 { get; set; } - - public override IEnumerable> ToKvp(int index) - { - foreach (var pair in base.ToKvp(index)) - yield return pair; - - yield return new KeyValuePair($"dst.{index}", WebDavPath.Combine("/disk", Destination)); - yield return new KeyValuePair($"force.{index}", Force ? "1" : "0"); - yield return new KeyValuePair($"size.{index}", Size.ToString()); - yield return new KeyValuePair($"md5.{index}", Md5); - yield return new KeyValuePair($"sha256.{index}", Sha256); - } - } - - internal class ResourceUploadUrlData : YadModelDataBase - { - // upload params - [JsonProperty("at_version")] - public long AtVersion { get; set; } - - [JsonProperty("upload_url")] - public string UploadUrl { get; set; } - - [JsonProperty("type")] - public string Type { get; set; } - - [JsonProperty("oid")] - public string Oid { get; set; } - - /// - /// or file exists in cloud, generally = "hardlinked" - /// - [JsonProperty("status")] - public string Status { get; set; } - } - - internal class ResourceUploadUrlParams - { - [JsonProperty("dst")] - public string Dst { get; set; } - - [JsonProperty("force")] - public long Force { get; set; } - - [JsonProperty("size")] - public long Size { get; set; } - - [JsonProperty("md5")] - public string Md5 { get; set; } - - [JsonProperty("sha256")] - public string Sha256 { get; set; } - } -} \ No newline at end of file diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/GetResourceUrl.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/GetResourceUrl.cs deleted file mode 100644 index 184d6549..00000000 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/GetResourceUrl.cs +++ /dev/null @@ -1,43 +0,0 @@ -using System.Collections.Generic; -using Newtonsoft.Json; - -namespace YaR.Clouds.Base.Repos.YandexDisk.YadWebV2.Models -{ - class YadGetResourceUrlPostModel : YadPostModel - { - public YadGetResourceUrlPostModel(string path) - { - Name = "do-get-resource-url"; - Path = path; - } - - public string Path { get; set; } - - public override IEnumerable> ToKvp(int index) - { - foreach (var pair in base.ToKvp(index)) - yield return pair; - - //yield return new KeyValuePair($"id.{index}", WebDavPath.Combine("/disk", Path)); - // 08.07.2023 в браузере при скачивании: "idResource.0" - yield return new KeyValuePair($"idResource.{index}", WebDavPath.Combine("/disk", Path)); - } - } - - internal class ResourceUrlData : YadModelDataBase - { - [JsonProperty("digest")] - public string Digest { get; set; } - - [JsonProperty("file")] - public string File { get; set; } - } - - internal class ResourceUrlParams - { - //[JsonProperty("id")] - // 08.07.2023 в браузере при скачивании: "idResource" - [JsonProperty("idResource")] - public string Id { get; set; } - } -} \ No newline at end of file diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/ItemInfo.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/ItemInfo.cs deleted file mode 100644 index 5cf3ce0a..00000000 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/ItemInfo.cs +++ /dev/null @@ -1,96 +0,0 @@ -using System.Collections.Generic; -using Newtonsoft.Json; - -namespace YaR.Clouds.Base.Repos.YandexDisk.YadWebV2.Models -{ - class YadItemInfoPostModel : YadPostModel - { - private readonly string _prefix; - - public YadItemInfoPostModel(string path, string prefix = "/disk") - { - _prefix = prefix; - Name = "resource"; - Path = path; - } - - public string Path { get; set; } - - public override IEnumerable> ToKvp(int index) - { - foreach (var pair in base.ToKvp(index)) - yield return pair; - - yield return new KeyValuePair($"id.{index}", WebDavPath.Combine(_prefix, Path)); - - //if (Path == "/Camera") - //{ - // yield return new KeyValuePair($"id.{index}", "/photounlim/"); - //} - //else - //{ - // yield return new KeyValuePair($"id.{index}", WebDavPath.Combine("/disk/", Path)); - //} - } - } - - class YadItemInfoRequestData : YadModelDataBase - { - [JsonProperty("ctime")] - public long Ctime { get; set; } - - [JsonProperty("meta")] - public YadItemInfoRequestMeta Meta { get; set; } - - [JsonProperty("mtime")] - public ulong Mtime { get; set; } - - [JsonProperty("path")] - public string Path { get; set; } - - [JsonProperty("utime")] - public long Utime { get; set; } - - [JsonProperty("type")] - public string Type { get; set; } - - [JsonProperty("id")] - public string Id { get; set; } - - [JsonProperty("name")] - public string Name { get; set; } - } - - class YadItemInfoRequestMeta - { - [JsonProperty("mimetype")] - public string Mimetype { get; set; } - - [JsonProperty("drweb")] - public long Drweb { get; set; } - - [JsonProperty("resource_id")] - public string ResourceId { get; set; } - - [JsonProperty("mediatype")] - public string Mediatype { get; set; } - - [JsonProperty("file_id")] - public string FileId { get; set; } - - [JsonProperty("versioning_status")] - public string VersioningStatus { get; set; } - - [JsonProperty("size")] - public long Size { get; set; } - - [JsonProperty("short_url")] - public string UrlShort { get; set; } - } - - class YadItemInfoRequestParams - { - [JsonProperty("id")] - public string Id { get; set; } - } -} \ No newline at end of file diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/KnownYadModelConverter.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/KnownYadModelConverter.cs deleted file mode 100644 index 59ee2f95..00000000 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/KnownYadModelConverter.cs +++ /dev/null @@ -1,40 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using Newtonsoft.Json; -using Newtonsoft.Json.Linq; - -namespace YaR.Clouds.Base.Repos.YandexDisk.YadWebV2.Models -{ - class KnownYadModelConverter : JsonConverter> - { - private readonly List _createdModels; - - public KnownYadModelConverter(List createdModels) - { - _createdModels = createdModels; - } - - public override List ReadJson(JsonReader reader, Type objectType, List existingValue, bool hasExistingValue, JsonSerializer serializer) - { - var token = JToken.Load(reader); - - var children = token.Children().ToList(); - for (int i = 0; i < children.Count; i++) - { - var chToken = children[i]; - var resItem = _createdModels[i]; - serializer.Populate(chToken.CreateReader(), resItem); - } - - return null; - } - - public override bool CanWrite => false; - - public override void WriteJson(JsonWriter writer, List value, JsonSerializer serializer) - { - throw new NotImplementedException(); - } - } -} \ No newline at end of file diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/Media/Albums.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/Media/Albums.cs deleted file mode 100644 index d92b6f2b..00000000 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/Media/Albums.cs +++ /dev/null @@ -1,205 +0,0 @@ -using Newtonsoft.Json; - -namespace YaR.Clouds.Base.Repos.YandexDisk.YadWebV2.Models.Media -{ - class YadAlbumsPostModel : YadPostModel - { - public YadAlbumsPostModel() - { - Name = "albums"; - } - - //public override IEnumerable> ToKvp(int index) - //{ - // foreach (var pair in base.ToKvp(index)) - // yield return pair; - //} - } - - internal class YadAlbumsRequestParams - { - } - - internal class YadAlbumsRequestData - { - [JsonProperty("album_type")] - public string AlbumType { get; set; } - - [JsonProperty("uid")] - public long Uid { get; set; } - - [JsonProperty("mtime")] - public long Mtime { get; set; } - - [JsonProperty("id")] - public string Id { get; set; } - - [JsonProperty("layout")] - public string Layout { get; set; } - - [JsonProperty("title")] - public string Title { get; set; } - - [JsonProperty("social_cover_url")] - public string SocialCoverUrl { get; set; } - - [JsonProperty("public")] - public FPublic Public { get; set; } - - [JsonProperty("album_items_sorting")] - public string AlbumItemsSorting { get; set; } - - [JsonProperty("is_empty")] - public bool IsEmpty { get; set; } - - [JsonProperty("user")] - public FUser User { get; set; } - - [JsonProperty("is_public")] - public bool IsPublic { get; set; } - - [JsonProperty("is_blocked")] - public bool IsBlocked { get; set; } - - [JsonProperty("fotki_album_id")] - public object FotkiAlbumId { get; set; } - - [JsonProperty("ctime")] - public long Ctime { get; set; } - - [JsonProperty("cover")] - public Cover Cover { get; set; } - - [JsonProperty("is_desc_sorting")] - public bool IsDescSorting { get; set; } - - [JsonProperty("social_cover_stid")] - public string SocialCoverStid { get; set; } - } - - internal class Cover - { - [JsonProperty("obj_type")] - public string ObjType { get; set; } - - [JsonProperty("object")] - public Object Object { get; set; } - - [JsonProperty("uid")] - public string Uid { get; set; } - - [JsonProperty("album_id")] - public string AlbumId { get; set; } - - [JsonProperty("obj_id")] - public string ObjId { get; set; } - - [JsonProperty("order_index")] - public double OrderIndex { get; set; } - - [JsonProperty("id")] - public string Id { get; set; } - } - - internal class Object - { - [JsonProperty("ctime")] - public long Ctime { get; set; } - - [JsonProperty("etime")] - public long Etime { get; set; } - - [JsonProperty("meta")] - public FMeta Meta { get; set; } - - [JsonProperty("mtime")] - public long Mtime { get; set; } - - [JsonProperty("path")] - public string Path { get; set; } - - [JsonProperty("utime")] - public long Utime { get; set; } - - [JsonProperty("type")] - public string Type { get; set; } - - [JsonProperty("id")] - public string Id { get; set; } - - [JsonProperty("name")] - public string Name { get; set; } - } - - internal class FMeta - { - [JsonProperty("sizes")] - public FSize[] Sizes { get; set; } - - [JsonProperty("mediatype")] - public string Mediatype { get; set; } - - [JsonProperty("etime")] - public long Etime { get; set; } - - [JsonProperty("storage_type")] - public string StorageType { get; set; } - - [JsonProperty("file_id")] - public string FileId { get; set; } - - [JsonProperty("size")] - public long Size { get; set; } - } - - internal class FSize - { - [JsonProperty("url")] - public string Url { get; set; } - - [JsonProperty("name")] - public string Name { get; set; } - } - - internal class FPublic - { - [JsonProperty("public_key")] - public string PublicKey { get; set; } - - [JsonProperty("public_url")] - public string PublicUrl { get; set; } - - [JsonProperty("views_count")] - public long ViewsCount { get; set; } - - [JsonProperty("short_url")] - public string ShortUrl { get; set; } - } - - internal class FUser - { - [JsonProperty("username")] - public string Username { get; set; } - - [JsonProperty("public_name")] - public string PublicName { get; set; } - - [JsonProperty("display_name")] - public string DisplayName { get; set; } - - [JsonProperty("uid")] - public string Uid { get; set; } - - [JsonProperty("locale")] - public string Locale { get; set; } - - [JsonProperty("login")] - public string Login { get; set; } - - [JsonProperty("paid")] - public long Paid { get; set; } - - [JsonProperty("advertising_enabled")] - public long AdvertisingEnabled { get; set; } - } -} \ No newline at end of file diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/Media/GetAlbumsSlices.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/Media/GetAlbumsSlices.cs deleted file mode 100644 index b3576eae..00000000 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/Media/GetAlbumsSlices.cs +++ /dev/null @@ -1,53 +0,0 @@ -using Newtonsoft.Json; - -namespace YaR.Clouds.Base.Repos.YandexDisk.YadWebV2.Models.Media -{ - class YadGetAlbumsSlicesPostModel : YadPostModel - { - public YadGetAlbumsSlicesPostModel() - { - Name = "getAlbumsSlices"; - } - - //public override IEnumerable> ToKvp(int index) - //{ - // foreach (var pair in base.ToKvp(index)) - // yield return pair; - //} - } - - internal class YadGetAlbumsSlicesRequestParams - { - } - - - internal class YadGetAlbumsSlicesRequestData : YadModelDataBase - { - [JsonProperty("albums")] - public GAlbums Albums { get; set; } - } - - internal class GAlbums - { - [JsonProperty("videos")] - public GAlbum Videos { get; set; } - - [JsonProperty("photounlim")] - public GAlbum Photounlim { get; set; } - - [JsonProperty("camera")] - public GAlbum Camera { get; set; } - } - - internal class GAlbum - { - [JsonProperty("id")] - public string Id { get; set; } - - [JsonProperty("count")] - public long Count { get; set; } - - [JsonProperty("preview")] - public string Preview { get; set; } - } -} \ No newline at end of file diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/Media/GetClustersWithResources.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/Media/GetClustersWithResources.cs deleted file mode 100644 index 05bbef0c..00000000 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/Media/GetClustersWithResources.cs +++ /dev/null @@ -1,255 +0,0 @@ -using System; -using System.Collections.Generic; -using Newtonsoft.Json; -using YaR.Clouds.Extensions; - -namespace YaR.Clouds.Base.Repos.YandexDisk.YadWebV2.Models.Media -{ - class YadGetClustersWithResourcesPostModel : YadPostModel - { - public string PhotoSliceId { get; } - public DateTime From { get; } - public DateTime Till { get; } - public YadMediaFilter? Filter { get; } - - public YadGetClustersWithResourcesPostModel(string photoSliceId, DateTime from, DateTime till, YadMediaFilter? filter) - { - PhotoSliceId = photoSliceId; - From = from; - Till = till; - Filter = filter; - Name = "getClustersWithResources"; - } - - public override IEnumerable> ToKvp(int index) - { - foreach (var pair in base.ToKvp(index)) - yield return pair; - - yield return new KeyValuePair($"photosliceId.{index}", PhotoSliceId); - if (Filter != null) - yield return new KeyValuePair($"filter.{index}", Filter.Value.ToString().ToLower()); - - string clusters = $"{{\"{From.ToUnix().ToString()}_{Till.ToUnix().ToString()}\":{{\"range\":[0,0]}}}}"; - yield return new KeyValuePair($"clusters.{index}", clusters); - } - } - - //------------------------------------------------------------------------------------------------------------------------------- - - internal class YadGetClustersWithResourcesRequestData : YadModelDataBase - { - [JsonProperty("clusters")] - public Clusters Clusters { get; set; } - - [JsonProperty("resources")] - public Zresources Resources { get; set; } - } - - internal class Clusters - { - [JsonProperty("fetched")] - public ClustersFetched[] Fetched { get; set; } - - [JsonProperty("missing")] - public object[] Missing { get; set; } - } - - internal class ClustersFetched - { - [JsonProperty("id")] - public string Id { get; set; } - - [JsonProperty("size")] - public long Size { get; set; } - - [JsonProperty("items")] - public Zitem[] Items { get; set; } - - [JsonProperty("albums")] - public Albums Albums { get; set; } - } - - internal class Albums - { - [JsonProperty("photounlim")] - public long Photounlim { get; set; } - - [JsonProperty("camera")] - public long Camera { get; set; } - - [JsonProperty("videos", NullValueHandling = NullValueHandling.Ignore)] - public long? Videos { get; set; } - } - - internal class Zitem - { - [JsonProperty("id")] - public string Id { get; set; } - - [JsonProperty("itemId")] - public string ItemId { get; set; } - - [JsonProperty("width", NullValueHandling = NullValueHandling.Ignore)] - public long? Width { get; set; } - - [JsonProperty("height", NullValueHandling = NullValueHandling.Ignore)] - public long? Height { get; set; } - - [JsonProperty("beauty", NullValueHandling = NullValueHandling.Ignore)] - public double? Beauty { get; set; } - - [JsonProperty("albums")] - public YadMediaFilter[] Albums { get; set; } - } - - internal class Zresources - { - [JsonProperty("fetched")] - public ResourcesFetched[] Fetched { get; set; } - - [JsonProperty("missing")] - public object[] Missing { get; set; } - } - internal class ResourcesFetched - { - [JsonProperty("clusterId")] - public string ClusterId { get; set; } - - [JsonProperty("name")] - public string Name { get; set; } - - [JsonProperty("etime")] - public long Etime { get; set; } - - [JsonProperty("meta")] - public Zmeta Meta { get; set; } - - [JsonProperty("__type")] - public string Type { get; set; } - - [JsonProperty("mtime")] - public long Mtime { get; set; } - - [JsonProperty("path")] - public string Path { get; set; } - - [JsonProperty("utime")] - public long Utime { get; set; } - - [JsonProperty("type")] - public string FetchedType { get; set; } - - [JsonProperty("id")] - public string Id { get; set; } - - [JsonProperty("ctime")] - public long Ctime { get; set; } - } - - internal class Zmeta - { - [JsonProperty("mimetype")] - public string Mimetype { get; set; } - - [JsonProperty("drweb")] - public long Drweb { get; set; } - - [JsonProperty("sizes")] - public Size[] Sizes { get; set; } - - [JsonProperty("resource_id")] - public string ResourceId { get; set; } - - [JsonProperty("mediatype")] - public string Mediatype { get; set; } - - [JsonProperty("etime")] - public long Etime { get; set; } - - [JsonProperty("storage_type")] - public YadMediaFilter StorageType { get; set; } - - [JsonProperty("file_id")] - public string FileId { get; set; } - - [JsonProperty("photoslice_time")] - public long PhotosliceTime { get; set; } - - [JsonProperty("size")] - public long Size { get; set; } - - [JsonProperty("short_url", NullValueHandling = NullValueHandling.Ignore)] - public Uri ShortUrl { get; set; } - - [JsonProperty("public", NullValueHandling = NullValueHandling.Ignore)] - public long? Public { get; set; } - - [JsonProperty("video_info", NullValueHandling = NullValueHandling.Ignore)] - public VideoInfo VideoInfo { get; set; } - } - - //----------------------------------------------------------------------------------------------------------------------------------- - internal class YadGetClustersWithResourcesRequestParams - { - [JsonProperty("photosliceId")] - public string PhotosliceId { get; set; } - - [JsonProperty("clusters")] - public string Clusters { get; set; } - - [JsonProperty("filter")] - [JsonConverter(typeof(FilterConverter))] - public YadMediaFilter Filter { get; set; } - } - - internal enum YadMediaFilter { Camera, Photounlim, Videos } - - internal class FilterConverter : JsonConverter - { - public override bool CanConvert(Type t) => t == typeof(YadMediaFilter) || t == typeof(YadMediaFilter?); - - public override object ReadJson(JsonReader reader, Type t, object existingValue, JsonSerializer serializer) - { - if (reader.TokenType == JsonToken.Null) return null; - var value = serializer.Deserialize(reader); - - return value switch - { - "camera" => YadMediaFilter.Camera, - "photounlim" => YadMediaFilter.Photounlim, - "videos" => YadMediaFilter.Videos, - _ => throw new Exception("Cannot unmarshal type Filter") - }; - } - - public override void WriteJson(JsonWriter writer, object untypedValue, JsonSerializer serializer) - { - if (untypedValue == null) - { - serializer.Serialize(writer, null); - return; - } - var value = (YadMediaFilter)untypedValue; - switch (value) - { - case YadMediaFilter.Camera: - serializer.Serialize(writer, "camera"); - return; - case YadMediaFilter.Photounlim: - serializer.Serialize(writer, "photounlim"); - return; - case YadMediaFilter.Videos: - serializer.Serialize(writer, "videos"); - return; - } - throw new Exception("Cannot marshal type YadMediaFilter"); - } - - //public static readonly FilterConverter Singleton = new FilterConverter(); - } - //------------------------------ - - - -} \ No newline at end of file diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/Media/InitSnapshot.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/Media/InitSnapshot.cs deleted file mode 100644 index f81d318f..00000000 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/Media/InitSnapshot.cs +++ /dev/null @@ -1,40 +0,0 @@ -using Newtonsoft.Json; - -namespace YaR.Clouds.Base.Repos.YandexDisk.YadWebV2.Models.Media -{ - class YadInitSnapshotPostModel : YadPostModel - { - public YadInitSnapshotPostModel() - { - Name = "initSnapshot"; - } - - //public override IEnumerable> ToKvp(int index) - //{ - // foreach (var pair in base.ToKvp(index)) - // yield return pair; - //} - } - - internal class YadInitSnapshotRequestData : YadModelDataBase - { - [JsonProperty("photoslice_id")] - public string PhotosliceId { get; set; } - - [JsonProperty("href")] - public string Href { get; set; } - - [JsonProperty("revision")] - public long Revision { get; set; } - - [JsonProperty("method")] - public string Method { get; set; } - - [JsonProperty("templated")] - public bool Templated { get; set; } - } - - internal class YadInitSnapshotRequestParams - { - } -} \ No newline at end of file diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/Move.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/Move.cs deleted file mode 100644 index 5dfeee5e..00000000 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/Move.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System.Collections.Generic; -using Newtonsoft.Json; - -namespace YaR.Clouds.Base.Repos.YandexDisk.YadWebV2.Models -{ - class YadMovePostModel : YadPostModel - { - public YadMovePostModel(string sourcePath, string destPath, bool force = true) - { - Name = "do-resource-move"; - Source = sourcePath; - Destination = destPath; - Force = force; - } - - public string Source { get; set; } - public string Destination { get; set; } - public bool Force { get; set; } - - public override IEnumerable> ToKvp(int index) - { - foreach (var pair in base.ToKvp(index)) - yield return pair; - - yield return new KeyValuePair($"src.{index}", WebDavPath.Combine("/disk", Source)); - yield return new KeyValuePair($"dst.{index}", WebDavPath.Combine("/disk", Destination)); - yield return new KeyValuePair($"force.{index}", Force ? "1" : "0"); - } - } - - internal class YadMoveRequestData : YadModelDataBase - { - [JsonProperty("at_version")] - public long AtVersion { get; set; } - - [JsonProperty("oid")] - public string Oid { get; set; } - - [JsonProperty("type")] - public string Type { get; set; } - } - - internal class YadMoveRequestParams - { - [JsonProperty("src")] - public string Src { get; set; } - - [JsonProperty("dst")] - public string Dst { get; set; } - - [JsonProperty("force")] - public long Force { get; set; } - } -} \ No newline at end of file diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/OperationStatus.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/OperationStatus.cs deleted file mode 100644 index 0323fd6c..00000000 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/OperationStatus.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.Collections.Generic; -using Newtonsoft.Json; - -namespace YaR.Clouds.Base.Repos.YandexDisk.YadWebV2.Models -{ - internal class YadOperationStatusPostModel : YadPostModel - { - public YadOperationStatusPostModel(string oid) - { - Name = "do-status-operation"; - Oid = oid; - } - - public string Oid { get; set; } - - public override IEnumerable> ToKvp(int index) - { - foreach (var pair in base.ToKvp(index)) - yield return pair; - - yield return new KeyValuePair($"oid.{index}", Oid); - } - } - - - internal class YadOperationStatusData : YadModelDataBase - { - [JsonProperty("status")] - public string Status { get; set; } - - [JsonProperty("type")] - public string Type { get; set; } - - [JsonProperty("state")] - public string State { get; set; } - - [JsonProperty("at_version")] - public long AtVersion { get; set; } - } - - internal class YadOperationStatusParams - { - [JsonProperty("oid")] - public string Oid { get; set; } - } -} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/Publish.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/Publish.cs deleted file mode 100644 index 557e25d5..00000000 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/Publish.cs +++ /dev/null @@ -1,55 +0,0 @@ -using System; -using System.Collections.Generic; -using Newtonsoft.Json; - -namespace YaR.Clouds.Base.Repos.YandexDisk.YadWebV2.Models -{ - class YadPublishPostModel : YadPostModel - { - public YadPublishPostModel(string path, bool reverse) - { - Name = "do-resource-publish"; - Path = path; - Reverse = reverse; - } - - public string Path { get; set; } - public bool Reverse { get; set; } - - public override IEnumerable> ToKvp(int index) - { - foreach (var pair in base.ToKvp(index)) - yield return pair; - - yield return new KeyValuePair($"id.{index}", WebDavPath.Combine("/disk/", Path)); - yield return new KeyValuePair($"reverse.{index}", Reverse ? "true" : "false"); - } - } - - public class YadPublishRequestData : YadModelDataBase - { - [JsonProperty("url")] - public string Url { get; set; } - - [JsonProperty("short_url")] - public string ShortUrl { get; set; } - - [JsonProperty("hash")] - public string Hash { get; set; } - - [JsonProperty("short_url_named")] - public Uri ShortUrlNamed { get; set; } - } - - public class YadPublishRequestParams - { - [JsonProperty("id")] - public string Id { get; set; } - - [JsonProperty("reverse")] - public bool Reverse { get; set; } - - [JsonProperty("type")] - public string Type { get; set; } - } -} \ No newline at end of file diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/YadResourceStatsPostModel.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/YadResourceStatsPostModel.cs deleted file mode 100644 index bb0654dc..00000000 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Models/YadResourceStatsPostModel.cs +++ /dev/null @@ -1,50 +0,0 @@ -using System.Collections.Generic; -using Newtonsoft.Json; - -namespace YaR.Clouds.Base.Repos.YandexDisk.YadWebV2.Models -{ - class YadResourceStatsPostModel : YadPostModel - { - private readonly string _prefix; - - public YadResourceStatsPostModel(string path, string prefix = "/disk") - { - _prefix = prefix; - Name = "resourceStats"; - Path = path; - } - - public string Path { get; set; } - - public override IEnumerable> ToKvp(int index) - { - foreach (var pair in base.ToKvp(index)) - yield return pair; - - yield return new KeyValuePair($"path.{index}", WebDavPath.Combine(_prefix, Path)); - } - } - - - class YadResourceStatsRequestData : YadModelDataBase - { - /// - /// Здесь количество файлов, только файлов, не директорий, на всех уровнях вложенности. - /// - [JsonProperty("files_count")] - public long FilesCount { get; set; } - - [JsonProperty("path")] - public string Path { get; set; } - - [JsonProperty("size")] - public long Size { get; set; } - } - - class YadResourceStatsRequestParams - { - [JsonProperty("path")] - public string Path { get; set; } - } - -} \ No newline at end of file diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YaDCommonRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YaDCommonRequest.cs deleted file mode 100644 index 9fe969aa..00000000 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YaDCommonRequest.cs +++ /dev/null @@ -1,96 +0,0 @@ -using System.Collections.Generic; -using System.Collections.Specialized; -using System.IO; -using System.Linq; -using System.Net; -using Newtonsoft.Json; -using YaR.Clouds.Base.Repos.YandexDisk.YadWebV2.Models; -using YaR.Clouds.Base.Requests; - -namespace YaR.Clouds.Base.Repos.YandexDisk.YadWebV2.Requests -{ - class YaDCommonRequest : BaseRequestJson - { - private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(YaDCommonRequest)); - - private readonly YadPostData _postData = new(); - - private readonly List _outData = new(); - - private YadWebAuth YadAuth { get; } - - public YaDCommonRequest(HttpCommonSettings settings, YadWebAuth auth) : base(settings, auth) - { - YadAuth = auth; - } - - protected override HttpWebRequest CreateRequest(string baseDomain = null) - { - var request = base.CreateRequest("https://disk.yandex.ru"); - request.Referer = "https://disk.yandex.ru/client/disk"; - return request; - } - - protected override byte[] CreateHttpContent() - { - _postData.Sk = YadAuth.DiskSk; - _postData.IdClient = YadAuth.Uuid; - - return _postData.CreateHttpContent(); - } - - public YaDCommonRequest With(T model, out TOut resOUt) - where T : YadPostModel - where TOut : YadResponseModel, new() - { - _postData.Models.Add(model); - _outData.Add(resOUt = new TOut()); - - return this; - } - - protected override string RelationalUri - => string.Concat("/models/?_m=", _postData.Models - .Select(m => m.Name) - .Aggregate((current, next) => current + "," + next)); - - protected override RequestResponse DeserializeMessage( - NameValueCollection responseHeaders, System.IO.Stream stream) - { - using var sr = new StreamReader(stream); - - string text = sr.ReadToEnd(); - //Logger.Debug(text); - - var msg = new RequestResponse - { - Ok = true, - Result = JsonConvert.DeserializeObject( - text, new KnownYadModelConverter(_outData)) - }; - - //Logger.Debug($"_postData.Sk={_postData?.Sk} | Result.sk={msg.Result?.Sk}"); - /* - * Строка sk выглядит так: "sk": "cdc3dee74a379c1adc792ef087cf8c9ba19ca9f5:1693681795" - * Правая часть содержит время после двоеточия - количество секунд, начиная с 01.01.1970. - * Обновляем sk полученным значением sk. - */ - if (!string.IsNullOrWhiteSpace(msg.Result?.Sk)) - YadAuth.DiskSk = msg.Result.Sk; - - if (msg.Result.Models != null && - msg.Result.Models.Any(m => m.Error != null)) - { - Logger.Debug(text); - } - if (_postData.Models != null && - _postData.Models.Count > 0 && - _postData.Models[0].Name == "space") - { - Logger.Warn($"Yandex has API version {msg.Result.Version}"); - } - - return msg; - } - } -} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YadAuthAccountsRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YadAuthAccountsRequest.cs deleted file mode 100644 index 003c45e9..00000000 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YadAuthAccountsRequest.cs +++ /dev/null @@ -1,60 +0,0 @@ -using System.Collections.Generic; -using System.Net; -using System.Net.Http; -using Newtonsoft.Json; -using YaR.Clouds.Base.Requests; - -namespace YaR.Clouds.Base.Repos.YandexDisk.YadWebV2.Requests -{ - class YadAuthAccountsRequest : BaseRequestJson - { - private readonly string _csrf; - - public YadAuthAccountsRequest(HttpCommonSettings settings, IAuth auth, string csrf) - : base(settings, auth) - { - _csrf = csrf; - } - - protected override string RelationalUri => "/registration-validations/auth/accounts"; - - protected override HttpWebRequest CreateRequest(string baseDomain = null) - { - var request = base.CreateRequest("https://passport.yandex.ru"); - return request; - } - - protected override byte[] CreateHttpContent() - { - var keyValues = new List> - { - new("csrf_token", _csrf), - new("origin", "disk_landing2_web_signin_ru") - }; - var content = new FormUrlEncodedContent(keyValues); - var d = content.ReadAsByteArrayAsync().Result; - return d; - } - } - - class YadAuthAccountsRequestResult - { - public bool HasError => Status == "error" || - Accounts == null; - - [JsonProperty("status")] - public string Status { get; set; } - - [JsonProperty("csrf")] - public string Csrf { get; set; } - - [JsonProperty("accounts")] - public YadAccounts Accounts { get; set; } - } - - class YadAccounts - { - [JsonProperty("authorizedAccountsDefaultUid")] - public string AuthorizedAccountsDefaultUid { get; set; } - } -} \ No newline at end of file diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YadAuthAskV2Request.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YadAuthAskV2Request.cs deleted file mode 100644 index 02d7dcf2..00000000 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YadAuthAskV2Request.cs +++ /dev/null @@ -1,56 +0,0 @@ -using Newtonsoft.Json; -using System.Collections.Generic; -using System.Net; -using System.Net.Http; -using YaR.Clouds.Base.Requests; - -namespace YaR.Clouds.Base.Repos.YandexDisk.YadWebV2.Requests -{ - class YadAuthAskV2Request : BaseRequestJson - { - private readonly string _csrf; - private readonly string _uid; - - public YadAuthAskV2Request(HttpCommonSettings settings, IAuth auth, string csrf, string uid) - : base(settings, auth) - { - _csrf = csrf; - _uid = uid; - } - - protected override string RelationalUri => "/registration-validations/auth/additional_data/ask_v2"; - - protected override HttpWebRequest CreateRequest(string baseDomain = null) - { - var request = base.CreateRequest("https://passport.yandex.ru"); - request.Referer = "https://passport.yandex.ru/"; - request.Headers["Sec-Fetch-Mode"] = "cors"; - request.Headers["Sec-Fetch-Site"] = "same-origin"; - - return request; - } - - protected override byte[] CreateHttpContent() - { - var keyValues = new List> - { - new("csrf_token", _csrf), - new("uid", _uid) - }; - FormUrlEncodedContent z = new FormUrlEncodedContent(keyValues); - var d = z.ReadAsByteArrayAsync().Result; - return d; - } - } - - class YadAuthAskV2RequestResult - { - public bool HasError => Status == "error"; - - [JsonProperty("status")] - public string Status { get; set; } - - [JsonProperty("errors")] - public List Errors { get; set; } - } -} \ No newline at end of file diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YadAuthPasswordRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YadAuthPasswordRequest.cs deleted file mode 100644 index 0f0db900..00000000 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YadAuthPasswordRequest.cs +++ /dev/null @@ -1,106 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Specialized; -using System.IO; -using System.Net; -using System.Net.Http; -using System.Security.Authentication; -using Newtonsoft.Json; -using YaR.Clouds.Base.Requests; - -namespace YaR.Clouds.Base.Repos.YandexDisk.YadWebV2.Requests -{ - class YadAuthPasswordRequest : BaseRequestJson - { - private readonly IAuth _auth; - private readonly string _csrf; - private readonly string _trackId; - private readonly string _secChUa; - - public YadAuthPasswordRequest(HttpCommonSettings settings, IAuth auth, string csrf, string trackId) - : base(settings, auth) - { - _auth = auth; - _csrf = csrf; - _trackId = trackId; - - _secChUa = settings.CloudSettings.SecChUa; - } - - protected override string RelationalUri => "/registration-validations/auth/multi_step/commit_password"; - - protected override HttpWebRequest CreateRequest(string baseDomain = null) - { - var request = base.CreateRequest("https://passport.yandex.ru"); - - request.Accept = "application/json, text/javascript, */*; q=0.01"; - request.Referer = "https://passport.yandex.ru/"; - request.ContentType = "application/x-www-form-urlencoded; charset=UTF-8"; - - // Строка вида "\" Not A; Brand\";v=\"99\", \"Chromium\";v=\"99\", \"Google Chrome\";v=\"99\"" - request.Headers.Add("sec-ch-ua", _secChUa); - request.Headers.Add("X-Requested-With", "XMLHttpRequest"); - request.Headers.Add("sec-ch-ua-mobile", "?0"); - request.Headers.Add("sec-ch-ua-platform", "\"Windows\""); - request.Headers.Add("Origin", "https://passport.yandex.ru"); - request.Headers.Add("Sec-Fetch-Site", "same-origin"); - request.Headers.Add("Sec-Fetch-Mode", "cors"); - request.Headers.Add("Sec-Fetch-Dest", "empty"); - request.Headers.Add("Accept-Encoding", "gzip, deflate, br"); - request.Headers.Add("Accept-Language", "ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7,es;q=0.6"); - - return request; - } - - protected override byte[] CreateHttpContent() - { -#pragma warning disable SYSLIB0013 // Type or member is obsolete - var keyValues = new List> - { - new("csrf_token", Uri.EscapeUriString(_csrf)), - new("track_id", _trackId), - new("password", Uri.EscapeUriString(_auth.Password)), - new("retpath", Uri.EscapeUriString("https://disk.yandex.ru/client/disk")) - }; -#pragma warning restore SYSLIB0013 // Type or member is obsolete - var content = new FormUrlEncodedContent(keyValues); - var d = content.ReadAsByteArrayAsync().Result; - return d; - } - - protected override RequestResponse DeserializeMessage(NameValueCollection responseHeaders, Stream stream) - { - var res = base.DeserializeMessage(responseHeaders, stream); - - if (res.Result.State == "auth_challenge") - throw new AuthenticationException("Browser login required to accept additional confirmations"); - - var uid = responseHeaders["X-Default-UID"]; - if (string.IsNullOrWhiteSpace(uid)) - throw new AuthenticationException("Cannot get X-Default-UID"); - res.Result.DefaultUid = uid; - - return res; - } - } - - class YadAuthPasswordRequestResult - { - public bool HasError => Status == "error"; - - [JsonProperty("status")] - public string Status { get; set; } - - [JsonProperty("state")] - public string State { get; set; } - - [JsonProperty("retpath")] - public string RetPath { get; set; } - - [JsonIgnore] - public string DefaultUid { get; set; } - - [JsonProperty("errors")] - public List Errors { get; set; } - } -} \ No newline at end of file diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YadDownloadRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YadDownloadRequest.cs deleted file mode 100644 index ee3c4783..00000000 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YadDownloadRequest.cs +++ /dev/null @@ -1,46 +0,0 @@ -using System.Net; -using System.Net.Mime; -using YaR.Clouds.Base.Requests; - -namespace YaR.Clouds.Base.Repos.YandexDisk.YadWebV2.Requests -{ - class YadDownloadRequest - { - public YadDownloadRequest(HttpCommonSettings settings, IAuth authenticator, string url, long instart, long inend) - { - Request = CreateRequest(authenticator, settings.Proxy, url, instart, inend, settings.UserAgent); - } - - public HttpWebRequest Request { get; } - - private static HttpWebRequest CreateRequest(IAuth Authenticator, IWebProxy proxy, string url, long instart, long inend, string userAgent) - { -#pragma warning disable SYSLIB0014 // Type or member is obsolete - var request = (HttpWebRequest)WebRequest.Create(url); -#pragma warning restore SYSLIB0014 // Type or member is obsolete - - request.Headers.Add("Accept-Ranges", "bytes"); - request.Headers.Add("Upgrade-Insecure-Requests", "1"); - request.Headers.Add("Sec-Fetch-Site", "cross-site"); - request.Headers.Add("Sec-Fetch-Mode", "nested-navigate"); - - request.AddRange(instart, inend); - request.Proxy = proxy; - request.CookieContainer = Authenticator.Cookies; - request.Method = "GET"; - request.ContentType = MediaTypeNames.Application.Octet; - request.Accept = "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3"; - request.UserAgent = userAgent; - request.AllowReadStreamBuffering = false; - request.AllowAutoRedirect = true; - request.Referer = "https://disk.yandex.ru/client/disk"; - - return request; - } - - public static implicit operator HttpWebRequest(YadDownloadRequest v) - { - return v.Request; - } - } -} \ No newline at end of file diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YadUploadRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YadUploadRequest.cs deleted file mode 100644 index fa4ca5d3..00000000 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/Requests/YadUploadRequest.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System.Net; -using YaR.Clouds.Base.Repos.MailRuCloud; -using YaR.Clouds.Base.Requests; - -namespace YaR.Clouds.Base.Repos.YandexDisk.YadWebV2.Requests -{ - class YadUploadRequest - { - public YadUploadRequest(HttpCommonSettings settings, YadWebAuth authenticator, string url, long size) - { - Request = CreateRequest(url, authenticator, settings.Proxy, size, settings.UserAgent); - } - - public HttpWebRequest Request { get; } - - private HttpWebRequest CreateRequest(string url, YadWebAuth authenticator, IWebProxy proxy, long size, string userAgent) - { -#pragma warning disable SYSLIB0014 // Type or member is obsolete - var request = (HttpWebRequest)WebRequest.Create(url); -#pragma warning restore SYSLIB0014 // Type or member is obsolete - request.Proxy = proxy; - request.CookieContainer = authenticator.Cookies; - request.Method = "PUT"; - request.ContentLength = size; - request.Referer = "https://disk.yandex.ru/client/disk"; - request.Headers.Add("Origin", ConstSettings.CloudDomain); - request.Accept = "*/*"; - request.UserAgent = userAgent; - request.AllowWriteStreamBuffering = false; - return request; - } - - public static implicit operator HttpWebRequest(YadUploadRequest v) - { - return v.Request; - } - } -} \ No newline at end of file diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/YadHasher.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/YadHasher.cs deleted file mode 100644 index c5fce414..00000000 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/YadHasher.cs +++ /dev/null @@ -1,88 +0,0 @@ -using System; -using System.IO; -using System.Security.Cryptography; - -namespace YaR.Clouds.Base.Repos.YandexDisk.YadWebV2 -{ - class YadHasher : ICloudHasher - { - public YadHasher() - { - _sha256.Initialize(); - _md5.Initialize(); - AppendInitBuffer(); - } - - public string Name => "yadsha256"; - - public void Append(byte[] buffer, int offset, int length) - { - if (_isClosed) - throw new Exception("Cannot append because hash already calculated."); - - _sha256.TransformBlock(buffer, offset, length, null, 0); - _md5.TransformBlock(buffer, offset, length, null, 0); - } - - public void Append(byte[] buffer) - { - Append(buffer, 0, buffer.Length); - } - - public void Append(Stream stream) - { - if (_isClosed) - throw new Exception("Cannot append because MRSHA1 already calculated."); - - byte[] buffer = new byte[8192]; - int read; - while ((read = stream.Read(buffer, 0, buffer.Length)) > 0) - { - Append(buffer, 0, read); - } - } - - //public string HashString => BitConverter.ToString(Hash).Replace("-", string.Empty); - - public IFileHash Hash - { - get - { - if (null != _hashSha256 && null != _hashMd5) - return new FileHashYad(_hashSha256, _hashMd5); - - AppendFinalBuffer(); - - _sha256.TransformFinalBlock(Array.Empty(), 0, 0); - _md5.TransformFinalBlock(Array.Empty(), 0, 0); - _hashSha256 = _sha256.Hash; - _hashMd5 = _md5.Hash; - _isClosed = true; - return new FileHashYad(_hashSha256, _hashMd5); - } - } - - public long Length => 20; - - private byte[] _hashSha256; - private byte[] _hashMd5; - - private readonly SHA256 _sha256 = SHA256.Create(); - private readonly MD5 _md5 = MD5.Create(); - private bool _isClosed; - - private static void AppendInitBuffer() - { - } - - private static void AppendFinalBuffer() - { - } - - public void Dispose() - { - _sha256?.Dispose(); - _md5?.Dispose(); - } - } -} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/YadWebAuth.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/YadWebAuth.cs deleted file mode 100644 index 6d80c7f6..00000000 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/YadWebAuth.cs +++ /dev/null @@ -1,288 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Net; -using System.Net.Http; -using System.Security.Authentication; -using System.Threading; -using System.Threading.Tasks; -using Newtonsoft.Json; -using YaR.Clouds.Base.Repos.YandexDisk.YadWebV2.Models; -using YaR.Clouds.Base.Repos.YandexDisk.YadWebV2.Requests; -using YaR.Clouds.Base.Requests; - -namespace YaR.Clouds.Base.Repos.YandexDisk.YadWebV2 -{ - class YadWebAuth : IAuth - { - private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(YadWebAuth)); - - public YadWebAuth(SemaphoreSlim connectionLimiter, HttpCommonSettings settings, IBasicCredentials credentials) - { - - _settings = settings; - _creds = credentials; - Cookies = new CookieContainer(); - bool doRegularLogin = true; - - // if local cookie cache on disk is enabled - if (!string.IsNullOrEmpty(_settings.CloudSettings.BrowserAuthenticatorCacheDir)) - { - string path = null; - - // Если в кеше аутентификации пустой, пытаемся загрузить куки из кеша - try - { - // Check file with cookies is created - path = Path.Combine( - settings.CloudSettings.BrowserAuthenticatorCacheDir, - credentials.Login); - - if (System.IO.File.Exists(path)) - { - var testAuthenticator = new YadWebAuth(_settings, _creds, path); - // Try to get user info using cached cookie - new YaDCommonRequest(_settings, testAuthenticator) - .With(new YadAccountInfoPostModel(), - out YadResponseModel itemInfo) - .MakeRequestAsync(connectionLimiter).Wait(); - - var res = itemInfo.ToAccountInfo(); - - // Request for user info using cached cookie finished successfully - Cookies = testAuthenticator.Cookies; - DiskSk = testAuthenticator.DiskSk; - Uuid = testAuthenticator.Uuid; - doRegularLogin = false; - Logger.Info($"Browser authentication refreshed using cached cookie"); - } - } - catch (Exception) - { - // Request for user info using cached cookie failed - - // Delete file with cache first - try - { - System.IO.File.Delete(path); - } - catch (Exception) { } - // Then make regular login - doRegularLogin = true; - } - } - - if (doRegularLogin) - { - try - { - MakeLogin().Wait(); - } - catch (AggregateException aex) when (aex.InnerException is HttpRequestException ex) - { - Logger.Error("Browser authentication failed! " + - "Please check browser authentication component is running!"); - - throw new InvalidCredentialException("Browser authentication failed! Browser component is not running!"); - } - catch (AggregateException aex) when (aex.InnerException is AuthenticationException ex) - { - string txt = string.Concat("Browser authentication failed! ", ex.Message); - Logger.Error(txt); - - throw new InvalidCredentialException(txt); - } - catch (Exception) - { - Logger.Error("Browser authentication failed! " + - "Check the URL and the password for browser authentication component!"); - - throw new InvalidCredentialException("Browser authentication failed!"); - } - Logger.Info($"Browser authentication successful"); - } - } - - public YadWebAuth(HttpCommonSettings settings, IBasicCredentials credentials, string path) - { - _settings = settings; - _creds = credentials; - Cookies = new CookieContainer(); - - string content = System.IO.File.ReadAllText(path); - BrowserAppResponse response = JsonConvert.DeserializeObject(content); - - DiskSk = /*YadAuth.DiskSk*/ response.Sk; - Uuid = /*YadAuth.Uuid*/response.Uuid; //yandexuid - - foreach (var item in response.Cookies) - { - var cookie = new Cookie(item.Name, item.Value, item.Path, item.Domain); - Cookies.Add(cookie); - } - } - - private readonly IBasicCredentials _creds; - private readonly HttpCommonSettings _settings; - - public async Task MakeLogin() - { - (BrowserAppResponse response, string responseHtml) = await ConnectToBrowserApp(); - - if (response != null && - !string.IsNullOrEmpty(response.Sk) && - !string.IsNullOrEmpty(response.Uuid) && - !string.IsNullOrEmpty(response.Login) && - GetNameOnly(response.Login) - .Equals(GetNameOnly(Login), StringComparison.OrdinalIgnoreCase) && - string.IsNullOrEmpty(response.ErrorMessage) - ) - { - DiskSk = /*YadAuth.DiskSk*/ response.Sk; - Uuid = /*YadAuth.Uuid*/response.Uuid; //yandexuid - - foreach (var item in response.Cookies) - { - var cookie = new Cookie(item.Name, item.Value, item.Path, item.Domain); - Cookies.Add(cookie); - } - - // Если аутентификация прошла успешно, сохраняем результат в кеш в файл - if (!string.IsNullOrEmpty(_settings.CloudSettings.BrowserAuthenticatorCacheDir)) - { - string path = Path.Combine( - _settings.CloudSettings.BrowserAuthenticatorCacheDir, - _creds.Login); - - try - { - string dir = Path.GetDirectoryName(path); - if (!Directory.Exists(dir)) - Directory.CreateDirectory(dir); - } - catch (Exception) - { - throw new AuthenticationException("Directory for cache can not be created, " + - "remove attribute CacheDir in BrowserAuthenticator tag in configuration file!"); - } - try - { -#if NET48 - System.IO.File.WriteAllText(path, responseHtml); -#else - await System.IO.File.WriteAllTextAsync(path, responseHtml); -#endif - } - catch (Exception) { } - } - } - else - { - if (string.IsNullOrEmpty(response?.ErrorMessage)) - throw new AuthenticationException("OAuth: Authentication using YandexAuthBrowser is failed!"); - - throw new AuthenticationException( - string.Concat( - "OAuth: Authentication using YandexAuthBrowser is failed! ", - response.ErrorMessage)); - } - } - - public string Login => _creds.Login; - public string Password => _creds.Password; - public string DiskSk { get; set; } - /// - /// yandexuid - /// - public string Uuid { get; set; } - - public bool IsAnonymous => false; - public string AccessToken { get; } - public string DownloadToken { get; } - public CookieContainer Cookies { get; private set; } - public void ExpireDownloadToken() - { - throw new NotImplementedException(); - } - - public class BrowserAppResponse - { - [JsonProperty("ErrorMessage")] - public string ErrorMessage { get; set; } - - [JsonProperty("Login")] - public string Login { get; set; } - - /// - /// yandexuid - /// - [JsonProperty("Uuid")] - public string Uuid { get; set; } - - [JsonProperty("Sk")] - public string Sk { get; set; } - - [JsonProperty("Cookies")] - public List Cookies { get; set; } - } - public class BrowserAppCookieResponse - { - [JsonProperty("Name")] - public string Name { get; set; } - - [JsonProperty("Value")] - public string Value { get; set; } - - [JsonProperty("Path")] - public string Path { get; set; } - - [JsonProperty("Domain")] - public string Domain { get; set; } - } - - private static string GetNameOnly(string value) - { - if (string.IsNullOrEmpty(value)) - return value; - int pos = value.IndexOf('@'); - if (pos == 0) - return ""; - if (pos > 0) - return value.Substring(0, pos); - return value; - } - - private async Task<(BrowserAppResponse, string)> ConnectToBrowserApp() - { - string url = _settings.CloudSettings.BrowserAuthenticatorUrl; - string password = string.IsNullOrWhiteSpace(Password) - ? _settings.CloudSettings.BrowserAuthenticatorPassword - : Password; - - if (string.IsNullOrEmpty(url)) - { - throw new Exception("Ошибка! " + - "Для работы с Яндекс.Диском запустите сервер аутентификации и задайте в параметре YandexAuthenticationUrl его настройки!"); - } - - using var client = new HttpClient { BaseAddress = new Uri(url) }; - var httpRequestMessage = new HttpRequestMessage - { - Method = HttpMethod.Get, - RequestUri = new Uri($"/{Uri.EscapeDataString(Login)}/{Uri.EscapeDataString(password)}/", UriKind.Relative), - Headers = { - { HttpRequestHeader.Accept.ToString(), "application/json" }, - { HttpRequestHeader.ContentType.ToString(), "application/json" }, - }, - //Content = new StringContent(JsonConvert.SerializeObject("")) - }; - - client.Timeout = new TimeSpan(0, 5, 0); - using var response = await client.SendAsync(httpRequestMessage); - var responseText = await response.Content.ReadAsStringAsync(); - response.EnsureSuccessStatusCode(); - BrowserAppResponse data = JsonConvert.DeserializeObject(responseText); - return (data, responseText); - } - } -} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/YadWebRequestRepo.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/YadWebRequestRepo.cs deleted file mode 100644 index f70bcb30..00000000 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWebV2/YadWebRequestRepo.cs +++ /dev/null @@ -1,808 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Net; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using YaR.Clouds.Base.Repos.MailRuCloud; -using YaR.Clouds.Base.Repos.YandexDisk.YadWebV2.Models; -using YaR.Clouds.Base.Repos.YandexDisk.YadWebV2.Models.Media; -using YaR.Clouds.Base.Repos.YandexDisk.YadWebV2.Requests; -using YaR.Clouds.Base.Requests; -using YaR.Clouds.Base.Requests.Types; -using YaR.Clouds.Base.Streams; -using YaR.Clouds.Common; -using Stream = System.IO.Stream; - -namespace YaR.Clouds.Base.Repos.YandexDisk.YadWebV2 -{ - class YadWebRequestRepo : IRequestRepo - { - private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(YadWebRequestRepo)); - - private readonly SemaphoreSlim _connectionLimiter; - - //private ItemOperation _lastRemoveOperation; - - private const int OperationStatusCheckIntervalMs = 300; - private const int OperationStatusCheckRetryCount = 8; - private readonly TimeSpan OperationStatusCheckRetryTimeout = TimeSpan.FromMinutes(5); - - private readonly IBasicCredentials _creds; - - private struct ParallelInfo - { - public int Offset; - public int Amount; - public YadFolderInfoRequestData Result; - } - - public YadWebRequestRepo(CloudSettings settings, IWebProxy proxy, IBasicCredentials credentials) - { - _connectionLimiter = new SemaphoreSlim(settings.MaxConnectionCount); - - HttpSettings = new() - { - UserAgent = settings.UserAgent, - CloudSettings = settings, - Proxy = proxy, - }; - - _creds = credentials; - - ServicePointManager.DefaultConnectionLimit = int.MaxValue; - - // required for Windows 7 breaking connection - ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13 | SecurityProtocolType.Tls12; - } - - private async Task>> GetShareListInner() - { - await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) - .With(new YadFolderInfoPostModel("/", "/published"), - out YadResponseModel folderInfo) - .MakeRequestAsync(_connectionLimiter); - - var res = folderInfo.Data.Resources - .Where(it => !string.IsNullOrEmpty(it.Meta?.UrlShort)) - .ToDictionary( - it => it.Path.Remove(0, "/disk".Length), - it => Enumerable.Repeat(new PublicLinkInfo("short", it.Meta.UrlShort), 1)); - - return res; - } - - public IAuth Authenticator => CachedAuth.Value; - - private Cached CachedAuth => _cachedAuth ??= - new Cached(_ => new YadWebAuth(_connectionLimiter, HttpSettings, _creds), _ => TimeSpan.FromHours(23)); - private Cached _cachedAuth; - - public Cached>> CachedSharedList - => _cachedSharedList ??= new Cached>>( - _ => - { - var res = GetShareListInner().Result; - return res; - }, - _ => TimeSpan.FromSeconds(30)); - private Cached>> _cachedSharedList; - - - public HttpCommonSettings HttpSettings { get; private set; } - - public Stream GetDownloadStream(File aFile, long? start = null, long? end = null) - { - CustomDisposable ResponseGenerator(long instart, long inend, File file) - { - //var urldata = new YadGetResourceUrlRequest(HttpSettings, (YadWebAuth)Authenticator, file.FullPath) - // .MakeRequestAsync(_connectionLimiter) - // .Result; - string url = null; - if (file.DownloadUrlCache == null || - file.DownloadUrlCacheExpirationTime <= DateTime.Now) - { - var _ = new YaDCommonRequest(HttpSettings, (YadWebAuth)Authenticator) - .With(new YadGetResourceUrlPostModel(file.FullPath), - out YadResponseModel itemInfo) - .MakeRequestAsync(_connectionLimiter).Result; - - if (itemInfo == null || - itemInfo.Error != null || - itemInfo.Data == null || - itemInfo.Data.Error != null || - itemInfo?.Data?.File == null) - { - throw new FileNotFoundException(string.Concat( - "File reading error ", itemInfo?.Error?.Message, - " ", - itemInfo?.Data?.Error?.Message, - " ", - itemInfo?.Data?.Error?.Body?.Title)); - } - url = "https:" + itemInfo.Data.File; - - file.DownloadUrlCache = url; - file.DownloadUrlCacheExpirationTime = DateTime.Now.AddMinutes(1); - } - else - { - url = file.DownloadUrlCache; - } - HttpWebRequest request = new YadDownloadRequest(HttpSettings, (YadWebAuth)Authenticator, url, instart, inend); - var response = (HttpWebResponse)request.GetResponse(); - - return new CustomDisposable - { - Value = response, - OnDispose = () => {} - }; - } - - if (start.HasValue || end.HasValue) - Logger.Debug($"Download: {aFile.FullPath} [{start}-{end}]"); - else - Logger.Debug($"Download: {aFile.FullPath}"); - - var stream = new DownloadStream(ResponseGenerator, aFile, start, end); - return stream; - } - - //public HttpWebRequest UploadRequest(File file, UploadMultipartBoundary boundary) - //{ - // var urldata = - // new YadGetResourceUploadUrlRequest(HttpSettings, (YadWebAuth)Authenticator, file.FullPath, file.OriginalSize) - // .MakeRequestAsync(_connectionLimiter) - // .Result; - // var url = urldata.Models[0].Data.UploadUrl; - - // var result = new YadUploadRequest(HttpSettings, (YadWebAuth)Authenticator, url, file.OriginalSize); - // return result; - //} - - public ICloudHasher GetHasher() - { - return new YadHasher(); - } - - public bool SupportsAddSmallFileByHash => false; - public bool SupportsDeduplicate => true; - - private (HttpRequestMessage, string oid) CreateUploadClientRequest(PushStreamContent content, File file) - { - var hash = (FileHashYad?) file.Hash; - var _ = new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) - .With(new YadGetResourceUploadUrlPostModel(file.FullPath, file.OriginalSize, - hash?.HashSha256.Value ?? string.Empty, - hash?.HashMd5.Value ?? string.Empty), - out YadResponseModel itemInfo) - .MakeRequestAsync(_connectionLimiter).Result; - - var url = itemInfo.Data.UploadUrl; - - var request = new HttpRequestMessage - { - RequestUri = new Uri(url), - Method = HttpMethod.Put - }; - - request.Headers.Add("Accept", "*/*"); - request.Headers.TryAddWithoutValidation("User-Agent", HttpSettings.UserAgent); - - request.Content = content; - request.Content.Headers.ContentLength = file.OriginalSize; - - - return (request,itemInfo?.Data?.Oid); - } - - public async Task DoUpload(HttpClient client, PushStreamContent content, File file) - { - (var request, string oid) = CreateUploadClientRequest(content, file); - var responseMessage = await client.SendAsync(request); - var ures = responseMessage.ToUploadPathResult(); - - ures.NeedToAddFile = false; - //await Task.Delay(1_000); - - if (!string.IsNullOrEmpty(oid)) - WaitForOperation(oid); - - return ures; - } - - private const string YadMediaPath = "/Media.wdyad"; - - /// - /// Сколько записей папки читать в первом обращении, до параллельного чтения. - /// Яндекс читает по 40 записей, путь тоже будет 40. - /// - private const int FirstReadEntriesCount = 40; - - public async Task FolderInfo(RemotePath path, int offset = 0, int limit = int.MaxValue, int depth = 1) - { - if (path.IsLink) - throw new NotImplementedException(nameof(FolderInfo)); - - if (path.Path.StartsWith(YadMediaPath)) - return await MediaFolderInfo(path.Path); - - // YaD perform async deletion - YadResponseModel entryInfo = null; - YadResponseModel folderInfo = null; - YadResponseModel entryStats = null; - YadResponseModel, YadActiveOperationsParams> operInfo = null; - - //bool hasRemoveOp = _lastRemoveOperation != null && - // WebDavPath.IsParentOrSame(path.Path, _lastRemoveOperation.Path) && - // (DateTime.Now - _lastRemoveOperation.DateTime).TotalMilliseconds < 1_000; - - int maxParallel = Math.Max(_connectionLimiter.CurrentCount - 1, 1); - // Если доступных подключений к серверу 2 или менее, то не делаем параллельного чтения - int firstReadLimit = maxParallel <= 2 ? int.MaxValue : FirstReadEntriesCount; - - Retry.Do( - () => - { - //var doPreSleep = hasRemoveOp ? TimeSpan.FromMilliseconds(OperationStatusCheckIntervalMs) : TimeSpan.Zero; - //if (doPreSleep > TimeSpan.Zero) - // Logger.Debug("Has remove op, sleep before"); - //return doPreSleep; - return TimeSpan.Zero; - }, - () => new YaDCommonRequest(HttpSettings, (YadWebAuth)Authenticator) - .With(new YadItemInfoPostModel(path.Path), out entryInfo) - .With(new YadFolderInfoPostModel(path.Path) { WithParent = true, Amount = firstReadLimit }, out folderInfo) - .With(new YadResourceStatsPostModel(path.Path), out entryStats) - .With(new YadActiveOperationsPostModel(), out operInfo) - .MakeRequestAsync(_connectionLimiter) - .Result, - _ => false, - //_ => - //{ - // bool doAgain = false; - // if (hasRemoveOp && _lastRemoveOperation != null) - // { - // string cmpPath = WebDavPath.Combine("/disk", _lastRemoveOperation.Path); - // doAgain = hasRemoveOp && - // folderInfo.Data.Resources.Any(r => WebDavPath.PathEquals(r.Path, cmpPath)); - // } - // if (doAgain) - // Logger.Debug("Remove op still not finished, let's try again"); - // return doAgain; - //}, - TimeSpan.FromMilliseconds(OperationStatusCheckIntervalMs), OperationStatusCheckRetryTimeout); - - - if (entryInfo?.Error != null || - (entryInfo?.Data?.Error?.Id ?? "HTTP_404") != "HTTP_404" || - entryStats?.Error != null || - (entryStats?.Data?.Error?.Id ?? "HTTP_404") != "HTTP_404" || - folderInfo?.Error != null || - (folderInfo?.Data?.Error?.Id ?? "HTTP_404") != "HTTP_404") - { - throw new IOException(string.Concat("Error reading file or directory information from server ", - entryInfo?.Error?.Message, - " ", - entryInfo?.Data?.Error?.Message, - " ", - entryStats?.Error?.Message, - " ", - entryStats?.Data?.Error?.Message)); - } - - var entryData = entryInfo?.Data; - if (entryData?.Type is null) - return null; - if (entryData.Type == "file") - return entryData.ToFile(); - - Folder folder = folderInfo.Data.ToFolder(entryData, entryStats.Data, path.Path, operInfo?.Data); - folder.IsChildrenLoaded = limit == int.MaxValue; - - int alreadyCount = folder.Descendants.Count; - // Если количество полученных элементов списка меньше максимального запрошенного числа элементов, - // даже с учетом, что в число элементов сверх запрошенного входит информация - // о папке-контейнере (папке, чей список элементов запросили), то считаем, - // что получен полный список содержимого папки и возвращает данные. - if (alreadyCount < firstReadLimit) - return folder; - // В противном случае делаем несколько параллельных выборок для ускорения чтения списков с сервера. - - int entryCount = folderInfo?.Data?.Resources.FirstOrDefault()?.Meta?.TotalEntityCount ?? 1; - - // Обновление количества доступных подключений - maxParallel = Math.Max(_connectionLimiter.CurrentCount - 1, 1); - var info = new ParallelInfo[maxParallel]; - int restAmount = entryCount - alreadyCount; - - int amountParallel = 40 * maxParallel > restAmount - ? 40 - : (restAmount + maxParallel-1) / maxParallel; - - int startIndex = alreadyCount; - int lastIndex = 0; - for (int i = 0; startIndex < entryCount && i < info.Length; i++) - { - info[i] = new ParallelInfo - { - Offset = startIndex, - Amount = amountParallel, - }; - startIndex += amountParallel; - lastIndex = i; - } - - if (lastIndex < info.Length - 1) - Array.Resize(ref info, lastIndex + 1); - - // Хвостовой кусок читаем без ограничения длины, на случай неправильных подсчетов - // или добавленных в параллели файлов. - info[info.Length - 1].Amount = int.MaxValue; - - Retry.Do( - () => - { - //var doPreSleep = hasRemoveOp ? TimeSpan.FromMilliseconds(OperationStatusCheckIntervalMs) : TimeSpan.Zero; - //if (doPreSleep > TimeSpan.Zero) - // Logger.Debug("Has remove op, sleep before"); - //return doPreSleep; - return TimeSpan.Zero; - }, - () => - { - string diskPath = WebDavPath.Combine("/disk", entryData.Path); - - Parallel.For(0, info.Length, (int index) => - { - YadResponseResult noReturn = new YaDCommonRequest(HttpSettings, (YadWebAuth)Authenticator) - .With(new YadFolderInfoPostModel(path.Path) - { - Offset = info[index].Offset, - Amount = info[index].Amount, - WithParent = false - }, out YadResponseModel folderPartInfo) - .MakeRequestAsync(_connectionLimiter) - .Result; - - if (folderPartInfo?.Error != null || - folderPartInfo?.Data?.Error != null) - throw new IOException(string.Concat("Error reading file or directory information from server ", - folderPartInfo?.Error?.Message, - " ", - folderPartInfo?.Data?.Error?.Message)); - - if (folderPartInfo?.Data is not null && folderPartInfo.Error is null) - info[index].Result = folderPartInfo.Data; - }); - - return (YadResponseResult)null; - }, - _ => false, - //{ - //TODO: Здесь полностью неправильная проверка - //bool doAgain = false; - //if (hasRemoveOp && _lastRemoveOperation != null) - //{ - // string cmpPath = WebDavPath.Combine("/disk", _lastRemoveOperation.Path); - // doAgain = hasRemoveOp && - // folderInfo.Data.Resources.Any(r => WebDavPath.PathEquals(r.Path, cmpPath)); - //} - //if (doAgain) - // Logger.Debug("Remove op still not finished, let's try again"); - //return doAgain; - //}, - TimeSpan.FromMilliseconds(OperationStatusCheckIntervalMs), OperationStatusCheckRetryTimeout); - - string diskPath = WebDavPath.Combine("/disk", path.Path); - var children = new List(folder.Descendants); - for (int i = 0; i < info.Length; i++) - { - var fi = info[i].Result.Resources; - children.AddRange( - fi.Where(it => it.Type == "file") - .Select(f => f.ToFile()) - .ToGroupedFiles()); - children.AddRange( - fi.Where(it => it.Type == "dir" && - // Пропуск элемента с информацией папки о родительской папке, - // этот элемент добавляется в выборки, если читается - // не всё содержимое папки, а делается только вырезка - it.Path != diskPath) - .Select(f => f.ToFolder())); - } - folder.Descendants = ImmutableList.Create(children.Distinct().ToArray()); - - return folder; - } - - - private async Task MediaFolderInfo(string path) - { - var entry = await MediaFolderRootInfo(); - - if (entry == null || entry is not Folder root) - return null; - - if (WebDavPath.PathEquals(path, YadMediaPath)) - return root; - - string albumName = WebDavPath.Name(path); - var child = entry.Descendants.FirstOrDefault(child => child.Name == albumName); - if (child is null) - return null; - - var album = child; - - var key = album.PublicLinks.Values.FirstOrDefault()?.Key; - if (key == null) - return null; - - _ = new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) - .With(new YadFolderInfoPostModel(key, "/album"), - out YadResponseModel folderInfo) - .MakeRequestAsync(_connectionLimiter) - .Result; - - Folder folder = folderInfo.Data.ToFolder(null, null, path, null); - folder.IsChildrenLoaded = true; - - return folder; - } - - private async Task MediaFolderRootInfo() - { - Folder res = new Folder(YadMediaPath); - - _ = await new YaDCommonRequest(HttpSettings, (YadWebAuth)Authenticator) - .With(new YadGetAlbumsSlicesPostModel(), - out YadResponseModel slices) - .With(new YadAlbumsPostModel(), - out YadResponseModel albums) - .MakeRequestAsync(_connectionLimiter); - - var children = new List(); - - if (slices.Data.Albums.Camera != null) - { - Folder folder = - new Folder($"{YadMediaPath}/.{slices.Data.Albums.Camera.Id}") - { - ServerFilesCount = (int)slices.Data.Albums.Camera.Count - }; - children.Add(folder); - } - if (slices.Data.Albums.Photounlim != null) - { - Folder folder = - new Folder($"{YadMediaPath}/.{slices.Data.Albums.Photounlim.Id}") - { - ServerFilesCount = (int)slices.Data.Albums.Photounlim.Count - }; - children.Add(folder); - } - if (slices.Data.Albums.Videos != null) - { - Folder folder = - new Folder($"{YadMediaPath}/.{slices.Data.Albums.Videos.Id}") - { - ServerFilesCount = (int)slices.Data.Albums.Videos.Count - }; - children.Add(folder); - } - res.Descendants = res.Descendants.AddRange(children); - - foreach (var item in albums.Data) - { - Folder folder = new Folder($"{YadMediaPath}/{item.Title}"); - folder.PublicLinks.TryAdd( - item.Public.PublicUrl, - new PublicLinkInfo(item.Public.PublicUrl) { Key = item.Public.PublicKey }); - } - - return res; - } - - - public Task ItemInfo(RemotePath path, int offset = 0, int limit = int.MaxValue) - { - throw new NotImplementedException(); - } - - - public async Task AccountInfo() - { - //var req = await new YadAccountInfoRequest(HttpSettings, (YadWebAuth)Authenticator).MakeRequestAsync(_connectionLimiter); - - await new YaDCommonRequest(HttpSettings, (YadWebAuth)Authenticator) - .With(new YadAccountInfoPostModel(), - out YadResponseModel itemInfo) - .MakeRequestAsync(_connectionLimiter); - - var res = itemInfo.ToAccountInfo(); - return res; - } - - public async Task CreateFolder(string path) - { - //var req = await new YadCreateFolderRequest(HttpSettings, (YadWebAuth)Authenticator, path) - // .MakeRequestAsync(_connectionLimiter); - - await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) - .With(new YadCreateFolderPostModel(path), - out YadResponseModel itemInfo) - .MakeRequestAsync(_connectionLimiter); - - var res = itemInfo.Params.ToCreateFolderResult(); - return res; - } - - public async Task AddFile(string fileFullPath, IFileHash fileHash, FileSize fileSize, DateTime dateTime, - ConflictResolver? conflictResolver) - { - var hash = (FileHashYad?)fileHash; - - var _ = new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) - .With(new YadGetResourceUploadUrlPostModel(fileFullPath, fileSize, hash?.HashSha256.Value, hash?.HashMd5.Value), - out YadResponseModel itemInfo) - .MakeRequestAsync(_connectionLimiter).Result; - - var res = new AddFileResult - { - Path = fileFullPath, - Success = itemInfo.Data.Status == "hardlinked" - }; - - return await Task.FromResult(res); - } - - public Task CloneItem(string fromUrl, string toPath) - { - throw new NotImplementedException(); - } - - public async Task Copy(string sourceFullPath, string destinationPath, ConflictResolver? conflictResolver = null) - { - string destFullPath = WebDavPath.Combine(destinationPath, WebDavPath.Name(sourceFullPath)); - - //var req = await new YadCopyRequest(HttpSettings, (YadWebAuth)Authenticator, sourceFullPath, destFullPath) - // .MakeRequestAsync(_connectionLimiter); - - await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) - .With(new YadCopyPostModel(sourceFullPath, destFullPath), - out YadResponseModel itemInfo) - .MakeRequestAsync(_connectionLimiter); - - var res = itemInfo.ToCopyResult(); - - if (res.IsSuccess) - WaitForOperation(itemInfo.Data.Oid); - - return res; - } - - public async Task Move(string sourceFullPath, string destinationPath, ConflictResolver? conflictResolver = null) - { - string destFullPath = WebDavPath.Combine(destinationPath, WebDavPath.Name(sourceFullPath)); - - await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) - .With(new YadMovePostModel(sourceFullPath, destFullPath), out YadResponseModel itemInfo) - .MakeRequestAsync(_connectionLimiter); - - var res = itemInfo.ToMoveResult(); - if( res.IsSuccess) - WaitForOperation(itemInfo.Data.Oid); - - return res; - } - - - public async Task ActiveOperationsAsync() - { - YadResponseModel, YadActiveOperationsParams> itemInfo = null; - - _ = await new YaDCommonRequest(HttpSettings, (YadWebAuth)Authenticator) - .With(new YadActiveOperationsPostModel(), out itemInfo) - .With(new YadAccountInfoPostModel(), - out YadResponseModel accountInfo) - .MakeRequestAsync(_connectionLimiter); - - var list = itemInfo?.Data? - .Select(x => new ActiveOperation - { - Oid = x.Oid, - Uid = x.Uid, - Type = x.Type, - SourcePath = GetOpPath(x.Data.Source), - TargetPath = GetOpPath(x.Data.Target), - })?.ToList(); - - var info = new CheckUpInfo - { - AccountInfo = new CheckUpInfo.CheckInfo - { - FilesCount = accountInfo?.Data?.FilesCount ?? 0, - Free = accountInfo?.Data?.Free ?? 0, - Trash = accountInfo?.Data?.Trash ?? 0, - }, - ActiveOperations = list, - }; - - return info; - } - - public static string GetOpPath(string path) - { - if (string.IsNullOrEmpty(path)) - return null; - int colon = path.IndexOf(':'); - return WebDavPath.Clean(path.Substring(1 + colon)).Remove(0, "/disk".Length); - } - - private void WaitForOperation(string operationOid) - { - if (string.IsNullOrWhiteSpace(operationOid)) - return; - - var flagWatch = Stopwatch.StartNew(); - - YadResponseModel itemInfo = null; - Retry.Do( - () => TimeSpan.Zero, - () => new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) - .With(new YadOperationStatusPostModel(operationOid), out itemInfo) - .MakeRequestAsync(_connectionLimiter) - .Result, - _ => - { - /* - * Яндекс повторяет проверку при переносе папки каждый 9 секунд. - * Когда операция завершилась: "status": "DONE", "state": "COMPLETED", "type": "move" - * "params": { - * "source": "12-it's_uid-34:/disk/source-folder", - * "target": "12-it's_uid-34:/disk/destination-folder" - * }, - * Когда операция еще в процессе: "status": "EXECUTING", "state": "EXECUTING", "type": "move" - * "params": { - * "source": "12-it's_uid-34:/disk/source-folder", - * "target": "12-it's_uid-34:/disk/destination-folder" - * }, - */ - var doAgain = itemInfo.Data.Error is null && itemInfo.Data.State != "COMPLETED"; - if (doAgain) - { - if (flagWatch.Elapsed > TimeSpan.FromSeconds(30)) - { - Logger.Debug("Operation is still in progress, let's wait..."); - flagWatch.Restart(); - } - } - return doAgain; - }, - TimeSpan.FromMilliseconds(OperationStatusCheckIntervalMs), OperationStatusCheckRetryTimeout); - } - - public async Task Publish(string fullPath) - { - await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) - .With(new YadPublishPostModel(fullPath, false), out YadResponseModel itemInfo) - .MakeRequestAsync(_connectionLimiter); - - var res = itemInfo.ToPublishResult(); - - if (res.IsSuccess) - CachedSharedList.Value[fullPath] = new List {new(res.Url)}; - - return res; - } - - public async Task Unpublish(Uri publicLink, string fullPath) - { - foreach (var item in CachedSharedList.Value - .Where(kvp => kvp.Key == fullPath).ToList()) - { - CachedSharedList.Value.Remove(item.Key); - } - - await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) - .With(new YadPublishPostModel(fullPath, true), out YadResponseModel itemInfo) - .MakeRequestAsync(_connectionLimiter); - - var res = itemInfo.ToUnpublishResult(); - - return res; - } - - public async Task Remove(string fullPath) - { - //var req = await new YadDeleteRequest(HttpSettings, (YadWebAuth)Authenticator, fullPath) - // .MakeRequestAsync(_connectionLimiter); - - await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) - .With(new YadDeletePostModel(fullPath), - out YadResponseModel itemInfo) - .MakeRequestAsync(_connectionLimiter); - - var res = itemInfo.ToRemoveResult(); - - if (res.IsSuccess) - WaitForOperation(itemInfo.Data.Oid); - - //if (res.IsSuccess) - // _lastRemoveOperation = res.ToItemOperation(); - - return res; - } - - public async Task Rename(string fullPath, string newName) - { - string destPath = WebDavPath.Parent(fullPath); - destPath = WebDavPath.Combine(destPath, newName); - - //var req = await new YadMoveRequest(HttpSettings, (YadWebAuth)Authenticator, fullPath, destPath).MakeRequestAsync(_connectionLimiter); - - await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) - .With(new YadMovePostModel(fullPath, destPath), - out YadResponseModel itemInfo) - .MakeRequestAsync(_connectionLimiter); - - var res = itemInfo.ToRenameResult(); - - if (res.IsSuccess) - WaitForOperation(itemInfo.Data.Oid); - - //if (res.IsSuccess) - // _lastRemoveOperation = res.ToItemOperation(); - - return res; - } - - public Dictionary GetShardInfo1() - { - throw new NotImplementedException(); - } - - - public IEnumerable GetShareLinks(string path) - { - if (!CachedSharedList.Value.TryGetValue(path, out var links)) - yield break; - - foreach (var link in links) - yield return link; - } - - - public async void CleanTrash() - { - await new YaDCommonRequest(HttpSettings, (YadWebAuth) Authenticator) - .With(new YadCleanTrashPostModel(), - out YadResponseModel _) - .MakeRequestAsync(_connectionLimiter); - } - - - - - public IEnumerable PublicBaseUrls { get; set; } = new[] - { - "https://disk.yandex.ru" - }; - public string PublicBaseUrlDefault => PublicBaseUrls.First(); - - - - - - - - public string ConvertToVideoLink(Uri publicLink, SharedVideoResolution videoResolution) - { - throw new NotImplementedException("Yad not implemented ConvertToVideoLink"); - } - } -} diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs index fa7ec082..d37324d4 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs @@ -15,13 +15,13 @@ internal abstract class BaseRequest where T : class { private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(BaseRequest)); - protected readonly HttpCommonSettings Settings; - protected readonly IAuth Auth; + protected readonly HttpCommonSettings _settings; + protected readonly IAuth _auth; protected BaseRequest(HttpCommonSettings settings, IAuth auth) { - Settings = settings; - Auth = auth; + _settings = settings; + _auth = auth; } protected abstract string RelationalUri { get; } @@ -40,15 +40,15 @@ protected virtual HttpWebRequest CreateRequest(string baseDomain = null) var request = WebRequest.CreateHttp(uriz); #pragma warning restore SYSLIB0014 // Type or member is obsolete request.Host = uriz.Host; - request.Proxy = Settings.Proxy; - request.CookieContainer = Auth?.Cookies; + request.Proxy = _settings.Proxy; + request.CookieContainer = _auth?.Cookies; request.Method = "GET"; request.ContentType = ConstSettings.DefaultRequestType; request.Accept = "application/json"; - request.UserAgent = Settings.UserAgent; - request.ContinueTimeout = Settings.CloudSettings.Wait100ContinueTimeoutSec * 1000; - request.Timeout = Settings.CloudSettings.WaitResponseTimeoutSec * 1000; - request.ReadWriteTimeout = Settings.CloudSettings.ReadWriteTimeoutSec * 1000; + request.UserAgent = _settings.UserAgent; + request.ContinueTimeout = _settings.CloudSettings.Wait100ContinueTimeoutSec * 1000; + request.Timeout = _settings.CloudSettings.WaitResponseTimeoutSec * 1000; + request.ReadWriteTimeout = _settings.CloudSettings.ReadWriteTimeoutSec * 1000; request.AllowWriteStreamBuffering = false; request.AllowReadStreamBuffering = true; request.SendChunked = false; @@ -131,7 +131,7 @@ public virtual async Task MakeRequestAsync(SemaphoreSlim serverMaxConnectionL * System.Text.Encoding.UTF8.GetString(requestContent) */ #if NET48 - await requestStream.WriteAsync(requestContent, 0, requestContent.Length).ConfigureAwait(false); + await requestStream.WriteAsync(requestContent, 0, requestContent.Length).ConfigureAwait(false); #else await requestStream.WriteAsync(requestContent).ConfigureAwait(false); #endif @@ -198,7 +198,7 @@ public virtual async Task MakeRequestAsync(SemaphoreSlim serverMaxConnectionL #if DEBUG Logger.Warn(msg); #else - Logger.Debug(msg); + Logger.Debug(msg); #endif throw; } @@ -209,7 +209,7 @@ public virtual async Task MakeRequestAsync(SemaphoreSlim serverMaxConnectionL #if DEBUG Logger.Warn(msg); #else - Logger.Debug(msg); + Logger.Debug(msg); #endif } } @@ -222,7 +222,7 @@ public virtual async Task MakeRequestAsync(SemaphoreSlim serverMaxConnectionL * т.к. от другого компьютера за требуемое время не получен нужный отклик, * или было разорвано уже установленное соединение из-за неверного отклика * уже подключенного компьютера. - * + * * Возможно превышено максимальное количество подключений к серверу. * Просто повторяем запрос после небольшого ожидания. */ @@ -232,7 +232,7 @@ public virtual async Task MakeRequestAsync(SemaphoreSlim serverMaxConnectionL #if DEBUG Logger.Warn(msg); #else - Logger.Debug(msg); + Logger.Debug(msg); #endif throw; } @@ -245,7 +245,7 @@ public virtual async Task MakeRequestAsync(SemaphoreSlim serverMaxConnectionL #if DEBUG Logger.Warn(msg); #else - Logger.Debug(msg); + Logger.Debug(msg); #endif Thread.Sleep(TimeSpan.FromSeconds(2)); } diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequestJson.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequestJson.cs index a2a0c47e..5e3379d9 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequestJson.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequestJson.cs @@ -43,7 +43,5 @@ protected override RequestResponse DeserializeMessage(NameValueCollection res //} } - - } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequestString.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequestString.cs index d81260fd..211b8151 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequestString.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequestString.cs @@ -5,7 +5,7 @@ namespace YaR.Clouds.Base.Requests { internal abstract class BaseRequestString : BaseRequestString { - protected BaseRequestString(HttpCommonSettings settings, IAuth auth) + protected BaseRequestString(HttpCommonSettings settings, IAuth auth) : base(settings, auth) { } @@ -20,4 +20,4 @@ protected override RequestResponse DeserializeMessage(NameValueCollectio return msg; } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/HttpCommonSettings.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/HttpCommonSettings.cs index 4ee8c126..d0a7e66a 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/HttpCommonSettings.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/HttpCommonSettings.cs @@ -9,4 +9,4 @@ public class HttpCommonSettings public string UserAgent { get; set; } public CloudSettings CloudSettings { get; set; } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/RequestException.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/RequestException.cs index cd8a3a4d..6dc9a145 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/RequestException.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/RequestException.cs @@ -14,7 +14,7 @@ public RequestException(string message) : base(message) { } public RequestException(string message, Exception inner) : base(message, inner) { } /// - /// HTTP Status Code retuned by server + /// HTTP Status Code returned by server /// public HttpStatusCode StatusCode { get; set; } @@ -33,5 +33,4 @@ public RequestException(string message, Exception inner) : base(message, inner) /// public long? ErrorCode { get; set; } } - } diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/AccountInfo.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/AccountInfo.cs index 1be756da..f5da4644 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/AccountInfo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/AccountInfo.cs @@ -13,5 +13,3 @@ public long FileSizeLimit public DiskUsage DiskUsage { get; set; } } } - - diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/AccountInfoRequestResult.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/AccountInfoRequestResult.cs index 29121b8a..e7b04517 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/AccountInfoRequestResult.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/AccountInfoRequestResult.cs @@ -74,4 +74,4 @@ public class BillingInfo } } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/ActiveOperation.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/ActiveOperation.cs index 27b3fbc3..543539ef 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/ActiveOperation.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/ActiveOperation.cs @@ -25,5 +25,5 @@ public class ActiveOperation /// /// Идентификатор операции, который можно передавать параметром в метод . /// - public string Oid { get; set; } + public string OpId { get; set; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/BrowserAppResult.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/BrowserAppResult.cs new file mode 100644 index 00000000..3ffb278e --- /dev/null +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/BrowserAppResult.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace YaR.Clouds.Base.Requests.Types; + +public class BrowserAppResult +{ + [JsonProperty("ErrorMessage")] + public string ErrorMessage { get; set; } + + [JsonProperty("Login")] + public string Login { get; set; } + + [JsonProperty("Cloud")] + public string Cloud { get; set; } + + + /// + /// yandexuid + /// + [JsonProperty("Uuid")] + public string Uuid { get; set; } + + [JsonProperty("Sk")] + public string Sk { get; set; } + + [JsonProperty("Cookies")] + public List Cookies { get; set; } +} + +public class BrowserAppCookie +{ + [JsonProperty("Name")] + public string Name { get; set; } + + [JsonProperty("Value")] + public string Value { get; set; } + + [JsonProperty("Path")] + public string Path { get; set; } + + [JsonProperty("Domain")] + public string Domain { get; set; } +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/CommonOperationResult.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/CommonOperationResult.cs index bb74d8cd..0f844bc0 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/CommonOperationResult.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/CommonOperationResult.cs @@ -13,4 +13,4 @@ public class CommonOperationResult [JsonProperty("status")] public int Status { get; set; } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/CreateFolderResult.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/CreateFolderResult.cs index b1b69566..0b28db1f 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/CreateFolderResult.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/CreateFolderResult.cs @@ -5,4 +5,4 @@ public class CreateFolderResult public bool IsSuccess { get; set; } public string Path{ get; set; } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/DownloadTokenResult.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/DownloadTokenResult.cs index fafcc191..9c8fa3ae 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/DownloadTokenResult.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/DownloadTokenResult.cs @@ -10,4 +10,4 @@ public class DownloadTokenBody public string Token { get; set; } } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/ItemOperation.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/ItemOperation.cs index 7f7f901a..a71bcd57 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/ItemOperation.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/ItemOperation.cs @@ -7,4 +7,4 @@ public class ItemOperation public DateTime DateTime { get; set; } public string Path { get; set; } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/LoginResult.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/LoginResult.cs index d79fe442..e0864a31 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/LoginResult.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/LoginResult.cs @@ -4,4 +4,4 @@ class LoginResult { public string Csrf { get; set; } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/PublishResult.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/PublishResult.cs index e4bcfd53..37b9d797 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/PublishResult.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/PublishResult.cs @@ -5,4 +5,4 @@ public class PublishResult public bool IsSuccess { get; set; } public string Url { get; set; } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/ShardType.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/ShardType.cs index b8f1540b..4a64b338 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/ShardType.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/ShardType.cs @@ -73,4 +73,4 @@ public enum ShardType [Description("thumbnails")] Thumbnails = 10 } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/UnpublishResult.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/UnpublishResult.cs index fd1ef057..0b05616f 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/UnpublishResult.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/UnpublishResult.cs @@ -4,4 +4,4 @@ public class UnpublishResult { public bool IsSuccess { get; set; } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/UploadFileResult.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/UploadFileResult.cs index 25f488ca..327ea4b6 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/UploadFileResult.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/UploadFileResult.cs @@ -10,4 +10,4 @@ public class UploadFileResult public bool HasReturnedData { get; set; } public bool NeedToAddFile { get; set; } = true; } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/ResolveFileConflictMethod.cs b/MailRuCloud/MailRuCloudApi/Base/ResolveFileConflictMethod.cs index e16aacd9..f9e352a5 100644 --- a/MailRuCloud/MailRuCloudApi/Base/ResolveFileConflictMethod.cs +++ b/MailRuCloud/MailRuCloudApi/Base/ResolveFileConflictMethod.cs @@ -44,4 +44,4 @@ public override int GetHashCode() return _value.GetHashCode(); } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/SplittedFile.cs b/MailRuCloud/MailRuCloudApi/Base/SplittedFile.cs index 23a4950c..05d1b788 100644 --- a/MailRuCloud/MailRuCloudApi/Base/SplittedFile.cs +++ b/MailRuCloud/MailRuCloudApi/Base/SplittedFile.cs @@ -8,7 +8,7 @@ public class SplittedFile : File { public SplittedFile(IList files) { - _fileHeader = files.First(f => !f.ServiceInfo.SplitInfo.IsPart); + _fileHeader = files.FirstOrDefault(f => !f.ServiceInfo.SplitInfo.IsPart); Files = files; Parts = files .Where(f => f.ServiceInfo.SplitInfo.IsPart) @@ -63,4 +63,4 @@ public override File New(string newFullPath) return spfile; } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/CacheStream.cs b/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/CacheStream.cs index ad03557a..edbb096c 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/CacheStream.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/CacheStream.cs @@ -58,4 +58,4 @@ public void Dispose() _cache?.Dispose(); } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/CacheType.cs b/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/CacheType.cs index a968f27a..25f4fb05 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/CacheType.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/CacheType.cs @@ -5,4 +5,4 @@ public enum CacheType Memory = 0, Disk = 1 } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/DataCache.cs b/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/DataCache.cs index 52394aec..16d29007 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/DataCache.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/DataCache.cs @@ -13,4 +13,4 @@ internal abstract class DataCache : IDisposable public abstract void Dispose(); } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/DeduplicateRule.cs b/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/DeduplicateRule.cs index 4c314c29..ad3c4c39 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/DeduplicateRule.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/DeduplicateRule.cs @@ -7,4 +7,4 @@ public struct DeduplicateRule public ulong MinSize { get; set; } public ulong MaxSize { get; set; } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/DeduplicateRulesBag.cs b/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/DeduplicateRulesBag.cs index 1649a5b4..8ece07ea 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/DeduplicateRulesBag.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/DeduplicateRulesBag.cs @@ -8,4 +8,4 @@ public class DeduplicateRulesBag public string DiskPath { get; set; } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/DiskDataCache.cs b/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/DiskDataCache.cs index 698b6671..a80af874 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/DiskDataCache.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/DiskDataCache.cs @@ -31,4 +31,4 @@ public override void Dispose() System.IO.File.Delete(_filename); } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/MemoryDataCache.cs b/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/MemoryDataCache.cs index 506249ac..b8a24a12 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/MemoryDataCache.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/MemoryDataCache.cs @@ -21,4 +21,4 @@ public override void Dispose() _ms?.Dispose(); } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Streams/DownloadStream.cs b/MailRuCloud/MailRuCloudApi/Base/Streams/DownloadStream.cs index 2e25e70c..3d216511 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Streams/DownloadStream.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Streams/DownloadStream.cs @@ -59,7 +59,7 @@ private async Task GetFileStream() { var totalLength = Length; long glostart = _start ?? 0; - long gloend = _end == null || + long gloend = _end == null || _start == _end && _end == 0 ? totalLength : _end.Value + 1; long fileStart = 0; @@ -74,7 +74,7 @@ private async Task GetFileStream() fileStart += file.OriginalSize; continue; } - + long clostart = Math.Max(0, glostart - fileStart); long cloend = gloend - fileStart - 1; diff --git a/MailRuCloud/MailRuCloudApi/Base/Streams/HttpClientFabric.cs b/MailRuCloud/MailRuCloudApi/Base/Streams/HttpClientFabric.cs index e1bd81d6..914f5b0e 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Streams/HttpClientFabric.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Streams/HttpClientFabric.cs @@ -16,8 +16,8 @@ public HttpClient this[Cloud cloud] var cli = _lockDict.GetOrAdd(cloud.Credentials, new HttpClient(new HttpClientHandler { UseProxy = true, - Proxy = cloud.RequestRepo.HttpSettings.Proxy, - CookieContainer = cloud.RequestRepo.Authenticator.Cookies, + Proxy = cloud.Settings.Proxy, + CookieContainer = cloud.RequestRepo.Auth.Cookies, UseCookies = true, AllowAutoRedirect = true, MaxConnectionsPerServer = int.MaxValue diff --git a/MailRuCloud/MailRuCloudApi/Base/Streams/RingBufferedStream.cs b/MailRuCloud/MailRuCloudApi/Base/Streams/RingBufferedStream.cs index 6eda80c1..f94e8767 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Streams/RingBufferedStream.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Streams/RingBufferedStream.cs @@ -72,7 +72,7 @@ public override long Position /// public override void Flush() { - if (_disposed) + if (_disposed) return; _flushed.Set(); @@ -464,10 +464,7 @@ public void Wait(CancellationToken cancellationToken, out bool canceled) GetWaitTask().Wait(cancellationToken); canceled = false; } - catch (Exception ex) - when (ex is OperationCanceledException - || (ex is AggregateException - && ex.InnerOf() != null)) + catch (Exception ex) when (ex.Contains()) { canceled = true; } @@ -584,10 +581,7 @@ public bool Wait( ret = GetWaitTask().Wait(millisecondsTimeout, cancellationToken); canceled = false; } - catch (Exception ex) - when (ex is OperationCanceledException - || (ex is AggregateException - && ex.InnerOf() != null)) + catch (Exception ex) when (ex.Contains()) { canceled = true; } diff --git a/MailRuCloud/MailRuCloudApi/Base/Streams/UploadStreamHttpClient.cs b/MailRuCloud/MailRuCloudApi/Base/Streams/UploadStreamHttpClient.cs index c19c3500..e9d3955b 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Streams/UploadStreamHttpClient.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Streams/UploadStreamHttpClient.cs @@ -20,7 +20,7 @@ protected UploadStreamHttpClient(string destinationPath, Cloud cloud, long size) { _cloud = cloud; _file = new File(destinationPath, size); - _cloudFileHasher = Repo.GetHasher(); + _cloudFileHasher = _cloud.RequestRepo.GetHasher(); Initialize(); } @@ -41,9 +41,9 @@ private void Upload() { try { - if (Repo.SupportsAddSmallFileByHash && _file.OriginalSize <= _cloudFileHasher.Length) // do not send upload request if file content fits to hash + if (_cloud.RequestRepo.SupportsAddSmallFileByHash && _file.OriginalSize <= _cloudFileHasher.Length) // do not send upload request if file content fits to hash UploadSmall(_ringBuffer); - else if (Repo.SupportsDeduplicate && _cloud.Settings.UseDeduplicate) // && !_file.ServiceInfo.IsCrypted) // && !_file.ServiceInfo.SplitInfo.IsPart) + else if (_cloud.RequestRepo.SupportsDeduplicate && _cloud.Settings.UseDeduplicate) // && !_file.ServiceInfo.IsCrypted) // && !_file.ServiceInfo.SplitInfo.IsPart) UploadCache(_ringBuffer); else UploadFull(_ringBuffer); @@ -76,7 +76,7 @@ private void UploadCache(Stream sourceStream) if (cache.Process()) { Logger.Debug($"Uploading [{cache.DataCacheName}] {_file.FullPath}"); - + OnFileStreamSent(); _file.Hash = _cloudFileHasher.Hash; @@ -115,8 +115,7 @@ private void UploadFull(Stream sourceStream, bool doInvokeFileStreamSent = true) }); var client = HttpClientFabric.Instance[_cloud]; - var uploadFileResult = Repo.DoUpload(client, pushContent, _file).Result; - + var uploadFileResult = _cloud.RequestRepo.DoUpload(client, pushContent, _file).Result; if (uploadFileResult.HttpStatusCode != HttpStatusCode.Created && uploadFileResult.HttpStatusCode != HttpStatusCode.OK) @@ -145,9 +144,13 @@ private void UploadFull(Stream sourceStream, bool doInvokeFileStreamSent = true) public override void Write(byte[] buffer, int offset, int count) { - if (CheckHashes || - (_cloudFileHasher != null && Repo.SupportsAddSmallFileByHash && _file.OriginalSize <= _cloudFileHasher.Length) ) + if (CheckHashes || + (_cloudFileHasher != null && + _cloud.RequestRepo.SupportsAddSmallFileByHash && + _file.OriginalSize <= _cloudFileHasher.Length)) + { _cloudFileHasher?.Append(buffer, offset, count); + } _ringBuffer.Write(buffer, offset, count); } @@ -166,7 +169,7 @@ protected override void Dispose(bool disposing) } - finally + finally { _ringBuffer?.Dispose(); _cloudFileHasher?.Dispose(); @@ -176,7 +179,6 @@ protected override void Dispose(bool disposing) private readonly Cloud _cloud; private readonly File _file; - private IRequestRepo Repo => _cloud.RequestRepo; private readonly ICloudHasher _cloudFileHasher; private Task _uploadTask; private readonly RingBufferedStream _ringBuffer = new(65536); @@ -209,4 +211,4 @@ public override int Read(byte[] buffer, int offset, int count) throw new NotImplementedException(); } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Streams/UploadStreamHttpClientV2.cs b/MailRuCloud/MailRuCloudApi/Base/Streams/UploadStreamHttpClientV2.cs index 767d699b..6927cecb 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Streams/UploadStreamHttpClientV2.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Streams/UploadStreamHttpClientV2.cs @@ -49,7 +49,7 @@ // // var guid = Guid.NewGuid(); // // var content = new MultipartFormDataContent($"----{guid}"); -// // var boundaryValue = content.Headers.ContentType.Parameters.First(p => p.Name == "boundary"); +// // var boundaryValue = content.Headers.ContentType.Parameters.FirstOrDefault(p => p.Name == "boundary"); // // boundaryValue.Value = boundaryValue.Value.Replace("\"", String.Empty); // // _pushContent = new PushStreamContent((stream, httpContent, arg3) => diff --git a/MailRuCloud/MailRuCloudApi/Base/Streams/UploadStreamHttpWebRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Streams/UploadStreamHttpWebRequest.cs index 2e2c6e55..08107ddc 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Streams/UploadStreamHttpWebRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Streams/UploadStreamHttpWebRequest.cs @@ -115,7 +115,7 @@ // throw; // } // #endif -// finally +// finally // { // _ringBuffer?.Dispose(); // _sha1?.Dispose(); diff --git a/MailRuCloud/MailRuCloudApi/Cloud.cs b/MailRuCloud/MailRuCloudApi/Cloud.cs index 49c40119..4a61278f 100644 --- a/MailRuCloud/MailRuCloudApi/Cloud.cs +++ b/MailRuCloud/MailRuCloudApi/Cloud.cs @@ -21,8 +21,6 @@ namespace YaR.Clouds { - //TODO: not thread-safe, refact - /// /// Cloud client. /// @@ -51,8 +49,8 @@ public partial class Cloud : IDisposable /// private readonly EntryCache _entryCache; - internal IRequestRepo RequestRepo { get; } - internal Credentials Credentials { get; } + internal IRequestRepo RequestRepo{ get; } + public Credentials Credentials { get; } public AccountInfoResult AccountInfo { get; private set; } = null; @@ -68,11 +66,46 @@ public Cloud(CloudSettings settings, Credentials credentials) if (!Credentials.IsAnonymous) { - AccountInfo = RequestRepo.AccountInfo().Result - ?? throw new AuthenticationException("Auth token hasn't been retrieved."); + try + { + AccountInfo = RequestRepo.AccountInfo().Result + ?? throw new AuthenticationException("The cloud server rejected the credentials provided"); + } + catch (Exception e) when (e.OfType().Any()) + { + Logger.Warn("Refresh credentials"); + + try + { + Credentials.Refresh(); + } + catch (Exception e2) when (e2.OfType().Any()) + { + Exception ae = e2.OfType().FirstOrDefault(); + Logger.Error("Failed to refresh credentials"); + throw new AuthenticationException( + "The cloud server rejected the credentials provided. Then failed to refresh credentials.", ae); + } + + // Проверка результата + try + { + AccountInfo = RequestRepo.AccountInfo().Result + ?? throw new AuthenticationException("The cloud server rejected the credentials provided"); + } + catch (Exception e2) when (e2.OfType().Any()) + { + Exception ae = e2.OfType().FirstOrDefault(); + Logger.Error("The server rejected the credentials provided"); + throw new AuthenticationException( + "The cloud server rejected the credentials provided. " + + "Credentials have been updated. " + + "Then the server rejected the credentials again. ", ae); + } + } } - _entryCache = new EntryCache(TimeSpan.FromSeconds(settings.CacheListingSec), RequestRepo.ActiveOperationsAsync); + _entryCache = new EntryCache(TimeSpan.FromSeconds(settings.CacheListingSec), RequestRepo.DetectOutsideChanges); ////TODO: wow very dummy linking, refact cache realization globally! //_itemCache = new ItemCache(TimeSpan.FromSeconds(settings.CacheListingSec)); @@ -230,7 +263,7 @@ private async Task GetItemInternalAsync(string path, bool resolveLinks, //if (itemType == ItemType.Unknown && ulink is not null) // itemType = ulink.ItemType; - // TODO: cache (parent) folder for file + // TODO: cache (parent) folder for file //if (itemType == ItemType.File) //{ // var cachefolder = datares.ToFolder(path, ulink); @@ -258,8 +291,8 @@ private async Task GetItemInternalAsync(string path, bool resolveLinks, //if (itemType == ItemType.Unknown) - // itemType = cloudResult is Folder - // ? ItemType.Folder + // itemType = cloudResult is Folder + // ? ItemType.Folder // : ItemType.File; //if (itemType == ItemType.Folder && cloudResult is Folder folder) // fill folder with links if any @@ -474,7 +507,7 @@ public async Task Unpublish(File file) { foreach (var innerFile in file.Files) { - await Unpublish(innerFile.GetPublicLinks(this).First().Uri, innerFile.FullPath); + await Unpublish(innerFile.GetPublicLinks(this).FirstOrDefault().Uri, innerFile.FullPath); innerFile.PublicLinks.Clear(); } _entryCache.OnRemoveTreeAsync(file.FullPath, GetItemAsync(file.FullPath, fastGetFromCloud: true)); @@ -1015,7 +1048,7 @@ public virtual async Task Remove(File file, bool removeShareDescription = switch (entry) { case Folder folder: - await Unpublish(folder.GetPublicLinks(this).First().Uri, folder.FullPath); + await Unpublish(folder.GetPublicLinks(this).FirstOrDefault().Uri, folder.FullPath); break; case File ifile: await Unpublish(ifile); @@ -1112,8 +1145,6 @@ public void AbortAllAsyncThreads() CancelToken.Cancel(false); } - public IRequestRepo Repo => RequestRepo; - /// /// Create folder on the server. /// @@ -1280,7 +1311,8 @@ protected virtual void Dispose(bool disposing) if (_disposedValue) return; if (disposing) { - CancelToken.Dispose(); + _entryCache?.Dispose(); + CancelToken?.Dispose(); } _disposedValue = true; } @@ -1379,7 +1411,7 @@ public async Task CryptInit(Folder folder) public void CleanTrash() { - Repo.CleanTrash(); + RequestRepo.CleanTrash(); } } } diff --git a/MailRuCloud/MailRuCloudApi/CloudSettings.cs b/MailRuCloud/MailRuCloudApi/CloudSettings.cs index 62aa9fae..911671a5 100644 --- a/MailRuCloud/MailRuCloudApi/CloudSettings.cs +++ b/MailRuCloud/MailRuCloudApi/CloudSettings.cs @@ -31,14 +31,16 @@ public int ListDepth public SharedVideoResolution DefaultSharedVideoResolution { get; set; } = SharedVideoResolution.All; public IWebProxy Proxy { get; set; } public bool UseLocks { get; set; } - + public bool UseDeduplicate { get; set; } public DeduplicateRulesBag DeduplicateRules { get; set; } public bool DisableLinkManager { get; set; } - #region Timeouts + public int CloudInstanceTimeoutMinutes { get; set; } + + #region Connection timeouts public int Wait100ContinueTimeoutSec { get; set; } public int WaitResponseTimeoutSec { get; set; } diff --git a/MailRuCloud/MailRuCloudApi/Common/EntryCache.cs b/MailRuCloud/MailRuCloudApi/Common/EntryCache.cs index 868bf0f0..f8712820 100644 --- a/MailRuCloud/MailRuCloudApi/Common/EntryCache.cs +++ b/MailRuCloud/MailRuCloudApi/Common/EntryCache.cs @@ -11,7 +11,7 @@ namespace YaR.Clouds.Common; -public class EntryCache +public class EntryCache : IDisposable { public enum GetState { @@ -37,8 +37,8 @@ public enum GetState private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(EntryCache)); - private static readonly TimeSpan _minCleanUpInterval = new TimeSpan(0, 0, 10 /* секунды */ ); - private static readonly TimeSpan _maxCleanUpInterval = new TimeSpan(0, 10 /* минуты */, 0); + //private static readonly TimeSpan _minCleanUpInterval = new TimeSpan(0, 0, 10 /* секунды */ ); + //private static readonly TimeSpan _maxCleanUpInterval = new TimeSpan(0, 10 /* минуты */, 0); // По умолчанию очистка кеша от устаревших записей производится каждые 30 секунд private TimeSpan _cleanUpPeriod = TimeSpan.FromSeconds(30); @@ -47,7 +47,7 @@ public enum GetState public bool IsCacheEnabled { get; private set; } - private readonly Timer _cleanTimer; + private readonly System.Timers.Timer _cleanTimer; private readonly ConcurrentDictionary _root = new(StringComparer.InvariantCultureIgnoreCase); @@ -56,7 +56,7 @@ public enum GetState public delegate Task CheckOperations(); private readonly CheckOperations _activeOperationsAsync; - private readonly Timer _checkActiveOperationsTimer; + private readonly System.Timers.Timer _checkActiveOperationsTimer; // Проверка активных операций на сервере и внешних изменений в облаке не через сервис // производится каждые 24 секунды, число не кратное очистке кеша, чтобы не пересекались // алгоритмы. @@ -95,7 +95,7 @@ internal class CacheItem : AllDescendantsInCache ? "<>" : "<>")}" - + $", Since {CreationTime:HH:mm:ss} {Entry.FullPath}"; + + $", Since {CreationTime:HH:mm:ss} {Entry?.FullPath}"; } public EntryCache(TimeSpan expirePeriod, CheckOperations activeOperationsAsync) @@ -110,17 +110,55 @@ public EntryCache(TimeSpan expirePeriod, CheckOperations activeOperationsAsync) if (IsCacheEnabled) { - _cleanTimer = new Timer(_ => RemoveExpired(), null, _cleanUpPeriod, _cleanUpPeriod); + _cleanTimer = new System.Timers.Timer() + { + Interval = _cleanUpPeriod.TotalMilliseconds, + Enabled = true, + AutoReset = true + }; + _cleanTimer.Elapsed += RemoveExpired; if (_activeOperationsAsync is not null && expirePeriod.TotalMinutes >= 1) { // Если кеш достаточно длительный, делаются регулярные // проверки на изменения в облаке - _checkActiveOperationsTimer = new Timer(_ => CheckActiveOps(), null, _opCheckPeriod, _opCheckPeriod); + _checkActiveOperationsTimer = new System.Timers.Timer() + { + Interval = _opCheckPeriod.TotalMilliseconds, + Enabled = true, + AutoReset = true + }; + _checkActiveOperationsTimer.Elapsed += CheckActiveOps; } } } + #region IDisposable Support + private bool _disposedValue; + + protected virtual void Dispose(bool disposing) + { + if (_disposedValue) return; + if (disposing) + { + _checkActiveOperationsTimer?.Stop(); + _checkActiveOperationsTimer?.Dispose(); + _cleanTimer?.Stop(); + _cleanTimer?.Dispose(); + Clear(); + _locker?.Dispose(); + + Logger.Debug("EntryCache disposed"); + } + _disposedValue = true; + } + + public void Dispose() + { + Dispose(true); + } + #endregion + //public TimeSpan CleanUpPeriod //{ // get => _cleanUpPeriod; @@ -142,9 +180,10 @@ public EntryCache(TimeSpan expirePeriod, CheckOperations activeOperationsAsync) // } //} - public int RemoveExpired() + private void RemoveExpired(object sender, System.Timers.ElapsedEventArgs e) { - if (_root.IsEmpty) return 0; + if (_root.IsEmpty) + return; var watch = Stopwatch.StartNew(); @@ -189,7 +228,7 @@ public int RemoveExpired() Logger.Debug($"Items cache clean: removed {removedCount} expired " + $"items, {partiallyExpiredCount} marked partially expired ({watch.ElapsedMilliseconds} ms)"); - return removedCount; + return; } public void ResetCheck() @@ -208,7 +247,7 @@ public void ResetCheck() } } - public async void CheckActiveOps() + private async void CheckActiveOps(object sender, System.Timers.ElapsedEventArgs e) { CheckUpInfo info = await _activeOperationsAsync(); if (info is null) @@ -585,7 +624,7 @@ parentEntry.Entry is Folder fld && * Содержимое папки берется не из этого списка, а собирается из кеша по path всех entry. * В кеше у папок Descendants всегда = ImmutableList.Empty */ - if (removed) + if (removed && cachedItem?.Entry is not null) { if (cachedItem.Entry.IsFile) { diff --git a/MailRuCloud/MailRuCloudApi/Common/ItemCache.cs b/MailRuCloud/MailRuCloudApi/Common/ItemCache.cs deleted file mode 100644 index 5a6b1ccd..00000000 --- a/MailRuCloud/MailRuCloudApi/Common/ItemCache.cs +++ /dev/null @@ -1,164 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Threading; - -/* - * ВНИМАНИЕ! Файл выключен из компиляции! - */ - -namespace YaR.Clouds.Common -{ - public class ItemCache - { - private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(ItemCache)); - - private static readonly TimeSpan _minCleanUpInterval = new TimeSpan(0, 0, 20 /* секунды */ ); - private static readonly TimeSpan _maxCleanUpInterval = new TimeSpan(0, 10 /* минуты */, 0); - - // По умолчанию очистка кеша от устаревших записей производится каждые 5 минут - private TimeSpan _cleanUpPeriod = TimeSpan.FromMinutes(5); - - private readonly TimeSpan _expirePeriod; - - private readonly bool _noCache; - - private readonly Timer _cleanTimer; - private readonly ConcurrentDictionary> _items = new(); - //private readonly object _locker = new object(); - - public ItemCache(TimeSpan expirePeriod) - { - _expirePeriod = expirePeriod; - _noCache = Math.Abs(_expirePeriod.TotalMilliseconds) < 0.001; - - long cleanPeriod = (long)CleanUpPeriod.TotalMilliseconds; - - // if there is no cache, we don't need clean up timer - _cleanTimer = _noCache ? null : new Timer(_ => RemoveExpired(), null, cleanPeriod, cleanPeriod); - } - - public TimeSpan CleanUpPeriod - { - get => _cleanUpPeriod; - set - { - // Очистку кеша от устаревших записей не следует проводить часто чтобы не нагружать машину, - // и не следует проводить редко, редко, чтобы не натыкаться постоянно на устаревшие записи. - _cleanUpPeriod = value < _minCleanUpInterval - ? _minCleanUpInterval - : value > _maxCleanUpInterval - ? _maxCleanUpInterval - : value; - - if (!_noCache) - { - long cleanPreiod = (long)_cleanUpPeriod.TotalMilliseconds; - _cleanTimer.Change(cleanPreiod, cleanPreiod); - } - } - } - - public int RemoveExpired() - { - if (_items.IsEmpty) return 0; - - DateTime threshold = DateTime.Now - _expirePeriod; - int removedCount = 0; - foreach (var item in _items) - if (item.Value.Created <= threshold) - if (_items.TryRemove(item.Key, out _)) - removedCount++; - - if (removedCount > 0) - Logger.Debug($"Items cache clean: removed {removedCount} expired items"); - - return removedCount; - } - - public TValue Get(TKey key) - { - if (_noCache) - return default; - - if (!_items.TryGetValue(key, out var item)) - return default; - - if (IsExpired(item)) - _items.TryRemove(key, out _); - else - { - Logger.Debug($"Cache hit: {key}"); - return item.Item; - } - return default; - } - - public void Add(TKey key, TValue value) - { - if (_noCache) return; - - var item = new TimedItem - { - Created = DateTime.Now, - Item = value - }; - - _items.AddOrUpdate(key, item, (_, _) => item); - } - - public void Add(IEnumerable> items) - { - foreach (var item in items) - Add(item.Key, item.Value); - } - - public TValue Invalidate(TKey key) - { - _items.TryRemove(key, out var item); - return item != null ? item.Item : default; - } - - public void Invalidate() - { - _items.Clear(); - } - - public void Invalidate(params TKey[] keys) - { - Invalidate(keys.AsEnumerable()); - } - - internal void Invalidate(IEnumerable keys) - { - foreach (var key in keys) - _items.TryRemove(key, out _); - } - - public void Forget(TKey whoKey, TKey whomKey) - { - Invalidate(whomKey); - - var who = Get(whoKey) as ICanForget; - who?.Forget(whomKey); - } - - private bool IsExpired(TimedItem item) - { - DateTime threshold = DateTime.Now - _expirePeriod; - return item.Created <= threshold; - } - - private class TimedItem - { - public DateTime Created { get; set; } - public T Item { get; set; } - } - } - - public interface ICanForget - { - void Forget(object whomKey); - } -} diff --git a/MailRuCloud/MailRuCloudApi/Common/SharedVideoResolution.cs b/MailRuCloud/MailRuCloudApi/Common/SharedVideoResolution.cs index 541ab0dc..fc9ee677 100644 --- a/MailRuCloud/MailRuCloudApi/Common/SharedVideoResolution.cs +++ b/MailRuCloud/MailRuCloudApi/Common/SharedVideoResolution.cs @@ -17,4 +17,4 @@ public enum SharedVideoResolution [EnumMember(Value = "1080p")] R1080 } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/CryptFileInfo.cs b/MailRuCloud/MailRuCloudApi/CryptFileInfo.cs index 99f3cf01..1f56afc1 100644 --- a/MailRuCloud/MailRuCloudApi/CryptFileInfo.cs +++ b/MailRuCloud/MailRuCloudApi/CryptFileInfo.cs @@ -13,4 +13,4 @@ public class CryptFileInfo public DateTime Initialized { get; set; } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Extensions/EnumExtensions.cs b/MailRuCloud/MailRuCloudApi/Extensions/EnumExtensions.cs index ee5d2650..f99c05f9 100644 --- a/MailRuCloud/MailRuCloudApi/Extensions/EnumExtensions.cs +++ b/MailRuCloud/MailRuCloudApi/Extensions/EnumExtensions.cs @@ -19,7 +19,7 @@ public static T ParseEnumMemberValue(string stringValue, bool ignoreCase = tr if (fi.GetCustomAttributes(typeof (EnumMemberAttribute), false) is EnumMemberAttribute[] { Length: > 0 } attrs) enumStringValue = attrs[0].Value; - if (string.Compare(enumStringValue, stringValue, ignoreCase) != 0) + if (string.Compare(enumStringValue, stringValue, ignoreCase) != 0) continue; output = (T)Enum.Parse(type, fi.Name); @@ -37,8 +37,8 @@ public static string ToEnumMemberValue(this Enum @enum) .OfType(). FirstOrDefault(); - return attr == null - ? @enum.ToString() + return attr == null + ? @enum.ToString() : attr.Value; } } diff --git a/MailRuCloud/MailRuCloudApi/Extensions/Extensions.cs b/MailRuCloud/MailRuCloudApi/Extensions/Extensions.cs index df9eb56b..136f9269 100644 --- a/MailRuCloud/MailRuCloudApi/Extensions/Extensions.cs +++ b/MailRuCloud/MailRuCloudApi/Extensions/Extensions.cs @@ -1,11 +1,7 @@ using System; using System.Collections.Generic; -using System.IO; -using System.Net; -using System.Threading; -using System.Threading.Tasks; +using System.Linq; using YaR.Clouds.Base; -using YaR.Clouds.Common; namespace YaR.Clouds.Extensions { @@ -143,5 +139,137 @@ public static Exception InnerOf(this Exception ex, Type t) ex = ex.InnerException; } } + + public static bool ContainsIgnoreCase(this string stringSearchIn, string stringToSearchFor, + StringComparison comparisonType = StringComparison.InvariantCultureIgnoreCase) + { +#if NET48 + System.Globalization.CultureInfo cu = comparisonType == StringComparison.InvariantCultureIgnoreCase + ? System.Globalization.CultureInfo.InvariantCulture + : System.Globalization.CultureInfo.CurrentCulture; + return cu.CompareInfo.IndexOf(stringSearchIn, stringToSearchFor) >= 0; +#else + return stringSearchIn.Contains(stringToSearchFor, comparisonType); +#endif + } + + /// Finds all exception of the requested type. + /// The type of exception to look for. + /// The exception to look in. + /// + /// + /// The enumeration of exceptions matching the specified type or null if not found. + /// + /// + /// + public static IEnumerable OfType(this Exception exception) where T : Exception + { + return exception.OfType(false); + } + + /// Finds all exception of the requested type. + /// The type of exception to look for. + /// The exception to look in. + /// + /// + /// If throwIfNotNotFound=false, + /// the enumeration of exceptions matching the specified type or null if not found. + /// + /// + /// If throwIfNotNotFound=true, + /// the enumeration of exceptions matching the specified type or throws the flatten AggregateException if not found. + /// + /// + public static IEnumerable OfType(this Exception exception, bool throwIfNotNotFound = false) where T : Exception + { + if (exception is AggregateException ae) + { + AggregateException flatten = ae.Flatten(); + IEnumerable result = flatten.InnerExceptions.OfType(); + if (throwIfNotNotFound && !result.Any()) + throw flatten; + return result; + } + else + { + if (exception is T item) + { + T[] array = new T[] { item }; + return array; + } + + if (throwIfNotNotFound) + throw exception; + } + return Enumerable.Empty(); + } + + /// Finds first occurrence of exception of the requested type. + /// The type of exception to look for. + /// The exception to look in. + /// + /// + /// The exception matching the specified type or null if not found. + /// + /// + public static T FirstOfType(this Exception exception) where T : Exception + { + return exception.FirstOfType(false); + } + + /// Finds first occurrence of exception of the requested type. + /// The type of exception to look for. + /// The exception to look in. + /// + /// + /// If throwIfNotNotFound=false, + /// the exception matching the specified type or null if not found. + /// + /// + /// If throwIfNotNotFound=true, + /// the exceptions matching the specified type or throws the flatten AggregateException if not found. + /// + /// + public static T FirstOfType(this Exception exception, bool throwIfNotNotFound = false) where T : Exception + { + if (exception is AggregateException ae) + { + AggregateException flatten = ae.Flatten(); + T result = (T)flatten.InnerExceptions.FirstOrDefault(x => x is T); + if (throwIfNotNotFound && result is null) + throw flatten; + return result; + } + else + { + if (exception is T item) + { + return item; + } + + if (throwIfNotNotFound) + throw exception; + } + return null; + } + + /// Searches through all inner exceptions for exception of the requested type and returns true if found. + /// The type of exception to look for. + /// The exception to look in. + /// + /// + /// True if the exception of the requested type is found, otherwise False. + /// + /// + public static bool Contains(this Exception exception) where T : Exception + { + if (exception is AggregateException ae) + { + AggregateException flatten = ae.Flatten(); + if (flatten.InnerExceptions.Any(x => x is T)) + return true; + } + return exception is T; + } } } diff --git a/MailRuCloud/MailRuCloudApi/FileUploadedDelegate.cs b/MailRuCloud/MailRuCloudApi/FileUploadedDelegate.cs index 183979a5..3b7f4e2c 100644 --- a/MailRuCloud/MailRuCloudApi/FileUploadedDelegate.cs +++ b/MailRuCloud/MailRuCloudApi/FileUploadedDelegate.cs @@ -4,4 +4,4 @@ namespace YaR.Clouds { public delegate void FileUploadedDelegate(IEnumerable file); -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Links/Dto/ItemLink.cs b/MailRuCloud/MailRuCloudApi/Links/Dto/ItemLink.cs index 5cfa5e48..8e07f7cd 100644 --- a/MailRuCloud/MailRuCloudApi/Links/Dto/ItemLink.cs +++ b/MailRuCloud/MailRuCloudApi/Links/Dto/ItemLink.cs @@ -11,4 +11,4 @@ public class ItemLink public long Size { get; set; } public DateTime? CreationDate { get; set; } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Links/Dto/ItemList.cs b/MailRuCloud/MailRuCloudApi/Links/Dto/ItemList.cs index 4e902c2b..b579353e 100644 --- a/MailRuCloud/MailRuCloudApi/Links/Dto/ItemList.cs +++ b/MailRuCloud/MailRuCloudApi/Links/Dto/ItemList.cs @@ -6,4 +6,4 @@ public class ItemList { public List Items { get; } = new(); } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Links/Link.cs b/MailRuCloud/MailRuCloudApi/Links/Link.cs index 6ae03882..d2b98223 100644 --- a/MailRuCloud/MailRuCloudApi/Links/Link.cs +++ b/MailRuCloud/MailRuCloudApi/Links/Link.cs @@ -89,6 +89,5 @@ public ConcurrentDictionary PublicLinks public FileSize Size { get; set; } public DateTime CreationTimeUtc { get; set; } - } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Links/LinkManager.cs b/MailRuCloud/MailRuCloudApi/Links/LinkManager.cs index 827f9aeb..ca2af920 100644 --- a/MailRuCloud/MailRuCloudApi/Links/LinkManager.cs +++ b/MailRuCloud/MailRuCloudApi/Links/LinkManager.cs @@ -117,7 +117,7 @@ public async void Load() { f.MapTo = WebDavPath.Clean(f.MapTo); if (!f.Href.IsAbsoluteUri) - f.Href = new Uri(_cloud.Repo.PublicBaseUrlDefault + f.Href); + f.Href = new Uri(_cloud.RequestRepo.PublicBaseUrlDefault + f.Href); } } } @@ -249,9 +249,9 @@ public async Task RemoveDeadLinks(bool doWriteHistory) // var entry = _cloud.GetItem(path).Result; // return entry is not null; // } - // catch (AggregateException e) + // catch (AggregateException e) // when ( // let's check if there really no file or just other network error - // e.InnerException is WebException we && + // e.InnerException is WebException we && // (we.Response as HttpWebResponse)?.StatusCode == HttpStatusCode.NotFound // ) // { @@ -260,7 +260,7 @@ public async Task RemoveDeadLinks(bool doWriteHistory) //} /// - /// + /// /// /// /// Resolving file/folder type requires addition request to cloud @@ -330,7 +330,7 @@ private async Task ResolveLink(Link link) //public IEnumerable GetChildren(string folderFullPath, bool doResolveType) //{ // var lst = _itemList.Items - // .Where(it => + // .Where(it => // WebDavPath.IsParentOrSame(folderFullPath, it.MapTo)); // return lst; @@ -393,7 +393,7 @@ public async Task Add(Uri url, string path, string name, bool isFile, long // foreach (string pbu in _cloud.RequestRepo.PublicBaseUrls) // { // if (!string.IsNullOrEmpty(pbu)) - // if (url.StartsWith(pbu)) + // if (url.StartsWith(pbu)) // return url.Remove(pbu.Length); // } // return url; @@ -444,14 +444,14 @@ public bool RenameLink(Link link, string newName) /// /// Сохранить изменения в файл в облаке /// - /// + /// /// Корневую ссылку просто перенесем - /// + /// /// Если это вложенная ссылка, то перенести ее нельзя, а можно /// 1. сделать новую ссылку на эту вложенность /// 2. скопировать содержимое /// если следовать логике, что при копировании мы копируем содержимое ссылок, а при перемещении - перемещаем ссылки, то надо делать новую ссылку - /// + /// /// Логика хороша, но /// некоторые клиенты сначала делают структуру каталогов, а потом по одному переносят файлы, например, TotalCommander c плагином WebDAV v.2.9 /// в таких условиях на каждый файл получится свой собственный линк, если делать правильно, т.е. в итоге расплодится миллион линков diff --git a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/CleanTrashCommand.cs b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/CleanTrashCommand.cs index b2845d4c..fb9e6f85 100644 --- a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/CleanTrashCommand.cs +++ b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/CleanTrashCommand.cs @@ -8,7 +8,7 @@ namespace YaR.Clouds.SpecialCommands.Commands /// public class CleanTrashCommand : SpecialCommand { - public CleanTrashCommand(Cloud cloud, string path, IList parames) : base(cloud, path, parames) + public CleanTrashCommand(Cloud cloud, string path, IList parameters) : base(cloud, path, parameters) { } @@ -16,9 +16,9 @@ public CleanTrashCommand(Cloud cloud, string path, IList parames) : base public override async Task Execute() { - Cloud.CleanTrash(); + _cloud.CleanTrash(); return await Task.FromResult(SpecialCommandResult.Success); } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/CopyCommand.cs b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/CopyCommand.cs index 63e9dfd7..a479e6ff 100644 --- a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/CopyCommand.cs +++ b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/CopyCommand.cs @@ -6,7 +6,7 @@ namespace YaR.Clouds.SpecialCommands.Commands { public class CopyCommand : SpecialCommand { - public CopyCommand(Cloud cloud, string path, IList parames) : base(cloud, path, parames) + public CopyCommand(Cloud cloud, string path, IList parameters) : base(cloud, path, parameters) { } @@ -14,12 +14,12 @@ public CopyCommand(Cloud cloud, string path, IList parames) : base(cloud public override async Task Execute() { - string source = WebDavPath.Clean(Parames.Count == 1 ? Path : Parames[0]); - string target = WebDavPath.Clean(Parames.Count == 1 ? Parames[0] : Parames[1]); + string source = WebDavPath.Clean(_parameters.Count == 1 ? _path : _parameters[0]); + string target = WebDavPath.Clean(_parameters.Count == 1 ? _parameters[0] : _parameters[1]); - var res = await Cloud.Copy(source, target); + var res = await _cloud.Copy(source, target); return new SpecialCommandResult(res); } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/CryptInitCommand.cs b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/CryptInitCommand.cs index d451b800..7b1bdec5 100644 --- a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/CryptInitCommand.cs +++ b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/CryptInitCommand.cs @@ -19,20 +19,20 @@ public CryptInitCommand(Cloud cloud, string path, IList parameters) public override async Task Execute() { string path; - string param = Parames.Count == 0 ? string.Empty : Parames[0].Replace("\\", WebDavPath.Separator); + string param = _parameters.Count == 0 ? string.Empty : _parameters[0].Replace("\\", WebDavPath.Separator); - if (Parames.Count == 0) - path = Path; + if (_parameters.Count == 0) + path = _path; else if (param.StartsWith(WebDavPath.Separator)) path = param; else - path = WebDavPath.Combine(Path, param); + path = WebDavPath.Combine(_path, param); - var entry = await Cloud.GetItemAsync(path); + var entry = await _cloud.GetItemAsync(path); if (entry is null || entry.IsFile) return SpecialCommandResult.Fail; - var res = await Cloud.CryptInit((Folder)entry); + var res = await _cloud.CryptInit((Folder)entry); return new SpecialCommandResult(res); } } diff --git a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/CryptPasswdCommand.cs b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/CryptPasswdCommand.cs index 9778bfcb..0d1c13c6 100644 --- a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/CryptPasswdCommand.cs +++ b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/CryptPasswdCommand.cs @@ -8,7 +8,7 @@ namespace YaR.Clouds.SpecialCommands.Commands /// public class CryptPasswdCommand : SpecialCommand { - public CryptPasswdCommand(Cloud cloud, string path, IList parames) : base(cloud, path, parames) + public CryptPasswdCommand(Cloud cloud, string path, IList parameters) : base(cloud, path, parameters) { } @@ -16,13 +16,13 @@ public CryptPasswdCommand(Cloud cloud, string path, IList parames) : bas public override async Task Execute() { - var newPasswd = Parames[0]; + var newPasswd = _parameters[0]; if (string.IsNullOrEmpty(newPasswd)) return await Task.FromResult(new SpecialCommandResult(false, "Crypt password is empty")); - Cloud.Credentials.PasswordCrypt = newPasswd; + _cloud.Credentials.PasswordCrypt = newPasswd; return await Task.FromResult(SpecialCommandResult.Success); } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/DeleteCommand.cs b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/DeleteCommand.cs index 8e157923..8a0344f5 100644 --- a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/DeleteCommand.cs +++ b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/DeleteCommand.cs @@ -16,20 +16,20 @@ public DeleteCommand(Cloud cloud, string path, IList parameters) public override async Task Execute() { string path; - string param = Parames.Count == 0 ? string.Empty : Parames[0].Replace("\\", WebDavPath.Separator); + string param = _parameters.Count == 0 ? string.Empty : _parameters[0].Replace("\\", WebDavPath.Separator); - if (Parames.Count == 0) - path = Path; + if (_parameters.Count == 0) + path = _path; else if (param.StartsWith(WebDavPath.Separator)) path = param; else - path = WebDavPath.Combine(Path, param); + path = WebDavPath.Combine(_path, param); - var entry = await Cloud.GetItemAsync(path); + var entry = await _cloud.GetItemAsync(path); if (entry is null) return SpecialCommandResult.Fail; - var res = await Cloud.Remove(entry); + var res = await _cloud.Remove(entry); return new SpecialCommandResult(res); } } diff --git a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/FishCommand.cs b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/FishCommand.cs index f4fdfc40..cff49b91 100644 --- a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/FishCommand.cs +++ b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/FishCommand.cs @@ -26,7 +26,7 @@ public FishCommand(Cloud cloud, string path, IList parameters) : base(cl public override async Task Execute() { const string name = "FISHA.YEA"; - string target = WebDavPath.Combine(Path, name); + string target = WebDavPath.Combine(_path, name); var randomHash = new byte[20]; Random.NextBytes(randomHash); @@ -37,7 +37,7 @@ public override async Task Execute() { //var res = await new CreateFileRequest(Cloud.CloudApi, target, strRandomHash, randomSize, ConflictResolver.Rename).MakeRequestAsync(_connectionLimiter); var hash = new FileHashMrc(randomHash); - var res = await Cloud.RequestRepo.AddFile(target, hash, randomSize, DateTime.Now, ConflictResolver.Rename); + var res = await _cloud.RequestRepo.AddFile(target, hash, randomSize, DateTime.Now, ConflictResolver.Rename); if (res.Success) { Logger.Warn("╔╗╔╗╔╦══╦╗╔╗╔╗╔╦╦╗"); @@ -74,11 +74,11 @@ public override async Task Execute() if (string.IsNullOrEmpty(content)) content = @"Maybe next time ¯\_(ツ)_/¯"; - Cloud.UploadFile(WebDavPath.Combine(Path, $"{DateTime.Now:yyyy-MM-dd hh-mm-ss} Not today, dude.txt"), content); + _cloud.UploadFile(WebDavPath.Combine(_path, $"{DateTime.Now:yyyy-MM-dd hh-mm-ss} Not today, dude.txt"), content); } return SpecialCommandResult.Success; } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/JoinCommand.cs b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/JoinCommand.cs index eb508cb7..f927fbdf 100644 --- a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/JoinCommand.cs +++ b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/JoinCommand.cs @@ -30,17 +30,17 @@ public partial class JoinCommand: SpecialCommand public JoinCommand(Cloud cloud, string path, IList parameters): base(cloud, path, parameters) { - var m = s_commandRegex.Match(Parames[0]); + var m = s_commandRegex.Match(_parameters[0]); if (m.Success) //join by shared link - _func = () => ExecuteByLink(Path, m.Groups["data"].Value); + _func = () => ExecuteByLink(_path, m.Groups["data"].Value); else { - var mhash = s_hashRegex.Match(Parames[0]); - var msize = s_sizeRegex.Match(Parames[1]); - if (mhash.Success && msize.Success && Parames.Count == 3) //join by hash and size + var mhash = s_hashRegex.Match(_parameters[0]); + var msize = s_sizeRegex.Match(_parameters[1]); + if (mhash.Success && msize.Success && _parameters.Count == 3) //join by hash and size { - _func = () => ExecuteByHash(Path, mhash.Groups["data"].Value, long.Parse(Parames[1]), Parames[2]); + _func = () => ExecuteByHash(_path, mhash.Groups["data"].Value, long.Parse(_parameters[1]), _parameters[2]); } } } @@ -58,7 +58,7 @@ public override Task Execute() private async Task ExecuteByLink(string path, string link) { - var k = await Cloud.CloneItem(path, link); + var k = await _cloud.CloneItem(path, link); return new SpecialCommandResult(k.IsSuccess); } @@ -69,8 +69,8 @@ private async Task ExecuteByHash(string path, string hash, : WebDavPath.Combine(path, paramPath); //TODO: now mail.ru only - var k = await Cloud.AddFile(new FileHashMrc(hash), fpath, size, ConflictResolver.Rename); + var k = await _cloud.AddFile(new FileHashMrc(hash), fpath, size, ConflictResolver.Rename); return new SpecialCommandResult(k.Success); } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/ListCommand.cs b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/ListCommand.cs index 7d0c1229..0911292e 100644 --- a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/ListCommand.cs +++ b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/ListCommand.cs @@ -23,18 +23,17 @@ public ListCommand(Cloud cloud, string path, IList parameters) : base(cl public override async Task Execute() { - string target = Parames.Count > 0 && !string.IsNullOrWhiteSpace(Parames[0]) - ? Parames[0].StartsWith(WebDavPath.Separator) ? Parames[0] : WebDavPath.Combine(Path, Parames[0]) - : Path; + string target = _parameters.Count > 0 && !string.IsNullOrWhiteSpace(_parameters[0]) + ? _parameters[0].StartsWith(WebDavPath.Separator) ? _parameters[0] : WebDavPath.Combine(_path, _parameters[0]) + : _path; - var resolvedTarget = await RemotePath.Get(target, Cloud.LinkManager); - - var entry = await Cloud.RequestRepo.FolderInfo(resolvedTarget); - string resFilepath = WebDavPath.Combine(Path, string.Concat(entry.Name, FileListExtention)); + var resolvedTarget = await RemotePath.Get(target, _cloud.LinkManager); + var entry = await _cloud.RequestRepo.FolderInfo(resolvedTarget); + string resFilepath = WebDavPath.Combine(_path, string.Concat(entry.Name, FileListExtention)); var sb = new StringBuilder(); - foreach (var e in Flat(entry, Cloud.LinkManager)) + foreach (var e in Flat(entry, _cloud.LinkManager)) { string hash = (e as File)?.Hash.ToString() ?? "-"; string link = e.PublicLinks.Values.FirstOrDefault()?.Uri.OriginalString ?? "-"; @@ -42,7 +41,7 @@ public override async Task Execute() $"{e.FullPath}\t{e.Size.DefaultValue}\t{e.CreationTimeUtc:yyyy.MM.dd HH:mm:ss}\t{hash}\t{link}"); } - Cloud.UploadFile(resFilepath, sb.ToString()); + _cloud.UploadFile(resFilepath, sb.ToString()); return SpecialCommandResult.Success; } @@ -59,7 +58,7 @@ private IEnumerable Flat(IEntry entry, LinkManager lm) File => it, Folder ifolder => ifolder.IsChildrenLoaded ? ifolder - : Cloud.RequestRepo.FolderInfo(RemotePath.Get(it.FullPath, lm).Result, depth: 3).Result, + : _cloud.RequestRepo.FolderInfo(RemotePath.Get(it.FullPath, lm).Result, depth: 3).Result, _ => throw new NotImplementedException("Unknown item type") }) .OrderBy(it => it.Name); diff --git a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/LocalToServerCopyCommand.cs b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/LocalToServerCopyCommand.cs index 68ba5cbe..3738e81a 100644 --- a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/LocalToServerCopyCommand.cs +++ b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/LocalToServerCopyCommand.cs @@ -19,15 +19,15 @@ public override async Task Execute() { var res = await Task.Run(async () => { - var sourceFileInfo = new FileInfo(Parames[0]); + var sourceFileInfo = new FileInfo(_parameters[0]); string name = sourceFileInfo.Name; - string targetPath = WebDavPath.Combine(Path, name); + string targetPath = WebDavPath.Combine(_path, name); - Logger.Info($"COMMAND:COPY:{Parames[0]}"); + Logger.Info($"COMMAND:COPY:{_parameters[0]}"); - using (var source = System.IO.File.Open(Parames[0], FileMode.Open, FileAccess.Read, FileShare.Read)) - using (var target = await Cloud.GetFileUploadStream(targetPath, sourceFileInfo.Length, null, null).ConfigureAwait(false)) + using (var source = System.IO.File.Open(_parameters[0], FileMode.Open, FileAccess.Read, FileShare.Read)) + using (var target = await _cloud.GetFileUploadStream(targetPath, sourceFileInfo.Length, null, null).ConfigureAwait(false)) { await source.CopyToAsync(target); } @@ -38,4 +38,4 @@ public override async Task Execute() return res; } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/MoveCommand.cs b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/MoveCommand.cs index 5fd35168..42dd3abb 100644 --- a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/MoveCommand.cs +++ b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/MoveCommand.cs @@ -15,14 +15,14 @@ public MoveCommand(Cloud cloud, string path, IList parameters) public override async Task Execute() { - string source = WebDavPath.Clean(Parames.Count == 1 ? Path : Parames[0]); - string target = WebDavPath.Clean(Parames.Count == 1 ? Parames[0] : Parames[1]); + string source = WebDavPath.Clean(_parameters.Count == 1 ? _path : _parameters[0]); + string target = WebDavPath.Clean(_parameters.Count == 1 ? _parameters[0] : _parameters[1]); - var entry = await Cloud.GetItemAsync(source); + var entry = await _cloud.GetItemAsync(source); if (entry is null) return SpecialCommandResult.Fail; - var res = await Cloud.MoveAsync(entry, target); + var res = await _cloud.MoveAsync(entry, target); return new SpecialCommandResult(res); } } diff --git a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/RemoveBadLinksCommand.cs b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/RemoveBadLinksCommand.cs index 804f45b0..6e0382d4 100644 --- a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/RemoveBadLinksCommand.cs +++ b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/RemoveBadLinksCommand.cs @@ -5,7 +5,7 @@ namespace YaR.Clouds.SpecialCommands.Commands { public class RemoveBadLinksCommand : SpecialCommand { - public RemoveBadLinksCommand(Cloud cloud, string path, IList parames): base(cloud, path, parames) + public RemoveBadLinksCommand(Cloud cloud, string path, IList parameters): base(cloud, path, parameters) { } @@ -13,8 +13,8 @@ public RemoveBadLinksCommand(Cloud cloud, string path, IList parames): b public override Task Execute() { - Cloud.RemoveDeadLinks(); + _cloud.RemoveDeadLinks(); return Task.FromResult(SpecialCommandResult.Success); } } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/ShareCommand.cs b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/ShareCommand.cs index 5409e634..ce89c287 100644 --- a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/ShareCommand.cs +++ b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/ShareCommand.cs @@ -26,33 +26,33 @@ public ShareCommand(Cloud cloud, string path, bool generateDirectVideoLink, bool public override async Task Execute() { string path; - string param = Parames.Count == 0 - ? string.Empty - : Parames[0].Replace("\\", WebDavPath.Separator); - SharedVideoResolution videoResolution = Parames.Count < 2 - ? Cloud.Settings.DefaultSharedVideoResolution - : EnumExtensions.ParseEnumMemberValue(Parames[1]); - - if (Parames.Count == 0) - path = Path; + string param = _parameters.Count == 0 + ? string.Empty + : _parameters[0].Replace("\\", WebDavPath.Separator); + SharedVideoResolution videoResolution = _parameters.Count < 2 + ? _cloud.Settings.DefaultSharedVideoResolution + : EnumExtensions.ParseEnumMemberValue(_parameters[1]); + + if (_parameters.Count == 0) + path = _path; else if (param.StartsWith(WebDavPath.Separator)) path = param; else - path = WebDavPath.Combine(Path, param); + path = WebDavPath.Combine(_path, param); - var entry = await Cloud.GetItemAsync(path); + var entry = await _cloud.GetItemAsync(path); if (entry is null) return SpecialCommandResult.Fail; try { - await Cloud.Publish(entry, true, _generateDirectVideoLink, _makeM3UFile, videoResolution); + await _cloud.Publish(entry, true, _generateDirectVideoLink, _makeM3UFile, videoResolution); } catch (Exception e) { return new SpecialCommandResult(false, e.Message); } - + return SpecialCommandResult.Success; } } diff --git a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/SharedFolderLinkCommand.cs b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/SharedFolderLinkCommand.cs index 3c0d6b8e..ecc5824d 100644 --- a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/SharedFolderLinkCommand.cs +++ b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/SharedFolderLinkCommand.cs @@ -30,28 +30,30 @@ public SharedFolderLinkCommand(Cloud cloud, string path, IList parameter public override async Task Execute() { - var m = s_commandRegex.Match(Parames[0]); + var m = s_commandRegex.Match(_parameters[0]); if (!m.Success) return SpecialCommandResult.Fail; + var publicBaseUrl = _cloud.RequestRepo.PublicBaseUrlDefault; var url = new Uri(m.Groups["url"].Value, UriKind.RelativeOrAbsolute); if (!url.IsAbsoluteUri) - url = new Uri(Cloud.Repo.PublicBaseUrlDefault + m.Groups["url"].Value, UriKind.Absolute); + url = new Uri(publicBaseUrl + m.Groups["url"].Value, UriKind.Absolute); //TODO: make method in MailRuCloud to get entry by url //var item = await new ItemInfoRequest(Cloud.CloudApi, m.Groups["url"].Value, true).MakeRequestAsync(_connectionLimiter); - var item = await Cloud.RequestRepo.ItemInfo(RemotePath.Get(new Link(url)) ); - var entry = item.ToEntry(Cloud.Repo.PublicBaseUrlDefault); + + var item = await _cloud.RequestRepo.ItemInfo(RemotePath.Get(new Link(url))); + var entry = item.ToEntry(publicBaseUrl); if (entry is null) return SpecialCommandResult.Fail; - string name = Parames.Count > 1 && !string.IsNullOrWhiteSpace(Parames[1]) - ? Parames[1] + string name = _parameters.Count > 1 && !string.IsNullOrWhiteSpace(_parameters[1]) + ? _parameters[1] : entry.Name; - var res = await Cloud.LinkItem( + var res = await _cloud.LinkItem( url, - Path, name, entry.IsFile, entry.Size, entry.CreationTimeUtc); + _path, name, entry.IsFile, entry.Size, entry.CreationTimeUtc); return new SpecialCommandResult(res); } diff --git a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/TestCommand.cs b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/TestCommand.cs index b3fba1a9..dcc83d47 100644 --- a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/TestCommand.cs +++ b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/TestCommand.cs @@ -15,14 +15,14 @@ public TestCommand(Cloud cloud, string path, IList parameters) public override async Task Execute() { - string path = Parames[0].Replace("\\", WebDavPath.Separator); + string path = _parameters[0].Replace("\\", WebDavPath.Separator); - if (await Cloud.GetItemAsync(path) is not File entry) + if (await _cloud.GetItemAsync(path) is not File entry) return SpecialCommandResult.Fail; //var auth = await new OAuthRequest(Cloud.CloudApi).MakeRequestAsync(_connectionLimiter); - bool removed = await Cloud.Remove(entry, false); + bool removed = await _cloud.Remove(entry, false); if (removed) { //var addreq = await new MobAddFileRequest(Cloud.CloudApi, entry.FullPath, entry.Hash, entry.Size, new DateTime(2010, 1, 1), ConflictResolver.Rename) diff --git a/MailRuCloud/MailRuCloudApi/SpecialCommands/SpecialCommand.cs b/MailRuCloud/MailRuCloudApi/SpecialCommands/SpecialCommand.cs index 0ed36456..39d9d7fb 100644 --- a/MailRuCloud/MailRuCloudApi/SpecialCommands/SpecialCommand.cs +++ b/MailRuCloud/MailRuCloudApi/SpecialCommands/SpecialCommand.cs @@ -6,24 +6,24 @@ namespace YaR.Clouds.SpecialCommands { public abstract class SpecialCommand { - protected readonly Cloud Cloud; - protected readonly string Path; - protected readonly IList Parames; + protected readonly Cloud _cloud; + protected readonly string _path; + protected readonly IList _parameters; protected abstract MinMax MinMaxParamsCount { get; } - protected SpecialCommand(Cloud cloud, string path, IList parames) + protected SpecialCommand(Cloud cloud, string path, IList parameters) { - Cloud = cloud; - Path = path; - Parames = parames; + _cloud = cloud; + _path = path; + _parameters = parameters; CheckParams(); } public virtual Task Execute() { - if (Parames.Count < MinMaxParamsCount.Min || Parames.Count > MinMaxParamsCount.Max) + if (_parameters.Count < MinMaxParamsCount.Min || _parameters.Count > MinMaxParamsCount.Max) return Task.FromResult(SpecialCommandResult.Fail); return Task.FromResult(SpecialCommandResult.Success); @@ -31,7 +31,7 @@ public virtual Task Execute() private void CheckParams() { - if (Parames.Count < MinMaxParamsCount.Min || Parames.Count > MinMaxParamsCount.Max) + if (_parameters.Count < MinMaxParamsCount.Min || _parameters.Count > MinMaxParamsCount.Max) throw new ArgumentException("Invalid parameters count"); } diff --git a/MailRuCloud/MailRuCloudApi/SpecialCommands/SpecialCommandFabric.cs b/MailRuCloud/MailRuCloudApi/SpecialCommands/SpecialCommandFabric.cs index 7b207c49..666169ac 100644 --- a/MailRuCloud/MailRuCloudApi/SpecialCommands/SpecialCommandFabric.cs +++ b/MailRuCloud/MailRuCloudApi/SpecialCommands/SpecialCommandFabric.cs @@ -106,12 +106,12 @@ public SpecialCommand Build(Cloud cloud, string param) if (!res.IsValid) return null; - var parames = ParseParameters(res.Data); - var commandContainer = FindCommandContainer(parames); + var parameters = ParseParameters(res.Data); + var commandContainer = FindCommandContainer(parameters); if (commandContainer == null) return null; - parames = parames.Skip(commandContainer.Commands.Length).ToList(); - var cmd = commandContainer.CreateFunc(cloud, res.Path, parames); + parameters = parameters.Skip(commandContainer.Commands.Length).ToList(); + var cmd = commandContainer.CreateFunc(cloud, res.Path, parameters); return cmd; } @@ -145,12 +145,12 @@ private struct ParamsData } - private static SpecialCommandContainer FindCommandContainer(ICollection parames) + private static SpecialCommandContainer FindCommandContainer(ICollection parameters) { var commandContainer = CommandContainers .Where(cm => - cm.Commands.Length <= parames.Count && - cm.Commands.SequenceEqual(parames.Take(cm.Commands.Length))) + cm.Commands.Length <= parameters.Count && + cm.Commands.SequenceEqual(parameters.Take(cm.Commands.Length))) .Aggregate((agg, next) => next.Commands.Length > agg.Commands.Length ? next : agg); return commandContainer; @@ -187,5 +187,4 @@ private class SpecialCommandContainer } } - -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/SpecialCommands/SpecialCommandResult.cs b/MailRuCloud/MailRuCloudApi/SpecialCommands/SpecialCommandResult.cs index c576e8ec..f152d6ee 100644 --- a/MailRuCloud/MailRuCloudApi/SpecialCommands/SpecialCommandResult.cs +++ b/MailRuCloud/MailRuCloudApi/SpecialCommands/SpecialCommandResult.cs @@ -19,4 +19,4 @@ public SpecialCommandResult(bool isSuccess, string message) : this(isSuccess) public static SpecialCommandResult Fail => new(false); } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Streams/DownloadStreamFabric.cs b/MailRuCloud/MailRuCloudApi/Streams/DownloadStreamFabric.cs index 9c305f46..4ee8b9a3 100644 --- a/MailRuCloud/MailRuCloudApi/Streams/DownloadStreamFabric.cs +++ b/MailRuCloud/MailRuCloudApi/Streams/DownloadStreamFabric.cs @@ -20,6 +20,7 @@ public Stream Create(File file, long? start = null, long? end = null) return CreateXTSStream(file, start, end); //return new DownloadStream(file, _cloud.CloudApi, start, end); + var stream = _cloud.RequestRepo.GetDownloadStream(file, start, end); return stream; } @@ -56,4 +57,4 @@ private Stream CreateXTSStream(File file, long? start = null, long? end = null) public const int XTSSectorSize = 512; public const long XTSBlockSize = XTSWriteOnlyStream.BlockSize; } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/Streams/UploadStreamFabric.cs b/MailRuCloud/MailRuCloudApi/Streams/UploadStreamFabric.cs index 8bcd0144..c9719902 100644 --- a/MailRuCloud/MailRuCloudApi/Streams/UploadStreamFabric.cs +++ b/MailRuCloud/MailRuCloudApi/Streams/UploadStreamFabric.cs @@ -37,7 +37,7 @@ public async Task Create(File file, FileUploadedDelegate onUploaded = nu // #142 remove crypted file parts if size changed await _cloud.Remove(file.FullPath); - + stream = GetCryptoStream(file, onUploaded); } else @@ -51,7 +51,8 @@ public async Task Create(File file, FileUploadedDelegate onUploaded = nu private Stream GetPlainStream(File file, FileUploadedDelegate onUploaded) { var stream = new SplittedUploadStream(file.FullPath, _cloud, file.Size, FileStreamSent, ServerFileProcessed); - if (onUploaded != null) stream.FileUploaded += onUploaded; + if (onUploaded is not null) + stream.FileUploaded += onUploaded; return stream; } @@ -73,7 +74,8 @@ private Stream GetCryptoStream(File file, FileUploadedDelegate onUploaded) : (file.OriginalSize / XTSWriteOnlyStream.BlockSize + 1) * XTSWriteOnlyStream.BlockSize; var ustream = new SplittedUploadStream(file.FullPath, _cloud, size, null, null, false, file.ServiceInfo.CryptInfo); - if (onUploaded != null) ustream.FileUploaded += onUploaded; + if (onUploaded is not null) + ustream.FileUploaded += onUploaded; // ReSharper disable once RedundantArgumentDefaultValue var encustream = new XTSWriteOnlyStream(ustream, xts, XTSWriteOnlyStream.DefaultSectorSize) { diff --git a/MailRuCloud/MailRuCloudApi/YaR.Clouds.csproj b/MailRuCloud/MailRuCloudApi/YaR.Clouds.csproj index 668037dc..c69b93b1 100644 --- a/MailRuCloud/MailRuCloudApi/YaR.Clouds.csproj +++ b/MailRuCloud/MailRuCloudApi/YaR.Clouds.csproj @@ -24,18 +24,10 @@ $(DefineConstants);DEBUG;TRACE - - - - - - - - diff --git a/MailRuCloud/TwoFA/UI/MailRuCloudApi.TwoFA.Console/AuthCodeConsole.cs b/MailRuCloud/TwoFA/UI/MailRuCloudApi.TwoFA.Console/AuthCodeConsole.cs index 771a36a9..abe38a62 100644 --- a/MailRuCloud/TwoFA/UI/MailRuCloudApi.TwoFA.Console/AuthCodeConsole.cs +++ b/MailRuCloud/TwoFA/UI/MailRuCloudApi.TwoFA.Console/AuthCodeConsole.cs @@ -5,7 +5,7 @@ namespace YaR.Clouds.MailRuCloud.TwoFA.UI // ReSharper disable once UnusedType.Global public class AuthCodeConsole : ITwoFaHandler { - public AuthCodeConsole(IEnumerable> parames) + public AuthCodeConsole(IEnumerable> parameters) { } diff --git a/MailRuCloud/TwoFA/UI/MailRuCloudApi.TwoFA.Dialog/AuthCodeWindow.cs b/MailRuCloud/TwoFA/UI/MailRuCloudApi.TwoFA.Dialog/AuthCodeWindow.cs index 14fb72b0..ef896994 100644 --- a/MailRuCloud/TwoFA/UI/MailRuCloudApi.TwoFA.Dialog/AuthCodeWindow.cs +++ b/MailRuCloud/TwoFA/UI/MailRuCloudApi.TwoFA.Dialog/AuthCodeWindow.cs @@ -7,7 +7,7 @@ namespace YaR.Clouds.MailRuCloud.TwoFA.UI // ReSharper disable once UnusedType.Global public class AuthCodeWindow : ITwoFaHandler { - public AuthCodeWindow(IEnumerable> parames) + public AuthCodeWindow(IEnumerable> parameters) { } diff --git a/MailRuCloud/TwoFA/UI/MailRuCloudApi.TwoFA.File/AuthCodeFile.cs b/MailRuCloud/TwoFA/UI/MailRuCloudApi.TwoFA.File/AuthCodeFile.cs index cf5ac2c2..af9bdeb9 100644 --- a/MailRuCloud/TwoFA/UI/MailRuCloudApi.TwoFA.File/AuthCodeFile.cs +++ b/MailRuCloud/TwoFA/UI/MailRuCloudApi.TwoFA.File/AuthCodeFile.cs @@ -17,17 +17,17 @@ public class AuthCodeFile : ITwoFaHandler private const string FilenamePrefixParamName = "FilenamePrefix"; private const string DoDeleteFileAfterName = "DoDeleteFileAfter"; - public AuthCodeFile(IEnumerable> parames) + public AuthCodeFile(IEnumerable> parameters) { - var parames1 = parames.ToList(); + var parameters1 = parameters.ToList(); - _dirPath = parames1.First(p => p.Key == DirectoryParamName).Value; + _dirPath = parameters1.FirstOrDefault(p => p.Key == DirectoryParamName).Value; if (!Directory.Exists(_dirPath)) throw new DirectoryNotFoundException($"2FA: directory not found {_dirPath}"); - _filePrefix = parames1.First(p => p.Key == FilenamePrefixParamName).Value ?? string.Empty; + _filePrefix = parameters1.FirstOrDefault(p => p.Key == FilenamePrefixParamName).Value ?? string.Empty; - var val = parames1.FirstOrDefault(p => p.Key == DoDeleteFileAfterName).Value; + var val = parameters1.FirstOrDefault(p => p.Key == DoDeleteFileAfterName).Value; _doDeleteFileAfter = string.IsNullOrWhiteSpace(val) || bool.Parse(val); } @@ -47,7 +47,7 @@ public string Get(string login, bool isAutoRelogin) var watcher = new FileSystemWatcher(_dirPath) { NotifyFilter = NotifyFilters.LastWrite }; watcher.Changed += (_, args) => { - if (!string.Equals(Path.GetFullPath(args.FullPath), Path.GetFullPath(filepath), StringComparison.OrdinalIgnoreCase)) + if (!string.Equals(Path.GetFullPath(args.FullPath), Path.GetFullPath(filepath), StringComparison.OrdinalIgnoreCase)) return; watcher.EnableRaisingEvents = false; @@ -71,4 +71,3 @@ public string Get(string login, bool isAutoRelogin) } } } - diff --git a/WDMRC.Console/CommandLineOptions.cs b/WDMRC.Console/CommandLineOptions.cs index c73dc19e..c7cf7ddc 100644 --- a/WDMRC.Console/CommandLineOptions.cs +++ b/WDMRC.Console/CommandLineOptions.cs @@ -1,6 +1,4 @@ -// Ignore Spelling: Ua - -using System; +using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using CommandLine; @@ -85,5 +83,8 @@ class CommandLineOptions [Option("read-write-timeout-sec", Required = false, Default = 300, HelpText = "Timeout in seconds, the maximum duration of read or write operation")] public int ReadWriteTimeoutSec { get; set; } + + [Option("cloud-instance-timeout", Required = false, Default = 30, HelpText = "Cloud instance (server+login) expiration timeout in minutes")] + public int CloudInstanceTimeoutMinutes { get; set; } } } diff --git a/WDMRC.Console/Config.cs b/WDMRC.Console/Config.cs index 8ce81167..4c5ae354 100644 --- a/WDMRC.Console/Config.cs +++ b/WDMRC.Console/Config.cs @@ -87,15 +87,15 @@ public static TwoFactorAuthHandlerInfo TwoFactorAuthHandler var node = Document.SelectSingleNode("/config/TwoFactorAuthHandler"); info.Name = node.Attributes["Name"].InnerText; - var parames = new List>(); + var parameters = new List>(); foreach (XmlNode childNode in node.ChildNodes) { string pname = childNode.Attributes["Name"].InnerText; string pvalue = childNode.Attributes["Value"].InnerText; - parames.Add(new KeyValuePair(pname, pvalue)); + parameters.Add(new KeyValuePair(pname, pvalue)); } - info.Parames = parames; + info.Parameters = parameters; return info; diff --git a/WDMRC.Console/HttpListenerOptions.cs b/WDMRC.Console/HttpListenerOptions.cs index a53bad8f..a1c5217d 100644 --- a/WDMRC.Console/HttpListenerOptions.cs +++ b/WDMRC.Console/HttpListenerOptions.cs @@ -20,8 +20,8 @@ public HttpListenerOptions(CommandLineOptions options) Prefixes.Add(prefix); } } - + public List Prefixes { get; } = new(); public AuthenticationSchemes AuthenticationScheme => AuthenticationSchemes.Basic; } -} \ No newline at end of file +} diff --git a/WDMRC.Console/Log4NetAdapter.cs b/WDMRC.Console/Log4NetAdapter.cs index f5283826..935e9878 100644 --- a/WDMRC.Console/Log4NetAdapter.cs +++ b/WDMRC.Console/Log4NetAdapter.cs @@ -78,4 +78,4 @@ public ILogger CreateLogger(Type type) return new Log4NetLoggerAdapter(type); } } -} \ No newline at end of file +} diff --git a/WDMRC.Console/Payload.cs b/WDMRC.Console/Payload.cs index 27c2d978..92229cd3 100644 --- a/WDMRC.Console/Payload.cs +++ b/WDMRC.Console/Payload.cs @@ -10,6 +10,7 @@ using NWebDav.Server.HttpListener; using NWebDav.Server.Locking; using NWebDav.Server.Logging; +using YaR.Clouds.Extensions; using YaR.Clouds.WebDavStore; using YaR.Clouds.WebDavStore.StoreBase; using RequestHandlerFactory = YaR.Clouds.WebDavStore.RequestHandlerFactory; @@ -26,7 +27,7 @@ public static void Stop() { CancelToken.Cancel(false); } - + public static void Run(CommandLineOptions options) { // trying to fix "infinite recursion during resource lookup with system.private.corelib" @@ -62,6 +63,8 @@ public static void Run(CommandLineOptions options) DisableLinkManager = options.DisableLinkManager, + CloudInstanceTimeoutMinutes = options.CloudInstanceTimeoutMinutes, + Wait100ContinueTimeoutSec = options.Wait100ContinueTimeoutSec, WaitResponseTimeoutSec = options.WaitResponseTimeoutSec, ReadWriteTimeoutSec = options.ReadWriteTimeoutSec, @@ -77,34 +80,41 @@ public static void Run(CommandLineOptions options) var httpListener = new HttpListener(); var httpListenerOptions = new HttpListenerOptions(options); - try - { - foreach (var prefix in httpListenerOptions.Prefixes) - httpListener.Prefixes.Add(prefix); - httpListener.AuthenticationSchemes = httpListenerOptions.AuthenticationScheme; - httpListener.Start(); + try + { + foreach (var prefix in httpListenerOptions.Prefixes) + httpListener.Prefixes.Add(prefix); + httpListener.AuthenticationSchemes = httpListenerOptions.AuthenticationScheme; + httpListener.Start(); Logger.Info( $"WebDAV server running at {httpListenerOptions.Prefixes.Aggregate( (current, next) => string.Concat(current, ", ", next))}"); - // Start dispatching requests - var t = DispatchHttpRequestsAsync(httpListener, options.MaxThreadCount); - t.Wait(CancelToken.Token); + // Start dispatching requests + var t = DispatchHttpRequestsAsync(httpListener, options.MaxThreadCount); + t.Wait(CancelToken.Token); - //do not use console input - it uses 100% CPU when running mono-service in ubuntu - } - catch (OperationCanceledException ce) when (ce.CancellationToken.IsCancellationRequested) - { - Logger.Info("Cancelled"); - } + //do not use console input - it uses 100% CPU when running mono-service in ubuntu + } + catch (OperationCanceledException ce) when (ce.CancellationToken.IsCancellationRequested) + { + Logger.Info("Cancelled"); + } + catch (HttpListenerException e) when (e.Message.ContainsIgnoreCase("conflicts with an existing")) + { + // System.Net.HttpListenerException: 'Failed to listen on prefix 'http://127.0.0.1:12345/' + // because it conflicts with an existing registration on the machine.' + Logger.Error(e.Message); + } finally { httpListener.Stop(); } } - private const string DefaultUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"; + private const string DefaultUserAgent = + "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"; private static string ConstructUserAgent(string fromOptions, string fromConfig) { if (!string.IsNullOrWhiteSpace(fromOptions)) @@ -131,7 +141,7 @@ private static string ConstructSecChUa(string fromOptions, string fromConfig) private static ITwoFaHandler LoadHandler(TwoFactorAuthHandlerInfo handlerInfo) { - if (string.IsNullOrEmpty(handlerInfo.Name)) + if (string.IsNullOrEmpty(handlerInfo.Name)) return null; var twoFaHandler = TwoFaHandlers.Get(handlerInfo); @@ -148,7 +158,7 @@ private static async Task DispatchHttpRequestsAsync(HttpListener httpListener, i // Create WebDAV dispatcher var homeFolder = new LocalStore( - isEnabledPropFunc: Config.IsEnabledWebDAVProperty, + isEnabledPropFunc: Config.IsEnabledWebDAVProperty, lockingManager: CloudManager.Settings.UseLocks ? new InMemoryLockingManager() : new EmptyLockingManager()); var webDavDispatcher = new WebDavDispatcher(homeFolder, requestHandlerFactory); @@ -220,6 +230,7 @@ private static void ShowInfo(CommandLineOptions options) Logger.Info($"Cloud download/upload timeout: {options.ReadWriteTimeoutSec} sec"); Logger.Info($"Wait for 100-Continue timeout: {options.Wait100ContinueTimeoutSec} sec"); Logger.Info($"Cloud & protocol: defined by login and the rest parameters"); + Logger.Info($"Cloud instance (server+login) expiration timeout: {options.CloudInstanceTimeoutMinutes} min"); Logger.Info($"Folder cache expiration timeout: {options.CacheListingSec} sec"); Logger.Info($"List query folder depth: {options.CacheListingDepth}"); Logger.Info($"Use locks: {options.UseLocks}"); diff --git a/WDMRC.Console/Program.cs b/WDMRC.Console/Program.cs index 75054987..3227f9e4 100644 --- a/WDMRC.Console/Program.cs +++ b/WDMRC.Console/Program.cs @@ -12,7 +12,7 @@ public class Program private static void Main(string[] args) { var result = Parser.Default.ParseArguments(args); - + var exitCode = result .MapResult( options => diff --git a/WDMRC.Console/ProxyFabric.cs b/WDMRC.Console/ProxyFabric.cs index b1b5666e..7453c22f 100644 --- a/WDMRC.Console/ProxyFabric.cs +++ b/WDMRC.Console/ProxyFabric.cs @@ -14,7 +14,7 @@ public static IWebProxy Get(string proxyAddress, string proxyUser, string proxyP #if NET48 return WebRequest.DefaultWebProxy; #else - return HttpClient.DefaultProxy; + return HttpClient.DefaultProxy; #endif @@ -28,7 +28,7 @@ public static IWebProxy Get(string proxyAddress, string proxyUser, string proxyP int port = int.Parse(match.Groups["port"].Value); var proxy = type == "socks" - ? string.IsNullOrEmpty(proxyUser) + ? string.IsNullOrEmpty(proxyUser) ? (IWebProxy)new HttpToSocks5Proxy(address, port) : new HttpToSocks5Proxy(address, port, proxyUser, proxyPassword) : new WebProxy(new Uri(proxyAddress)) @@ -40,4 +40,4 @@ public static IWebProxy Get(string proxyAddress, string proxyUser, string proxyP return proxy; } } -} \ No newline at end of file +} diff --git a/WDMRC.Console/wdmrc.config b/WDMRC.Console/wdmrc.config index 1f3ae550..40539635 100644 --- a/WDMRC.Console/wdmrc.config +++ b/WDMRC.Console/wdmrc.config @@ -27,7 +27,7 @@ - + @@ -38,7 +38,7 @@ - + @@ -70,8 +70,8 @@ - Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36 - "Not.A/Brand";v="8", "Chromium";v="114", "Google Chrome";v="114" + Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36 + "Google Chrome";v="119", "Chromium";v="119", "Not?A_Brand";v="24" @@ -93,13 +93,13 @@ CacheDir="" /> - + .,. - 0p - + @@ -138,9 +138,9 @@ - + - + @@ -198,5 +198,5 @@ - + \ No newline at end of file diff --git a/WebDAV.Uploader/UploadStub.cs b/WebDAV.Uploader/UploadStub.cs index 0a43d2f5..21fd1868 100644 --- a/WebDAV.Uploader/UploadStub.cs +++ b/WebDAV.Uploader/UploadStub.cs @@ -36,7 +36,7 @@ public static int Upload(UploadOptions cmdOptions) TwoFaHandler = null, Protocol = Protocol.WebM1Bin }; - var credentials = new Credentials(user, password); + var credentials = new Credentials(settings, user, password); var cloud = new Cloud(settings, credentials); using (var file = new StreamReader(listname)) diff --git a/WebDAVMailRuCloud.sln b/WebDAVMailRuCloud.sln index 5838774c..b2bd0eab 100644 --- a/WebDAVMailRuCloud.sln +++ b/WebDAVMailRuCloud.sln @@ -40,7 +40,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MailRuCloudApi.TwoFA.File", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Hasher", "Hasher\Hasher.csproj", "{CB339F56-A0D8-4133-B322-6894DC70FAE6}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "YandexAuthBrowser", "YandexAuthBrowser\YandexAuthBrowser.csproj", "{85766D6A-EA1E-4413-BF95-FEC07D72BA99}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BrowserAuthenticator", "BrowserAuthenticator\BrowserAuthenticator.csproj", "{85766D6A-EA1E-4413-BF95-FEC07D72BA99}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/WebDavMailRuCloudStore/BrowserAuthenticator.cs b/WebDavMailRuCloudStore/BrowserAuthenticator.cs index 95e00f68..9a06a0f7 100644 --- a/WebDavMailRuCloudStore/BrowserAuthenticator.cs +++ b/WebDavMailRuCloudStore/BrowserAuthenticator.cs @@ -1,29 +1,22 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; +namespace YaR.Clouds.WebDavStore; -namespace YaR.Clouds.WebDavStore +public class BrowserAuthenticatorInfo { - public class BrowserAuthenticatorInfo - { - public string Url { get; private set; } - - public string Password { get; private set; } + public string Url { get; private set; } - public string CacheDir { get; private set; } + public string Password { get; private set; } - public BrowserAuthenticatorInfo(string url, string password, string cacheDir ) - { - Url = url; - Password = password; - CacheDir = cacheDir; - } - } + public string CacheDir { get; private set; } - public static class BrowserAuthenticator + public BrowserAuthenticatorInfo(string url, string password, string cacheDir ) { - public static BrowserAuthenticatorInfo Instance { get; set; } + Url = url; + Password = password; + CacheDir = cacheDir; } } + +public static class BrowserAuthenticator +{ + public static BrowserAuthenticatorInfo Instance { get; set; } +} diff --git a/WebDavMailRuCloudStore/CloudManager.cs b/WebDavMailRuCloudStore/CloudManager.cs index 03472dab..43b3c5eb 100644 --- a/WebDavMailRuCloudStore/CloudManager.cs +++ b/WebDavMailRuCloudStore/CloudManager.cs @@ -1,103 +1,146 @@ using System; using System.Collections.Concurrent; using System.Net; -using System.Security.Authentication; +using System.Reflection; using System.Security.Principal; using System.Threading; using YaR.Clouds.Base; -namespace YaR.Clouds.WebDavStore +namespace YaR.Clouds.WebDavStore; + +public static class CloudManager { - public static class CloudManager + private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private class ClockedCloud { - private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(CloudManager)); + public DateTime LastAccess; + public Cloud Cloud; + } - private static readonly ConcurrentDictionary CloudCache = new(StringComparer.InvariantCultureIgnoreCase); + private static readonly ConcurrentDictionary CloudCache + = new(StringComparer.InvariantCultureIgnoreCase); - public static CloudSettings Settings { get; set; } + public static CloudSettings Settings { get; set; } - private static SemaphoreSlim _locker = new SemaphoreSlim(1); + private static readonly SemaphoreSlim _dictionaryLocker = new SemaphoreSlim(1); + private static readonly SemaphoreSlim _creationLocker = new SemaphoreSlim(1); + private static readonly System.Timers.Timer _cleanTimer; - public static Cloud Instance(IIdentity identity) + static CloudManager() + { + _cleanTimer = new System.Timers.Timer() { - var basicIdentity = (HttpListenerBasicIdentity) identity; - string key = basicIdentity.Name + basicIdentity.Password; - - if (CloudCache.TryGetValue(key, out var cloud)) - return cloud; - - _locker.Wait(); - try - { - if (CloudCache.TryGetValue(key, out cloud)) - return cloud; + Interval = 60 * 1000, // 1 minute + Enabled = false, + AutoReset = true + }; + _cleanTimer.Elapsed += RemoveExpired; + } - cloud = CreateCloud(basicIdentity); + public static Cloud Instance(IIdentity identity) + { + var basicIdentity = (HttpListenerBasicIdentity)identity; + string key = basicIdentity.Name + basicIdentity.Password; - CloudCache.TryAdd(key, cloud); - } - finally + _dictionaryLocker.Wait(); + try + { + if (CloudCache.TryGetValue(key, out var cloudItem)) { - _locker.Release(); + cloudItem.LastAccess = DateTime.Now; + return cloudItem.Cloud; } - - return cloud; } - - private static Cloud CreateCloud(HttpListenerBasicIdentity identity) + finally { - var credentials = new Credentials(identity.Name, identity.Password); - - if (credentials.Protocol == Protocol.Autodetect && - Settings.Protocol != Protocol.Autodetect) - { - // Если протокол не определился из строки логина, - // то пользуемся подсказкой в виде параметра командной строки - credentials.Protocol = Settings.Protocol; - } + _dictionaryLocker.Release(); + } - if (credentials.Protocol == Protocol.Autodetect && - credentials.CloudType == CloudType.Yandex) + _creationLocker.Wait(); + try + { + // Когда дождались своей очереди на создание экземпляра, + // обязательно надо проверить, что кто-то уже не создал экземпляр, + // пока стояли в очереди на создание. + if (CloudCache.TryGetValue(key, out var cloudItem)) { - if (string.IsNullOrEmpty(Settings.BrowserAuthenticatorUrl) || - string.IsNullOrEmpty(Settings.BrowserAuthenticatorPassword)) + _dictionaryLocker.Wait(); + try { - credentials.Protocol = Protocol.YadWeb; + cloudItem.LastAccess = DateTime.Now; + return cloudItem.Cloud; } - else - if (!string.IsNullOrEmpty(Settings.BrowserAuthenticatorUrl) && - !string.IsNullOrEmpty(Settings.BrowserAuthenticatorPassword) && - identity.Password.Equals(Settings.BrowserAuthenticatorPassword, StringComparison.InvariantCultureIgnoreCase)) + finally { - credentials.Protocol = Protocol.YadWebV2; - } - else - { - Logger.Info("Protocol auto detect is ON. Can not choose between YadWeb and YadWebV2"); - throw new InvalidCredentialException( - "Protocol auto detect is ON. Can not choose between YadWeb and YadWebV2. " + - "Please specify protocol version in login string, see manuals."); + _dictionaryLocker.Release(); } } + Cloud cloudInstance = CreateCloud(basicIdentity); + _dictionaryLocker.Wait(); + try + { + CloudCache.TryAdd(key, new ClockedCloud { LastAccess = DateTime.Now, Cloud = cloudInstance }); + + if (!_cleanTimer.Enabled) + _cleanTimer.Enabled = true; - if (credentials.CloudType == CloudType.Unkown) + return cloudInstance; + } + finally { - Logger.Info("Cloud type is not detected by user login string"); - throw new InvalidCredentialException("Cloud type is not detected. " + - "Please specify protocol and email in login string, see manuals."); + _dictionaryLocker.Release(); } + } + finally + { + _creationLocker.Release(); + } + } + + private static Cloud CreateCloud(HttpListenerBasicIdentity identity) + { + var credentials = new Credentials(Settings, identity.Name, identity.Password); + + var cloud = new Cloud(Settings, credentials); + Logger.Warn($"{(credentials.CloudType == CloudType.Mail ? "Mail.Ru" : "Yandex.Ru")} " + + $"cloud instance created for {credentials.Login}, " + + $"protocol is {credentials.Protocol}" + + $"{(credentials.AuthenticationUsingBrowser ? ", using browser cookies" : "")}"); - if (credentials.Protocol == Protocol.Autodetect) + return cloud; + } + + private static void RemoveExpired(object sender, System.Timers.ElapsedEventArgs e) + { + _dictionaryLocker.Wait(); + try + { + DateTime threshold = DateTime.Now - TimeSpan.FromMinutes(Settings.CloudInstanceTimeoutMinutes); + foreach (var pair in CloudCache) { - Logger.Info("Protocol is undefined by user credentials"); - throw new InvalidCredentialException("Protocol type is not detected. " + - "Please specify protocol and email in login string, see manuals."); - } + if (pair.Value.LastAccess < threshold) + { + CloudCache.TryRemove(pair.Key, out var removedItem); - var cloud = new Cloud(Settings, credentials); - Logger.Info($"Cloud instance created for {credentials.Login}"); + Credentials credentials = removedItem.Cloud.Credentials; + Logger.Warn($"{(credentials.CloudType == CloudType.Mail ? "Mail.Ru" : "Yandex.Ru")} " + + $"cloud instance for {credentials.Login} is disposed"); - return cloud; + try + { + removedItem.Cloud?.Dispose(); + } + catch { } + } + } + + if (CloudCache.IsEmpty) + _cleanTimer.Enabled = false; + } + finally + { + _dictionaryLocker.Release(); } } } diff --git a/WebDavMailRuCloudStore/CustomHandlers/CopyHandler.cs b/WebDavMailRuCloudStore/CustomHandlers/CopyHandler.cs index 89680ab1..b3b9ab6a 100644 --- a/WebDavMailRuCloudStore/CustomHandlers/CopyHandler.cs +++ b/WebDavMailRuCloudStore/CustomHandlers/CopyHandler.cs @@ -117,7 +117,7 @@ private static async Task CopyAsync(IStoreItem source, IStoreCollection destinat errors.AddResult(newBaseUri, copyResult.Result); } - //do not copy recursively + //do not copy recursively } } } diff --git a/WebDavMailRuCloudStore/CustomHandlers/GetAndHeadHandler.cs b/WebDavMailRuCloudStore/CustomHandlers/GetAndHeadHandler.cs index f74a11fb..1ecd227a 100644 --- a/WebDavMailRuCloudStore/CustomHandlers/GetAndHeadHandler.cs +++ b/WebDavMailRuCloudStore/CustomHandlers/GetAndHeadHandler.cs @@ -213,7 +213,5 @@ private static async Task CopyToAsync(Stream src, Stream dest, long start, long? bytesToRead -= bytesRead; } } - - } } diff --git a/WebDavMailRuCloudStore/CustomHandlers/MkcolHandler.cs b/WebDavMailRuCloudStore/CustomHandlers/MkcolHandler.cs index 4fd7eebf..ee88dc90 100644 --- a/WebDavMailRuCloudStore/CustomHandlers/MkcolHandler.cs +++ b/WebDavMailRuCloudStore/CustomHandlers/MkcolHandler.cs @@ -54,7 +54,7 @@ public async Task HandleRequestAsync(IHttpContext httpContext, IStore stor // Obtain the parent entry var collection = await store.GetCollectionAsync(splitUri.Parent, httpContext).ConfigureAwait(false); - if (collection is not { IsValid: true }) + if (collection is not { IsValid: true }) { // Source not found response.SetStatus(DavStatusCode.Conflict); diff --git a/WebDavMailRuCloudStore/CustomHandlers/MoveHandler.cs b/WebDavMailRuCloudStore/CustomHandlers/MoveHandler.cs index 01fb1cc4..847ae5c9 100644 --- a/WebDavMailRuCloudStore/CustomHandlers/MoveHandler.cs +++ b/WebDavMailRuCloudStore/CustomHandlers/MoveHandler.cs @@ -109,7 +109,7 @@ private static WebDavUri GetDestinationUri(IHttpRequest request) { // Obtain the destination string destinationHeader = request.GetHeaderValue("Destination"); - + if (destinationHeader == null) return null; diff --git a/WebDavMailRuCloudStore/CustomProperties/DavBsiisreadonly.cs b/WebDavMailRuCloudStore/CustomProperties/DavBsiisreadonly.cs index 5ae1f3dd..232090b2 100644 --- a/WebDavMailRuCloudStore/CustomProperties/DavBsiisreadonly.cs +++ b/WebDavMailRuCloudStore/CustomProperties/DavBsiisreadonly.cs @@ -18,4 +18,4 @@ public class DavBsiisreadonly : DavBoolean where TEntry : IStore /// public override XName Name => PropertyName; } -} \ No newline at end of file +} diff --git a/WebDavMailRuCloudStore/CustomProperties/DavCollection.cs b/WebDavMailRuCloudStore/CustomProperties/DavCollection.cs index b09d90d6..ff04ff48 100644 --- a/WebDavMailRuCloudStore/CustomProperties/DavCollection.cs +++ b/WebDavMailRuCloudStore/CustomProperties/DavCollection.cs @@ -18,4 +18,4 @@ public class DavCollection : DavString where TEntry : IStoreItem /// public override XName Name => PropertyName; } -} \ No newline at end of file +} diff --git a/WebDavMailRuCloudStore/CustomProperties/DavHref.cs b/WebDavMailRuCloudStore/CustomProperties/DavHref.cs index 0d8a2142..e74bc0ad 100644 --- a/WebDavMailRuCloudStore/CustomProperties/DavHref.cs +++ b/WebDavMailRuCloudStore/CustomProperties/DavHref.cs @@ -18,4 +18,4 @@ public class DavHref : DavString where TEntry : IStoreItem /// public override XName Name => PropertyName; } -} \ No newline at end of file +} diff --git a/WebDavMailRuCloudStore/CustomProperties/DavLastAccessed.cs b/WebDavMailRuCloudStore/CustomProperties/DavLastAccessed.cs index 9c6bbb99..57da945f 100644 --- a/WebDavMailRuCloudStore/CustomProperties/DavLastAccessed.cs +++ b/WebDavMailRuCloudStore/CustomProperties/DavLastAccessed.cs @@ -13,4 +13,4 @@ public class DavLastAccessed : DavRfc1123Date where TEntry : ISt /// Name of the property. public override XName Name => PropertyName; } -} \ No newline at end of file +} diff --git a/WebDavMailRuCloudStore/CustomProperties/DavLoctoken.cs b/WebDavMailRuCloudStore/CustomProperties/DavLoctoken.cs index 8d62a117..27011b48 100644 --- a/WebDavMailRuCloudStore/CustomProperties/DavLoctoken.cs +++ b/WebDavMailRuCloudStore/CustomProperties/DavLoctoken.cs @@ -18,4 +18,4 @@ public class DavLoctoken : DavString where TEntry : IStoreItem /// public override XName Name => PropertyName; } -} \ No newline at end of file +} diff --git a/WebDavMailRuCloudStore/CustomProperties/DavSharedLink.cs b/WebDavMailRuCloudStore/CustomProperties/DavSharedLink.cs index a86a3c90..6374e5e8 100644 --- a/WebDavMailRuCloudStore/CustomProperties/DavSharedLink.cs +++ b/WebDavMailRuCloudStore/CustomProperties/DavSharedLink.cs @@ -10,5 +10,4 @@ public class DavSharedLink : DavString where TEntry : IStoreItem public override XName Name => PropertyName; } - -} \ No newline at end of file +} diff --git a/WebDavMailRuCloudStore/CustomProperties/DavSrtfileattributes.cs b/WebDavMailRuCloudStore/CustomProperties/DavSrtfileattributes.cs index 2a3175fa..89aa3e51 100644 --- a/WebDavMailRuCloudStore/CustomProperties/DavSrtfileattributes.cs +++ b/WebDavMailRuCloudStore/CustomProperties/DavSrtfileattributes.cs @@ -8,7 +8,7 @@ namespace YaR.Clouds.WebDavStore.CustomProperties { - public class DavSrtfileattributes : DavTypedProperty where TEntry : IStoreItem + public class DavSrtFileAttributes : DavTypedProperty where TEntry : IStoreItem { private class FileAttributesConverter : IConverter { diff --git a/WebDavMailRuCloudStore/Extensions.cs b/WebDavMailRuCloudStore/Extensions.cs index 415b3561..8fb17458 100644 --- a/WebDavMailRuCloudStore/Extensions.cs +++ b/WebDavMailRuCloudStore/Extensions.cs @@ -21,11 +21,11 @@ public static async Task Remove(this Cloud cloud, IStoreItem item) public static Task Rename(this Cloud cloud, IStoreItem item, string destinationName) { - if (item == null) + if (item == null) throw new ArgumentNullException(nameof(item)); - if (string.IsNullOrEmpty(destinationName)) + if (string.IsNullOrEmpty(destinationName)) throw new ArgumentNullException(nameof(destinationName)); - if (item is not ILocalStoreItem storeItem) + if (item is not ILocalStoreItem storeItem) throw new ArgumentException($"{nameof(ILocalStoreItem)} required.", nameof(item)); return cloud.Rename(storeItem.EntryInfo, destinationName); @@ -33,20 +33,20 @@ public static Task Rename(this Cloud cloud, IStoreItem item, string destin public static Task Move(this Cloud cloud, IStoreItem item, string destinationPath) { - if (item == null) + if (item == null) throw new ArgumentNullException(nameof(item)); - if (string.IsNullOrEmpty(destinationPath)) + if (string.IsNullOrEmpty(destinationPath)) throw new ArgumentNullException(nameof(destinationPath)); - if (item is not ILocalStoreItem storeItem) + if (item is not ILocalStoreItem storeItem) throw new ArgumentException($"{nameof(ILocalStoreItem)} required.", nameof(item)); return cloud.MoveAsync(storeItem.EntryInfo, destinationPath); } public static string GetFullPath(this IStoreItem item) { - if (item == null) + if (item == null) throw new ArgumentNullException(nameof(item)); - if (item is not ILocalStoreItem storeItem) + if (item is not ILocalStoreItem storeItem) throw new ArgumentException($"{nameof(ILocalStoreItem)} required.", nameof(item)); return storeItem.FullPath; diff --git a/WebDavMailRuCloudStore/RequestHandlerFactory.cs b/WebDavMailRuCloudStore/RequestHandlerFactory.cs index 3911beb5..1e70bee4 100644 --- a/WebDavMailRuCloudStore/RequestHandlerFactory.cs +++ b/WebDavMailRuCloudStore/RequestHandlerFactory.cs @@ -5,10 +5,10 @@ namespace YaR.Clouds.WebDavStore { - public class RequestHandlerFactory : IRequestHandlerFactory + public class RequestHandlerFactory : IRequestHandlerFactory { private static readonly IDictionary RequestHandlers = new Dictionary - { + { { "COPY", new CopyHandler() }, { "DELETE", new DeleteHandler() }, { "GET", new GetAndHeadHandler() }, @@ -26,8 +26,8 @@ public class RequestHandlerFactory : IRequestHandlerFactory public IRequestHandler GetRequestHandler(IHttpContext httpContext) { - return !RequestHandlers.TryGetValue(httpContext.Request.HttpMethod, out var requestHandler) - ? null + return !RequestHandlers.TryGetValue(httpContext.Request.HttpMethod, out var requestHandler) + ? null : requestHandler; } diff --git a/WebDavMailRuCloudStore/StoreBase/EmptyLockingManager.cs b/WebDavMailRuCloudStore/StoreBase/EmptyLockingManager.cs index b5412401..46d9fcf2 100644 --- a/WebDavMailRuCloudStore/StoreBase/EmptyLockingManager.cs +++ b/WebDavMailRuCloudStore/StoreBase/EmptyLockingManager.cs @@ -75,10 +75,10 @@ private static ActiveLock GetActiveLockInfo(ItemLockInfo itemLockInfo) public IEnumerable GetActiveLockInfo(IStoreItem item) { - return EmptyActiveLockEntry; + return EmptyActiveLockEntry; } private static readonly ActiveLock[] EmptyActiveLockEntry = Array.Empty(); - + public IEnumerable GetSupportedLocks(IStoreItem item) { @@ -96,4 +96,4 @@ public bool HasLock(IStoreItem item, WebDavUri lockToken) return false; } } -} \ No newline at end of file +} diff --git a/WebDavMailRuCloudStore/StoreBase/LocalStore.cs b/WebDavMailRuCloudStore/StoreBase/LocalStore.cs index ddff200d..35275415 100644 --- a/WebDavMailRuCloudStore/StoreBase/LocalStore.cs +++ b/WebDavMailRuCloudStore/StoreBase/LocalStore.cs @@ -30,7 +30,7 @@ public LocalStore(bool isWritable = true, ILockingManager lockingManager = null, public async Task GetItemAsync(WebDavUri uri, IHttpContext httpContext) { var path = uri.Path; - + try { var entry = await CloudManager diff --git a/WebDavMailRuCloudStore/StoreBase/LocalStoreCollection.cs b/WebDavMailRuCloudStore/StoreBase/LocalStoreCollection.cs index e030901a..a64fa942 100644 --- a/WebDavMailRuCloudStore/StoreBase/LocalStoreCollection.cs +++ b/WebDavMailRuCloudStore/StoreBase/LocalStoreCollection.cs @@ -30,7 +30,7 @@ public class LocalStoreCollection : ILocalStoreCollection private string DebuggerDisplay => FolderWithDescendants.FullPath; - public LocalStoreCollection(IHttpContext context, Folder folderWithChildren, bool isWritable, + public LocalStoreCollection(IHttpContext context, Folder folderWithChildren, bool isWritable, LocalStore store) { _context = context; @@ -95,7 +95,7 @@ public IEnumerable Items public Task GetItemAsync(string name, IHttpContext httpContext) { - var res = name == string.Empty + var res = name == string.Empty ? this : Items.FirstOrDefault(i => i.Name == name); @@ -308,7 +308,7 @@ public override int GetHashCode() public override bool Equals(object obj) { - return obj is LocalStoreCollection storeCollection && + return obj is LocalStoreCollection storeCollection && storeCollection.FolderWithDescendants.FullPath.Equals(FolderWithDescendants.FullPath, StringComparison.CurrentCultureIgnoreCase); } } diff --git a/WebDavMailRuCloudStore/StoreBase/LocalStoreCollectionProps.cs b/WebDavMailRuCloudStore/StoreBase/LocalStoreCollectionProps.cs index dfb11260..24efe009 100644 --- a/WebDavMailRuCloudStore/StoreBase/LocalStoreCollectionProps.cs +++ b/WebDavMailRuCloudStore/StoreBase/LocalStoreCollectionProps.cs @@ -48,7 +48,7 @@ public LocalStoreCollectionProps(Func isEnabledPropFunc) // } //}, ////==================================================================================================== - + new DavIsreadonly { diff --git a/WebDavMailRuCloudStore/StoreBase/LocalStoreItem.cs b/WebDavMailRuCloudStore/StoreBase/LocalStoreItem.cs index a8aad79b..b59deee0 100644 --- a/WebDavMailRuCloudStore/StoreBase/LocalStoreItem.cs +++ b/WebDavMailRuCloudStore/StoreBase/LocalStoreItem.cs @@ -63,7 +63,7 @@ private Stream OpenReadStream(Cloud cloud, long? start, long? end) } - public Task GetReadableStreamAsync(IHttpContext httpContext) //=> + public Task GetReadableStreamAsync(IHttpContext httpContext) //=> { var cloud = CloudManager.Instance((HttpListenerBasicIdentity)httpContext.Session.Principal.Identity); var range = httpContext.Request.GetRange(); @@ -131,16 +131,17 @@ void ServerProcessFinishedAction() // Copy the information to the destination stream - using (var outputStream = IsWritable - ? await CloudManager.Instance(httpContext.Session.Principal.Identity).GetFileUploadStream(FileInfo.FullPath, FileInfo.Size, StreamCopiedAction, ServerProcessFinishedAction).ConfigureAwait(false) - : null) - { + using var outputStream = IsWritable + ? await CloudManager + .Instance(httpContext.Session.Principal.Identity) + .GetFileUploadStream(FileInfo.FullPath, FileInfo.Size, StreamCopiedAction, ServerProcessFinishedAction) + .ConfigureAwait(false) + : null; #if NET48 - await inputStream.CopyToAsync(outputStream).ConfigureAwait(false); + await inputStream.CopyToAsync(outputStream).ConfigureAwait(false); #else - await inputStream.CopyToAsync(outputStream, cts.Token).ConfigureAwait(false); + await inputStream.CopyToAsync(outputStream, cts.Token).ConfigureAwait(false); #endif - } return DavStatusCode.Ok; } catch (IOException ioException) when (ioException.IsDiskFull()) @@ -172,7 +173,7 @@ public async Task CopyAsync(IStoreCollection destination, strin // Create the item in the destination collection var result = await destination.CreateItemAsync(name, overwrite, httpContext).ConfigureAwait(false); - if (result.Item == null) + if (result.Item == null) return new StoreItemResult(result.Result, result.Item); using (var sourceStream = await GetReadableStreamAsync(httpContext).ConfigureAwait(false)) @@ -203,7 +204,7 @@ public override int GetHashCode() public override bool Equals(object obj) { - return obj is LocalStoreItem storeItem && + return obj is LocalStoreItem storeItem && storeItem.FileInfo.FullPath.Equals(FileInfo.FullPath, StringComparison.CurrentCultureIgnoreCase); } @@ -227,7 +228,4 @@ private static byte[] GetBytes(string str) return bytes; } } - - - } diff --git a/WebDavMailRuCloudStore/StoreBase/LocalStoreItemProps.cs b/WebDavMailRuCloudStore/StoreBase/LocalStoreItemProps.cs index fef88dc3..602964e7 100644 --- a/WebDavMailRuCloudStore/StoreBase/LocalStoreItemProps.cs +++ b/WebDavMailRuCloudStore/StoreBase/LocalStoreItemProps.cs @@ -139,6 +139,6 @@ public LocalStoreItemProps(Func isEnabledPropFunc) } public IEnumerable> Props => _props; - private readonly DavProperty[] _props; + private readonly DavProperty[] _props; } -} \ No newline at end of file +} diff --git a/WebDavMailRuCloudStore/TwoFaHandlers.cs b/WebDavMailRuCloudStore/TwoFaHandlers.cs index 5318c4bd..4d68420e 100644 --- a/WebDavMailRuCloudStore/TwoFaHandlers.cs +++ b/WebDavMailRuCloudStore/TwoFaHandlers.cs @@ -10,7 +10,7 @@ public class TwoFactorAuthHandlerInfo { public string Name { get; set; } - public IEnumerable> Parames { get; set; } + public IEnumerable> Parameters { get; set; } } public static class TwoFaHandlers @@ -33,13 +33,13 @@ public static ITwoFaHandler Get(TwoFactorAuthHandlerInfo handlerInfo) ITwoFaHandler inst = null; try { - inst = (ITwoFaHandler)Activator.CreateInstance(type, handlerInfo.Parames); + inst = (ITwoFaHandler)Activator.CreateInstance(type, handlerInfo.Parameters); } catch (Exception e) { Logger.Error($"Cannot create instance of 2FA handler {handlerInfo.Name}. {e}"); } - + return inst; } @@ -55,8 +55,8 @@ private static IEnumerable GetHandlers() { try { - //If an application has been copied from the web, it is flagged by Windows as being a web application, even if it resides on the local computer. - //You can change that designation by changing the file properties, or you can use the element to grant the assembly full trust. + //If an application has been copied from the web, it is flagged by Windows as being a web application, even if it resides on the local computer. + //You can change that designation by changing the file properties, or you can use the element to grant the assembly full trust. //As an alternative, you can use the UnsafeLoadFrom method to load a local assembly that the operating system has flagged as having been loaded from the web. Assembly assembly = Assembly.UnsafeLoadFrom(file); @@ -71,4 +71,4 @@ private static IEnumerable GetHandlers() return types; } } -} \ No newline at end of file +} diff --git a/YandexAuthBrowser/AuthForm.Designer.cs b/YandexAuthBrowser/AuthForm.Designer.cs deleted file mode 100644 index 924472c5..00000000 --- a/YandexAuthBrowser/AuthForm.Designer.cs +++ /dev/null @@ -1,82 +0,0 @@ -namespace YandexAuthBrowser -{ - partial class AuthForm - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if( disposing && ( components != null ) ) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - components= new System.ComponentModel.Container() ; - System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(AuthForm)); - WebView= new Microsoft.Web.WebView2.WinForms.WebView2() ; - DelayTimer= new System.Windows.Forms.Timer(components) ; - NobodyHomeTimer= new System.Windows.Forms.Timer(components) ; - ( (System.ComponentModel.ISupportInitialize) WebView ).BeginInit(); - SuspendLayout(); - // - // WebView - // - WebView.AllowExternalDrop= true ; - WebView.CreationProperties= null ; - WebView.DefaultBackgroundColor= Color.White ; - WebView.Dock= DockStyle.Fill ; - WebView.Location= new Point(0, 0) ; - WebView.Name= "WebView" ; - WebView.Size= new Size(1165, 895) ; - WebView.TabIndex= 1 ; - WebView.ZoomFactor= 1D ; - WebView.NavigationCompleted+= WebView_NavigationCompleted ; - // - // DelayTimer - // - DelayTimer.Interval= 3000 ; - DelayTimer.Tick+= DelayTimer_Tick ; - // - // NobodyHomeTimer - // - NobodyHomeTimer.Interval= 3000 ; - NobodyHomeTimer.Tick+= NobodyHomeTimer_Tick ; - // - // AuthForm - // - AutoScaleDimensions= new SizeF(12F, 30F) ; - AutoScaleMode= AutoScaleMode.Font ; - ClientSize= new Size(1165, 895) ; - Controls.Add(WebView); - Icon= (Icon) resources.GetObject("$this.Icon") ; - Name= "AuthForm" ; - Text= "Авторизация на Яндекс.Диск" ; - FormClosed+= AuthForm_FormClosed ; - Load+= AuthForm_Load ; - ( (System.ComponentModel.ISupportInitialize) WebView ).EndInit(); - ResumeLayout(false); - } - - #endregion - private Microsoft.Web.WebView2.WinForms.WebView2 WebView; - private System.Windows.Forms.Timer DelayTimer; - private System.Windows.Forms.Timer NobodyHomeTimer; - } -} \ No newline at end of file diff --git a/YandexAuthBrowser/AuthForm.cs b/YandexAuthBrowser/AuthForm.cs deleted file mode 100644 index 4d30e858..00000000 --- a/YandexAuthBrowser/AuthForm.cs +++ /dev/null @@ -1,203 +0,0 @@ -using System.Text.Json; -using System.Text.RegularExpressions; -using Microsoft.Web.WebView2.Core; - -namespace YandexAuthBrowser -{ - public partial class AuthForm : Form - { - [GeneratedRegex("\\\\?\"sk\\\\?\":\\\\?\"(?.*?)\\\\?\"")] - private static partial Regex SkRegex(); - - [GeneratedRegex("\\\\?\"yandexuid\\\\?\":\\\\?\"(?.*?)\\\\?\"")] - private static partial Regex UuidRegex(); - - [GeneratedRegex("\\\\?\"login\\\\?\":\\\\?\"(?.*?)\\\\?\"")] - private static partial Regex LoginRegex(); - - private readonly string? DesiredLogin; - private readonly BrowserAppResponse Response; - private bool WeAreFinished; - - public AuthForm(string desiredLogin, BrowserAppResponse response) - { - WeAreFinished = false; - - InitializeComponent(); - - DesiredLogin = desiredLogin; - Response = response; - - if (!string.IsNullOrEmpty(DesiredLogin)) - { - this.Text += $" : {DesiredLogin}"; - } - - /* - * , - * , , - * ... - * : - * - , - * , , ; - * , - * - ? - * - - , - * 3 - * . - * , 3 - . - */ - - WindowState = FormWindowState.Normal; - var screen = Screen.GetWorkingArea(this); - Top = screen.Height + 100; - ShowInTaskbar = false; - NobodyHomeTimer.Interval = 4 * 60_000; // 4 minutes to login - NobodyHomeTimer.Enabled = true; - DelayTimer.Interval = 3000; - DelayTimer.Enabled = true; - } - private void DelayTimer_Tick(object sender, EventArgs e) - { - if (WeAreFinished) - return; - - DelayTimer.Enabled = false; - var screen = Screen.GetWorkingArea(this); - - Top = screen.Height / 2 - Height / 2; - WindowState = FormWindowState.Maximized; - ShowInTaskbar = true; - } - - private void AuthForm_Load(object sender, EventArgs e) - { - // - , - // 5 . , -. - // - . - for (int retry = 5; retry > 0; retry--) - { - try - { - _ = InitializeAsync(); - retry = 0; - } - catch (Exception) - { - } - } - } - - private async Task InitializeAsync() - { - var env = await CoreWebView2Environment.CreateAsync(userDataFolder: DesiredLogin ?? "default"); - - // if the control is not visible - this will keep waiting - await WebView.EnsureCoreWebView2Async(env); - - // any code here is not fired until the control becomes visible - WebView.CoreWebView2.Navigate("https://disk.yandex.ru/client/disk"); - } - - private async void WebView_NavigationCompleted(object sender, Microsoft.Web.WebView2.Core.CoreWebView2NavigationCompletedEventArgs e) - { - if (e.IsSuccess && WebView.CoreWebView2.Source.StartsWith("https://disk.yandex.ru/client/disk")) - { - var htmlEncoded = await WebView.CoreWebView2.ExecuteScriptAsync("document.body.outerHTML"); - var html = JsonDocument.Parse(htmlEncoded).RootElement.ToString(); - - var matchSk = SkRegex().Match(html); - var matchUuid = UuidRegex().Match(html); - var matchLogin = LoginRegex().Match(html); - - var sk = matchSk.Success ? matchSk.Groups["sk"].Value : string.Empty; - var uuid = matchUuid.Success ? matchUuid.Groups["uuid"].Value : string.Empty; - var login = matchLogin.Success ? matchLogin.Groups["login"].Value : string.Empty; - - // Ivan Ivan@yandex.ru, - // , , @ - - if (!string.IsNullOrEmpty(sk) && !string.IsNullOrEmpty(uuid) && - !string.IsNullOrEmpty(login) && - GetNameOnly(login).Equals(GetNameOnly(DesiredLogin), StringComparison.OrdinalIgnoreCase)) - { - Response.Login = login; - Response.Sk = sk; - Response.Uuid = uuid; - Response.Cookies = new List(); - - var list = await WebView.CoreWebView2.CookieManager.GetCookiesAsync("https://disk.yandex.ru/client/disk"); - foreach (var item in list) - { - BrowserAppCookieResponse cookie = new BrowserAppCookieResponse() - { - Name = item.Name, - Value = item.Value, - Path = item.Path, - Domain = item.Domain - }; - Response.Cookies.Add(cookie); - } - - WeAreFinished = true; - if (this.InvokeRequired) - { - this.Invoke((MethodInvoker)delegate - { - // Running on the UI thread - DelayTimer.Enabled = false; - this.Close(); - }); - } - else - { - // Running on the UI thread - DelayTimer.Enabled = false; - this.Close(); - } - } - } - } - - private static string GetNameOnly(string? value) - { - if (string.IsNullOrEmpty(value)) - return string.Empty; - int pos = value.IndexOf('@'); - if (pos == 0) - return string.Empty; - if (pos > 0) - return value.Substring(0, pos); - return value; - } - - private void AuthForm_FormClosed(object sender, FormClosedEventArgs e) - { - try - { - WebView?.Dispose(); - } - catch { } - } - - private void NobodyHomeTimer_Tick(object sender, EventArgs e) - { - WeAreFinished = true; - if (this.InvokeRequired) - { - this.Invoke((MethodInvoker)delegate - { - // Running on the UI thread - DelayTimer.Enabled = false; - this.Close(); - }); - } - else - { - // Running on the UI thread - DelayTimer.Enabled = false; - this.Close(); - } - } - } -} diff --git a/YandexAuthBrowser/JsonResult.cs b/YandexAuthBrowser/JsonResult.cs deleted file mode 100644 index 19c74501..00000000 --- a/YandexAuthBrowser/JsonResult.cs +++ /dev/null @@ -1,47 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Text; -using System.Threading.Tasks; -using Newtonsoft.Json; - -namespace YandexAuthBrowser -{ - public class BrowserAppResponse - { - [JsonProperty("ErrorMessage")] - public string? ErrorMessage { get; set; } - - [JsonProperty("Login")] - public string? Login { get; set; } - - [JsonProperty("Uuid")] - public string? Uuid { get; set; } - - [JsonProperty("Sk")] - public string? Sk { get; set; } - - [JsonProperty("Cookies")] - public List? Cookies { get; set; } - - public string Serialize() - { - return JsonConvert.SerializeObject(this); - } - } - public class BrowserAppCookieResponse - { - [JsonProperty("name")] - public string? Name { get; set; } - - [JsonProperty("Value")] - public string? Value { get; set; } - - [JsonProperty("Path")] - public string? Path { get; set; } - - [JsonProperty("Domain")] - public string? Domain { get; set; } - } -} diff --git a/YandexAuthBrowser/Program.cs b/YandexAuthBrowser/Program.cs deleted file mode 100644 index 09367e8e..00000000 --- a/YandexAuthBrowser/Program.cs +++ /dev/null @@ -1,15 +0,0 @@ -namespace YandexAuthBrowser -{ - internal static class Program - { - [STAThread] - static void Main() - { - // To customize application configuration such as set high DPI settings or default font, - // see https://aka.ms/applicationconfiguration. - ApplicationConfiguration.Initialize(); - - Application.Run(new ResidentForm()); - } - } -} diff --git a/YandexAuthBrowser/ResidentForm.Designer.cs b/YandexAuthBrowser/ResidentForm.Designer.cs deleted file mode 100644 index f192f65b..00000000 --- a/YandexAuthBrowser/ResidentForm.Designer.cs +++ /dev/null @@ -1,243 +0,0 @@ -namespace YandexAuthBrowser -{ - partial class ResidentForm - { - /// - /// Required designer variable. - /// - private System.ComponentModel.IContainer components = null; - - /// - /// Clean up any resources being used. - /// - /// true if managed resources should be disposed; otherwise, false. - protected override void Dispose(bool disposing) - { - if( disposing && ( components != null ) ) - { - components.Dispose(); - } - base.Dispose(disposing); - } - - #region Windows Form Designer generated code - - /// - /// Required method for Designer support - do not modify - /// the contents of this method with the code editor. - /// - private void InitializeComponent() - { - components= new System.ComponentModel.Container() ; - System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(ResidentForm)); - label1= new Label() ; - Port= new TextBox() ; - NotifyIcon= new NotifyIcon(components) ; - label2= new Label() ; - Password= new TextBox() ; - GeneratePassword= new LinkLabel() ; - HideButton= new Button() ; - Lock= new CheckBox() ; - HideTimer= new System.Windows.Forms.Timer(components) ; - AuthButton= new Button() ; - CopyPortPic= new PictureBox() ; - CopyPasswordPic= new PictureBox() ; - SaveConfigTimer= new System.Windows.Forms.Timer(components) ; - label3= new Label() ; - Counter= new Label() ; - ( (System.ComponentModel.ISupportInitialize) CopyPortPic ).BeginInit(); - ( (System.ComponentModel.ISupportInitialize) CopyPasswordPic ).BeginInit(); - SuspendLayout(); - // - // label1 - // - label1.AutoSize= true ; - label1.Location= new Point(22, 21) ; - label1.Name= "label1" ; - label1.Size= new Size(329, 30) ; - label1.TabIndex= 1 ; - label1.Text= "Порт для входящего соединения" ; - // - // Port - // - Port.Location= new Point(22, 54) ; - Port.Name= "Port" ; - Port.Size= new Size(90, 35) ; - Port.TabIndex= 2 ; - Port.Text= "54321" ; - Port.TextChanged+= Port_TextChanged ; - Port.Validating+= Port_Validating ; - // - // NotifyIcon - // - NotifyIcon.BalloonTipIcon= ToolTipIcon.Info ; - NotifyIcon.BalloonTipText= "WebDAVCloud для Яндекс.Диск" ; - NotifyIcon.BalloonTipTitle= "Резидентная часть для отображения на десктопе окна браузера для входа на Яндекс.Диск" ; - NotifyIcon.Icon= (Icon) resources.GetObject("NotifyIcon.Icon") ; - NotifyIcon.Text= "WebDAVCloud" ; - NotifyIcon.MouseDoubleClick+= NotifyIcon_MouseDoubleClick ; - // - // label2 - // - label2.AutoSize= true ; - label2.Location= new Point(22, 100) ; - label2.Name= "label2" ; - label2.Size= new Size(353, 30) ; - label2.TabIndex= 3 ; - label2.Text= "Пароль для входящего соединения" ; - // - // Password - // - Password.Location= new Point(22, 133) ; - Password.Name= "Password" ; - Password.Size= new Size(523, 35) ; - Password.TabIndex= 4 ; - Password.TextChanged+= Password_TextChanged ; - // - // GeneratePassword - // - GeneratePassword.AutoSize= true ; - GeneratePassword.Location= new Point(22, 171) ; - GeneratePassword.Name= "GeneratePassword" ; - GeneratePassword.Size= new Size(429, 30) ; - GeneratePassword.TabIndex= 5 ; - GeneratePassword.TabStop= true ; - GeneratePassword.Text= "Сгенерировать пароль в виде нового GUID" ; - GeneratePassword.LinkClicked+= GeneratePassword_LinkClicked ; - // - // HideButton - // - HideButton.Location= new Point(22, 260) ; - HideButton.Name= "HideButton" ; - HideButton.Size= new Size(564, 40) ; - HideButton.TabIndex= 6 ; - HideButton.Text= "Свернуть программу в System Tray" ; - HideButton.UseVisualStyleBackColor= true ; - HideButton.Click+= HideButton_Click ; - // - // Lock - // - Lock.AutoSize= true ; - Lock.Cursor= Cursors.Hand ; - Lock.Location= new Point(505, 14) ; - Lock.Name= "Lock" ; - Lock.Size= new Size(81, 34) ; - Lock.TabIndex= 0 ; - Lock.Text= "Lock" ; - Lock.UseVisualStyleBackColor= true ; - Lock.CheckedChanged+= Lock_CheckedChanged ; - // - // HideTimer - // - HideTimer.Tick+= HideTimer_Tick ; - // - // AuthButton - // - AuthButton.Location= new Point(505, 54) ; - AuthButton.Name= "AuthButton" ; - AuthButton.Size= new Size(81, 40) ; - AuthButton.TabIndex= 7 ; - AuthButton.Text= "Test" ; - AuthButton.UseVisualStyleBackColor= true ; - AuthButton.Click+= AuthButton_Click ; - // - // CopyPortPic - // - CopyPortPic.Cursor= Cursors.Hand ; - CopyPortPic.Image= (Image) resources.GetObject("CopyPortPic.Image") ; - CopyPortPic.InitialImage= (Image) resources.GetObject("CopyPortPic.InitialImage") ; - CopyPortPic.Location= new Point(118, 54) ; - CopyPortPic.Name= "CopyPortPic" ; - CopyPortPic.Size= new Size(35, 35) ; - CopyPortPic.SizeMode= PictureBoxSizeMode.Zoom ; - CopyPortPic.TabIndex= 9 ; - CopyPortPic.TabStop= false ; - CopyPortPic.Click+= CopyPortPic_Click ; - // - // CopyPasswordPic - // - CopyPasswordPic.Cursor= Cursors.Hand ; - CopyPasswordPic.Image= (Image) resources.GetObject("CopyPasswordPic.Image") ; - CopyPasswordPic.InitialImage= (Image) resources.GetObject("CopyPasswordPic.InitialImage") ; - CopyPasswordPic.Location= new Point(551, 133) ; - CopyPasswordPic.Name= "CopyPasswordPic" ; - CopyPasswordPic.Size= new Size(35, 35) ; - CopyPasswordPic.SizeMode= PictureBoxSizeMode.Zoom ; - CopyPasswordPic.TabIndex= 9 ; - CopyPasswordPic.TabStop= false ; - CopyPasswordPic.Click+= CopyPasswordPic_Click ; - // - // SaveConfigTimer - // - SaveConfigTimer.Tick+= SaveConfigTimer_Tick ; - // - // label3 - // - label3.AutoSize= true ; - label3.Location= new Point(22, 312) ; - label3.Name= "label3" ; - label3.Size= new Size(564, 30) ; - label3.TabIndex= 1 ; - label3.Text= "Для выхода используйте меню иконки в системном трее" ; - // - // Counter - // - Counter.AutoSize= true ; - Counter.Location= new Point(22, 211) ; - Counter.Name= "Counter" ; - Counter.Size= new Size(29, 30) ; - Counter.TabIndex= 1 ; - Counter.Text= "--" ; - // - // ResidentForm - // - AutoScaleDimensions= new SizeF(12F, 30F) ; - AutoScaleMode= AutoScaleMode.Font ; - ClientSize= new Size(612, 354) ; - Controls.Add(CopyPasswordPic); - Controls.Add(CopyPortPic); - Controls.Add(AuthButton); - Controls.Add(Lock); - Controls.Add(HideButton); - Controls.Add(GeneratePassword); - Controls.Add(Password); - Controls.Add(Port); - Controls.Add(label2); - Controls.Add(Counter); - Controls.Add(label3); - Controls.Add(label1); - FormBorderStyle= FormBorderStyle.FixedDialog ; - Icon= (Icon) resources.GetObject("$this.Icon") ; - MaximizeBox= false ; - MinimizeBox= false ; - Name= "ResidentForm" ; - StartPosition= FormStartPosition.CenterScreen ; - Text= "WebDAVCloud browser authentication" ; - FormClosing+= ResidentForm_FormClosing ; - Load+= ResidentForm_Load ; - Move+= ResidentForm_Move ; - ( (System.ComponentModel.ISupportInitialize) CopyPortPic ).EndInit(); - ( (System.ComponentModel.ISupportInitialize) CopyPasswordPic ).EndInit(); - ResumeLayout(false); - PerformLayout(); - } - - #endregion - - private Label label1; - private TextBox Port; - private NotifyIcon NotifyIcon; - private Label label2; - private TextBox Password; - private LinkLabel GeneratePassword; - private Button HideButton; - private CheckBox Lock; - private System.Windows.Forms.Timer HideTimer; - private Button AuthButton; - private PictureBox CopyPortPic; - private PictureBox CopyPasswordPic; - private System.Windows.Forms.Timer SaveConfigTimer; - private Label label3; - private Label Counter; - } -} \ No newline at end of file diff --git a/YandexAuthBrowser/ResidentForm.cs b/YandexAuthBrowser/ResidentForm.cs deleted file mode 100644 index eedf7c9d..00000000 --- a/YandexAuthBrowser/ResidentForm.cs +++ /dev/null @@ -1,398 +0,0 @@ -using System.ComponentModel; -using System.Configuration; -using System.Net; -using System.Text; -using System.Text.RegularExpressions; - -/* - * Частично код взят отсюда: - * https://gist.github.com/define-private-public/d05bc52dd0bed1c4699d49e2737e80e7 - */ - -namespace YandexAuthBrowser -{ - public partial class ResidentForm : Form - { - [GeneratedRegex("http://[^/]*/(?.*?)/(?.*?)/", RegexOptions.IgnoreCase)] - private static partial Regex CompiledUrlRegex(); - private static readonly Regex UrlRegex = CompiledUrlRegex(); - - private HttpListener? Listener; - private bool RunServer = false; - private string PreviousPort; - public delegate void Execute(string desiredLogin, BrowserAppResponse response); - public Execute AuthExecuteDelegate; - private readonly int? SavedTop = null; - private readonly int? SavedLeft = null; - private SemaphoreSlim _showBrowserLocker; - private bool _doNotSave = false; - - private int AuthenticationOkCounter = 0; - private int AuthenticationFailCounter = 0; - - - public ResidentForm() - { - InitializeComponent(); - - _showBrowserLocker = new SemaphoreSlim(1, 1); - - var screen = Screen.GetWorkingArea(this); - Top = screen.Height + 100; - ShowInTaskbar = false; - - NotifyIcon.Visible = true; - -#if DEBUG - _doNotSave = true; - Port.Text = "54322"; - Password.Text = "adb4bcd5-b4b6-45b7-bb7d-b38470917448"; - _doNotSave = false; -#endif - - // Get the current configuration file. - Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None); - - _doNotSave = true; - string? value = config.AppSettings?.Settings?["port"]?.Value; - if (!string.IsNullOrWhiteSpace(value) && int.TryParse(value, out _)) - Port.Text = value; - value = config.AppSettings?.Settings?["password"]?.Value; - if (!string.IsNullOrWhiteSpace(value)) - Password.Text = value; - - value = config.AppSettings?.Settings?["Top"]?.Value; - if (!string.IsNullOrWhiteSpace(value) && int.TryParse(value, out int top)) - SavedTop = top; - value = config.AppSettings?.Settings?["Left"]?.Value; - if (!string.IsNullOrWhiteSpace(value) && int.TryParse(value, out int left)) - SavedLeft = left; - _doNotSave = false; - - - PreviousPort = Port.Text; - - NotifyIcon.ContextMenuStrip = new ContextMenuStrip(); - NotifyIcon.ContextMenuStrip.Items.Add("Показать окно", null, NotifyIcon_ShowClick); - NotifyIcon.ContextMenuStrip.Items.Add("Выход", null, NotifyIcon_ExitClick); - - AuthExecuteDelegate = OpenDialog; - - Counter.Text = ""; - - StartServer(); - } - - private void ResidentForm_Load(object sender, EventArgs e) - { - Lock.Checked = true; - Lock.Focus(); - HideTimer.Interval = 100; - HideTimer.Enabled = true; - } - private void HideTimer_Tick(object sender, EventArgs e) - { - HideTimer.Enabled = false; - - HideShow(false); - } - - private void HideShow(bool show) - { - if (show) - { - if (!ShowInTaskbar) - { - var screen = Screen.GetWorkingArea(this); - - if (SavedTop.HasValue && SavedLeft.HasValue && - SavedTop.Value >= 0 && SavedTop.Value + Height < screen.Height && - SavedLeft.Value >= 0 && SavedLeft.Value + Width < screen.Width) - - { - Top = SavedTop.Value; - Left = SavedLeft.Value; - } - else - { - Left = screen.Width - Width - 10; - Top = screen.Height - Height - 100; - } - ShowInTaskbar = true; - } - Visible = true; - } - else - { - Visible = false; - } - } - private void ResidentForm_Move(object sender, EventArgs e) - { - if (Visible) - { - SaveConfigTimer.Interval = 1000; - SaveConfigTimer.Enabled = true; - } - - } - private void SaveConfigTimer_Tick(object sender, EventArgs e) - { - if (_doNotSave) - return; - - SaveConfigTimer.Enabled = false; - - Configuration config = - ConfigurationManager.OpenExeConfiguration( - ConfigurationUserLevel.None); - - config.AppSettings.Settings.Remove("Top"); - config.AppSettings.Settings.Remove("Left"); - - config.AppSettings.Settings.Add("Top", Top.ToString()); - config.AppSettings.Settings.Add("Left", Left.ToString()); - - // Save the configuration file. - config.Save(ConfigurationSaveMode.Modified); - } - private void NotifyIcon_MouseDoubleClick(object? sender, MouseEventArgs e) - { - HideShow(!Visible); - } - - /* - * Метод - * ResidentForm_FormClosed( object? sender, FormClosingEventArgs e ) - * здесь не использовать, т.к. событие перекрывается и обрабатывается - * в HiddenContent. См. там. - */ - - private void ResidentForm_FormClosing(object? sender, FormClosingEventArgs e) - { - HideShow(false); - e.Cancel = RunServer ? /*просто закрывается окно*/ true : /*Выход в меню TrayIcon*/ false; - } - private void NotifyIcon_ExitClick(object? sender, EventArgs e) - { - NotifyIcon.Visible = false; - StopServer(); - if (SaveConfigTimer.Enabled) - { - SaveConfigTimer.Enabled = false; - SaveConfig(); - } - - // При вызове Close дальше будет обработка в HiddenContext, см. там. - Close(); - } - private void NotifyIcon_ShowClick(object? sender, EventArgs e) - { - HideShow(true); - } - private void HideButton_Click(object sender, EventArgs e) - { - HideShow(false); - } - private void SaveConfig() - { - if (_doNotSave) - return; - - // Get the current configuration file. - Configuration config = - ConfigurationManager.OpenExeConfiguration( - ConfigurationUserLevel.None); - - config.AppSettings.Settings.Remove("port"); - config.AppSettings.Settings.Remove("password"); - - config.AppSettings.Settings.Add("port", Port.Text); - config.AppSettings.Settings.Add("password", Password.Text); - - // Save the configuration file. - config.Save(ConfigurationSaveMode.Modified); - } - private void Port_TextChanged(object sender, EventArgs e) - { - SaveConfig(); - if (RunServer) - { - StopServer(); - StartServer(); - } - } - - private void Password_TextChanged(object sender, EventArgs e) - { - SaveConfig(); - if (RunServer) - { - StopServer(); - StartServer(); - } - } - private void GeneratePassword_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e) - { - if (Lock.Checked) - return; - Password.Text = Guid.NewGuid().ToString(); - } - private void Port_Validating(object sender, CancelEventArgs e) - { - if (!int.TryParse(Port.Text, out int value) || value < 1 || value > ushort.MaxValue) - { - e.Cancel = true; - Port.Text = PreviousPort; - } - else - { - PreviousPort = Port.Text; - - } - } - - private void CopyPortPic_Click(object sender, EventArgs e) - { - Clipboard.SetText(Port.Text); - } - - private void CopyPasswordPic_Click(object sender, EventArgs e) - { - if (string.IsNullOrEmpty(Password.Text)) - Password.Text = Guid.NewGuid().ToString(); - - Clipboard.SetText(Password.Text); - } - - private void Lock_CheckedChanged(object sender, EventArgs e) - { - Port.Enabled = !Lock.Checked; - Password.Enabled = !Lock.Checked; - GeneratePassword.Visible = !Lock.Checked; - } - - private void StartServer() - { - if (!int.TryParse(Port.Text, out int port)) - { - Port.Text = "54321"; - port = 54321; - } - - try - { - Listener = new HttpListener(); - // Create a http server and start listening for incoming connections - Listener?.Prefixes.Add($"http://localhost:{port}/"); - Listener?.Start(); - RunServer = true; - - // Handle requests - _ = Task.Run(HandleIncomingConnections); - } - catch (Exception ex) - { - MessageBox.Show(ex.Message, - "Ошибка инициализации сервера аутентификации Яндекс.Диска", MessageBoxButtons.OK, MessageBoxIcon.Error); - Application.Exit(); - } - } - private void StopServer() - { - RunServer = false; - Listener?.Abort(); - Listener?.Close(); - Listener = null; - } - - private void AuthButton_Click(object sender, EventArgs e) - { - new AuthForm("default", new BrowserAppResponse()).ShowDialog(); - - } - private void OpenDialog(string desiredLogin, BrowserAppResponse response) - { - // Переключение на поток, обрабатывающий UI. - //System.Threading.SynchronizationContext.Current?.Post( ( _ ) => - //{ - // new AuthForm( desiredLogin, response ).ShowDialog(); - //}, null ); - new AuthForm(desiredLogin, response).ShowDialog(); - - if (response.Cookies != null) - AuthenticationOkCounter++; - else - AuthenticationFailCounter++; - - Counter.Text = $"Входов успешных / не успешных : {AuthenticationOkCounter} / {AuthenticationFailCounter}"; - } - - public async Task HandleIncomingConnections() - { - string passwordToCompre = Password.Text; - - while (RunServer) - { - try - { - if (Listener == null) - break; - - // Will wait here until we hear from a connection - HttpListenerContext ctx = await Listener.GetContextAsync(); - - // Peel out the requests and response objects - HttpListenerRequest req = ctx.Request; - using HttpListenerResponse resp = ctx.Response; - - var match = UrlRegex.Match(req.Url?.AbsoluteUri ?? ""); - - var login = Uri.UnescapeDataString(match.Success ? match.Groups["login"].Value : string.Empty); - var password = Uri.UnescapeDataString(match.Success ? match.Groups["password"].Value : string.Empty); - - BrowserAppResponse response = new BrowserAppResponse(); - - if (string.IsNullOrEmpty(login)) - response.ErrorMessage = "Login is not provided"; - else - if (string.IsNullOrEmpty(password)) - response.ErrorMessage = "Password is not provided"; - else - if (password != passwordToCompre) - response.ErrorMessage = "Password is wrong"; - else - { - _showBrowserLocker.Wait(); - // Окно с браузером нужно открыть в потоке, обрабатывающем UI - if (AuthButton.InvokeRequired) - AuthButton.Invoke(AuthExecuteDelegate, login, response); - else - AuthExecuteDelegate(login, response); - _showBrowserLocker.Release(); - } - - string text = response.Serialize(); - byte[] data = Encoding.UTF8.GetBytes(text); - resp.ContentType = "application/json"; - resp.ContentEncoding = Encoding.UTF8; - resp.ContentLength64 = data.Length; - - // Write out to the response stream (asynchronously), then close it - await resp.OutputStream.WriteAsync(data); - resp.Close(); - } - catch (ObjectDisposedException) - { - // Такое исключение при Listener.Abort(), значит работа закончена - return; - } - catch (HttpListenerException) - { - if (!RunServer) - return; - } - } - } - } -} From 50dda007bcb2e1d3a78bd3e38424fe2663e70a39 Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Sat, 2 Dec 2023 13:20:00 +0300 Subject: [PATCH 45/77] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D0=B0?= =?UTF-8?q?,=20=D0=BA=D0=BE=D0=B3=D0=B4=D0=B0=20=D0=BF=D0=BE=D1=81=D0=BB?= =?UTF-8?q?=D0=B5=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20=D1=84=D0=B0=D0=B9=D0=BB=D0=B0=20=D0=BA=D0=B5=D1=88=20?= =?UTF-8?q?=D0=B3=D0=BE=D0=B2=D0=BE=D1=80=D0=B8=D0=BB,=20=D1=87=D1=82?= =?UTF-8?q?=D0=BE=20=D1=84=D0=B0=D0=B9=D0=BB=D0=B0=20=D0=BD=D0=B5=D1=82.?= =?UTF-8?q?=20=D0=A2=D0=BE=D0=BB=D1=8C=D0=BA=D0=BE=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?Mail.Ru.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../WebM1/Requests/FolderInfoRequest.cs | 97 ++++++++++++++++++- .../Base/Requests/Types/FolderInfoResult.cs | 10 ++ 2 files changed, 104 insertions(+), 3 deletions(-) diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/FolderInfoRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/FolderInfoRequest.cs index c864806a..294b6063 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/FolderInfoRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/FolderInfoRequest.cs @@ -1,4 +1,6 @@ using System; +using System.Collections.Specialized; +using System.IO; using System.Text; using YaR.Clouds.Base.Requests; using YaR.Clouds.Base.Requests.Types; @@ -12,6 +14,20 @@ internal class FolderInfoRequest : BaseRequestJson private readonly int _offset; private readonly int _limit; + /* + * Внимание! + * При выборке с limit меньшим, чем количество файлов в папке, + * Mail.Ru выдает список файлов от начала папки, игнорируя название файла в path. + * Например, в папке файлы: a, b, c, d... z. Делаем выборку /x с limit равным 1. Сервер вернет файл a вместо x. + * + * Когда выборка делается с limit равным 1 или 2 вместо int.MaxValue, + * это означает, что была одна из операций создания, удаления, переименования и т.д., + * после которой нужно подтверждение наличия файла или директории с соответствующим названием, + * вместо всего списка выбирается только одно названия для подтверждения. + * Из-за описанной выше проблемы при выборке одного названия + * при обращении к серверу запрашивается информация типа file вместо folder, + * а затем данные перекладываются в структуру, для которой давно есть обработка. + */ public FolderInfoRequest(HttpCommonSettings settings, IAuth auth, RemotePath path, int offset = 0, int limit = int.MaxValue) : base(settings, auth) @@ -24,23 +40,98 @@ public FolderInfoRequest(HttpCommonSettings settings, IAuth auth, RemotePath pat _path = "/" + ustr.Remove(0, ustr.IndexOf("/public/", StringComparison.Ordinal) + "/public/".Length); } else + { _path = path.Path; + } _offset = offset; _limit = limit; } - protected override string RelationalUri => $"/api/m1/folder?access_token={_auth.AccessToken}&offset={_offset}&limit={_limit}"; - + protected override string RelationalUri + => _limit <= 2 + ? $"/api/m1/file?access_token={_auth.AccessToken}" + : $"/api/m1/folder?access_token={_auth.AccessToken}&offset={_offset}&limit={_limit}"; protected override byte[] CreateHttpContent() { - // path sended using POST cause of unprintable Unicode charactes may exists + // path sent using POST cause of unprintable Unicode characters may exists // https://github.com/yar229/WebDavMailRuCloud/issues/54 var data = _isWebLink ? $"weblink={_path}" : $"home={Uri.EscapeDataString(_path)}"; return Encoding.UTF8.GetBytes(data); } + + protected override RequestResponse DeserializeMessage(NameValueCollection responseHeaders, Stream stream) + { + RequestResponse response = base.DeserializeMessage(responseHeaders, stream); + + if (_limit > 2) + return response; + + #region Данные об одиночном файле или папке нужно переложить структуру списка + + FolderInfoResult.FolderInfoBody body = null; + FolderInfoResult folderInfoResult = null; + if (response.Result is not null) + { + if (response.Result.Body is not null) + { + string home = response.Result.Body.Home; + string name = response.Result.Body.Name; + if (response.Result.Body.Kind == "file") + { + home = WebDavPath.Parent(response.Result.Body.Home); + name = WebDavPath.Name(home); + } + + FolderInfoResult.FolderInfoBody.FolderInfoProps item = new() + { + Weblink = response.Result.Body.Weblink, + Size = response.Result.Body.Size, + Name = response.Result.Body.Name, + Home = response.Result.Body.Home, + Kind = response.Result.Body.Kind, + Hash = response.Result.Body.Hash, + Mtime = response.Result.Body.Mtime, + }; + body = new() + { + Name = name, + Home = home, + Kind = response.Result.Body.Kind, + Hash = response.Result.Body.Hash, + Mtime = response.Result.Body.Mtime, + Size = response.Result.Body.Size, + Weblink = response.Result.Body.Weblink, + List = [item], + Count = new FolderInfoResult.FolderInfoBody.FolderInfoCount() + { + Files = response.Result.Body.Kind == "file" ? 1 : 0, + Folders = response.Result.Body.Kind == "folder" ? 1 : 0, + }, + }; + } + folderInfoResult = new FolderInfoResult() + { + Time = response.Result.Time, + Email = response.Result.Email, + Status = response.Result.Status, + Body = body, + }; + } + RequestResponse dataRes = new() + { + Ok = response.Ok, + ErrorCode = response.ErrorCode, + Description = response.Description, + Result = folderInfoResult, + }; + + return dataRes; + + #endregion + } } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/FolderInfoResult.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/FolderInfoResult.cs index 80bb2c71..37c97a2e 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/FolderInfoResult.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/FolderInfoResult.cs @@ -43,6 +43,16 @@ public class FolderInfoBody [JsonProperty("weblink")] public string Weblink { get; set; } + #region Преимущественно для выборки отдельного файла или папки вместо списка + + [JsonProperty("mtime")] + public ulong Mtime; + + [JsonProperty("hash")] + public string Hash { get; set; } + + #endregion + public class FolderInfoProps { [JsonProperty("mtime")] From ebf8d7e1cdefc74c92091a19cff9cb14c511cdde Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Sat, 2 Dec 2023 13:30:38 +0300 Subject: [PATCH 46/77] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D0=BD=D0=BE?= =?UTF-8?q?=D1=81=20BaseDomain=20=D0=B8=D0=B7=20ConstSettings=20=D0=B2=20?= =?UTF-8?q?=D0=BA=D0=BE=D0=BD=D1=81=D1=82=D1=80=D1=83=D0=BA=D1=82=D0=BE?= =?UTF-8?q?=D1=80=20=D0=BF=D1=80=D0=BE=D1=82=D0=BE=D0=BA=D0=BE=D0=BB=D0=B0?= =?UTF-8?q?,=20=D1=83=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=82?= =?UTF-8?q?=D0=B0=D0=BA=D0=B8=D0=BC=20=D0=BE=D0=B1=D1=80=D0=B0=D0=B7=D0=BE?= =?UTF-8?q?=D0=BC=20mail.ru=20=D0=B8=D0=B7=20=D0=BA=D0=BE=D0=B4=D0=B0,=20?= =?UTF-8?q?=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=D1=8E=D1=89=D0=B5=D0=B3?= =?UTF-8?q?=D0=BE=20=D1=81=20yandex.ru.=20=D0=9F=D0=BB=D1=8E=D1=81=20?= =?UTF-8?q?=D0=BD=D0=B5=D0=B1=D0=BE=D0=BB=D1=8C=D1=88=D0=B0=D1=8F=20=D0=BF?= =?UTF-8?q?=D0=BE=D0=BB=D0=B8=D1=80=D0=BE=D0=B2=D0=BA=D0=B0=20=D0=BA=D0=BE?= =?UTF-8?q?=D0=B4=D0=B0,=20=D0=B2=20=D0=BE=D1=81=D0=BD=D0=BE=D0=B2=D0=BD?= =?UTF-8?q?=D0=BE=D0=BC=20=D0=B4=D0=BB=D1=8F=20Mail.Ru.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Base/Repos/MailRuCloud/ConstSettings.cs | 10 -------- .../MailRuCloud/Mobile/MobileRequestRepo.cs | 3 ++- .../Mobile/Requests/AccountInfoRequest.cs | 2 +- .../Mobile/Requests/MobAddFileRequest.cs | 2 +- .../Requests/WeblinkGetServerRequest.cs | 9 +------- .../MailRuCloud/WebBin/WebBinRequestRepo.cs | 23 ++++++++++--------- .../WebM1/Requests/AccountInfoRequest.cs | 2 +- .../WebM1/Requests/CloneItemRequest.cs | 9 +------- .../WebM1/Requests/ShardInfoRequest.cs | 2 +- .../MailRuCloud/WebM1/WebM1RequestRepo.cs | 7 +++--- .../WebV2/Requests/AccountInfoRequest.cs | 2 +- .../WebV2/Requests/CloneItemRequest.cs | 9 +------- .../WebV2/Requests/EnsureSdcCookieRequest.cs | 9 +------- .../WebV2/Requests/ShardInfoRequest.cs | 2 +- .../WebV2/Requests/UploadRequest.cs | 13 ++++++----- .../MailRuCloud/WebV2/WebV2RequestRepo.cs | 3 ++- .../Base/Repos/YandexDisk/FileHashYad.cs | 8 +++---- .../Requests/YadAuthPreAuthRequestResult.cs | 2 +- .../YadWeb/Requests/YadUploadRequest.cs | 10 ++++---- .../YandexDisk/YadWeb/YadWebRequestRepo.cs | 1 + .../Base/Requests/BaseRequest.cs | 11 +++++---- .../Base/Requests/HttpCommonSettings.cs | 1 + .../NWebDav.Server/Handlers/LockHandler.cs | 2 +- NWebDav/NWebDav.Server/WebDavDispatcher.cs | 10 ++++---- 24 files changed, 61 insertions(+), 91 deletions(-) delete mode 100644 MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/ConstSettings.cs diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/ConstSettings.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/ConstSettings.cs deleted file mode 100644 index 766523c3..00000000 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/ConstSettings.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace YaR.Clouds.Base.Repos.MailRuCloud -{ - public static class ConstSettings - { - public const string CloudDomain = "https://cloud.mail.ru"; - //public const string PublishFileLink = CloudDomain + "/public/"; - - public const string DefaultRequestType = "application/x-www-form-urlencoded"; - } -} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/MobileRequestRepo.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/MobileRequestRepo.cs index d43afd41..8d7b06fa 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/MobileRequestRepo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/MobileRequestRepo.cs @@ -25,7 +25,8 @@ class MobileRequestRepo : MailRuBaseRepo, IRequestRepo public override HttpCommonSettings HttpSettings { get; } = new() { ClientId = "cloud-win", - UserAgent = "CloudDiskOWindows 17.12.0009 beta WzBbt1Ygbm" + UserAgent = "CloudDiskOWindows 17.12.0009 beta WzBbt1Ygbm", + BaseDomain = "https://cloud.mail.ru" }; public MobileRequestRepo(CloudSettings settings, IWebProxy proxy, IAuth auth, int listDepth) diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/AccountInfoRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/AccountInfoRequest.cs index 10eedd24..d04ee195 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/AccountInfoRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/AccountInfoRequest.cs @@ -9,6 +9,6 @@ public AccountInfoRequest(HttpCommonSettings settings, IAuth auth) : base(settin { } - protected override string RelationalUri => $"{ConstSettings.CloudDomain}/api/m1/user?access_token={_auth.AccessToken}"; + protected override string RelationalUri => $"{_settings.BaseDomain}/api/m1/user?access_token={_auth.AccessToken}"; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/MobAddFileRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/MobAddFileRequest.cs index c5cb68ac..1ecafc2f 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/MobAddFileRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/MobAddFileRequest.cs @@ -59,7 +59,7 @@ protected override byte[] CreateHttpContent() return body; } - private static readonly OpResult[] SuccessCodes = { OpResult.Ok, OpResult.NotModified, OpResult.Dunno04, OpResult.Dunno09}; + private static readonly OpResult[] SuccessCodes = { OpResult.Ok, OpResult.NotModified, OpResult.Dunno04, OpResult.Dunno09 }; protected override RequestResponse DeserializeMessage(NameValueCollection responseHeaders, ResponseBodyStream data) { diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/WeblinkGetServerRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/WeblinkGetServerRequest.cs index 0560339e..af2335c5 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/WeblinkGetServerRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/WeblinkGetServerRequest.cs @@ -19,13 +19,6 @@ public WeblinkGetServerRequest(HttpCommonSettings settings) { } - protected override string RelationalUri - { - get - { - const string uri = $"{ConstSettings.CloudDomain}/api/v2/dispatcher?api=2&email=anonym&x-email=anonym"; - return uri; - } - } + protected override string RelationalUri => $"{_settings.BaseDomain}/api/v2/dispatcher?api=2&email=anonym&x-email=anonym"; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebBin/WebBinRequestRepo.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebBin/WebBinRequestRepo.cs index 85ea333c..d981ff80 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebBin/WebBinRequestRepo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebBin/WebBinRequestRepo.cs @@ -21,6 +21,8 @@ using CreateFolderRequest = YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests.CreateFolderRequest; using MoveRequest = YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests.MoveRequest; using static YaR.Clouds.Cloud; +using System.Timers; +using System.Xml.Linq; namespace YaR.Clouds.Base.Repos.MailRuCloud.WebBin { @@ -44,7 +46,8 @@ class WebBinRequestRepo : MailRuBaseRepo, IRequestRepo public sealed override HttpCommonSettings HttpSettings { get; } = new() { //ClientId = "cloud-android" - ClientId = "cloud-win" + ClientId = "cloud-win", + BaseDomain = "https://cloud.mail.ru" }; public WebBinRequestRepo(CloudSettings settings, IBasicCredentials credentials, AuthCodeRequiredDelegate onAuthCodeRequired) @@ -65,11 +68,9 @@ public WebBinRequestRepo(CloudSettings settings, IBasicCredentials credentials, // required for Windows 7 breaking connection ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13 | SecurityProtocolType.Tls12; - } - public Stream GetDownloadStream(File file, long? start = null, long? end = null) { var istream = GetDownloadStreamInternal(file, start, end); @@ -173,7 +174,7 @@ public async Task Copy(string sourceFullPath, string destinationPath public async Task Move(string sourceFullPath, string destinationPath, ConflictResolver? conflictResolver = null) { - //var req = await new MoveRequest(HttpSettings, Authent, sourceFullPath, destinationPath).MakeRequestAsync(_connectionLimiter); + //var req = await new MoveRequest(HttpSettings, Auth, sourceFullPath, destinationPath).MakeRequestAsync(_connectionLimiter); //var res = req.ToCopyResult(); //return res; @@ -230,11 +231,11 @@ public async Task FolderInfo(RemotePath path, int offset = 0, int limit if (!path.IsLink && depth > 1) return await FolderInfo(path.Path, depth); - FolderInfoResult datares; + FolderInfoResult dataRes; try { - datares = await new FolderInfoRequest(HttpSettings, Auth, path, offset, limit) - .MakeRequestAsync(_connectionLimiter); + dataRes = await new FolderInfoRequest(HttpSettings, Auth, path, offset, limit) + .MakeRequestAsync(_connectionLimiter); } catch (WebException e) when (e.Response is HttpWebResponse { StatusCode: HttpStatusCode.NotFound }) { @@ -245,8 +246,8 @@ public async Task FolderInfo(RemotePath path, int offset = 0, int limit //TODO: subject to refact, bad-bad-bad if (!path.IsLink || path.Link.ItemType == Cloud.ItemType.Unknown) - itemType = datares.Body.Home == path.Path || - WebDavPath.PathEquals("/" + datares.Body.Weblink, path.Path) + itemType = dataRes.Body.Home == path.Path || + WebDavPath.PathEquals("/" + dataRes.Body.Weblink, path.Path) ? Cloud.ItemType.Folder : Cloud.ItemType.File; else @@ -254,13 +255,13 @@ public async Task FolderInfo(RemotePath path, int offset = 0, int limit var entry = itemType == Cloud.ItemType.File - ? (IEntry)datares.ToFile( + ? (IEntry)dataRes.ToFile( PublicBaseUrlDefault, home: WebDavPath.Parent(path.Path ?? string.Empty), ulink: path.Link, fileName: path.Link == null ? WebDavPath.Name(path.Path) : path.Link.OriginalName, nameReplacement: path.Link?.IsLinkedToFileSystem ?? true ? WebDavPath.Name(path.Path) : path.Link.Name) - : (IEntry)datares.ToFolder(PublicBaseUrlDefault, path.Path, path.Link); + : (IEntry)dataRes.ToFolder(PublicBaseUrlDefault, path.Path, path.Link); if (limit == int.MaxValue && entry is Folder fld) fld.IsChildrenLoaded = limit == int.MaxValue; diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/AccountInfoRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/AccountInfoRequest.cs index baa104be..80998d20 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/AccountInfoRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/AccountInfoRequest.cs @@ -9,6 +9,6 @@ public AccountInfoRequest(HttpCommonSettings settings, IAuth auth) : base(settin { } - protected override string RelationalUri => $"{ConstSettings.CloudDomain}/api/m1/user?access_token={_auth.AccessToken}"; + protected override string RelationalUri => $"{_settings.BaseDomain}/api/m1/user?access_token={_auth.AccessToken}"; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/CloneItemRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/CloneItemRequest.cs index b4dc853d..c028bb5a 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/CloneItemRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/CloneItemRequest.cs @@ -16,13 +16,6 @@ public CloneItemRequest(HttpCommonSettings settings, IAuth auth, string fromUrl, _toPath = toPath; } - protected override string RelationalUri - { - get - { - var uri = $"{ConstSettings.CloudDomain}/api/m1/clone?conflict=rename&folder={Uri.EscapeDataString(_toPath)}&weblink={Uri.EscapeDataString(_fromUrl)}&access_token={_auth.AccessToken}"; - return uri; - } - } + protected override string RelationalUri => $"{_settings.BaseDomain}/api/m1/clone?conflict=rename&folder={Uri.EscapeDataString(_toPath)}&weblink={Uri.EscapeDataString(_fromUrl)}&access_token={_auth.AccessToken}"; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/ShardInfoRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/ShardInfoRequest.cs index e2fbc5dc..ddd78256 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/ShardInfoRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/ShardInfoRequest.cs @@ -13,7 +13,7 @@ protected override string RelationalUri { get { - var uri = $"{ConstSettings.CloudDomain}/api/m1/dispatcher?client_id={_settings.ClientId}"; + var uri = $"{_settings.BaseDomain}/api/m1/dispatcher?client_id={_settings.ClientId}"; if (!string.IsNullOrEmpty(_auth.AccessToken)) uri += $"&access_token={_auth.AccessToken}"; return uri; diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/WebM1RequestRepo.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/WebM1RequestRepo.cs index aa538614..a36e6972 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/WebM1RequestRepo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/WebM1RequestRepo.cs @@ -36,7 +36,8 @@ abstract class WebM1RequestRepo : MailRuBaseRepo, IRequestRepo public sealed override HttpCommonSettings HttpSettings { get; } = new() { ClientId = "cloud-win", - UserAgent = "CloudDiskOWindows 17.12.0009 beta WzBbt1Ygbm" + UserAgent = "CloudDiskOWindows 17.12.0009 beta WzBbt1Ygbm", + BaseDomain = "https://cloud.mail.ru" }; protected WebM1RequestRepo(CloudSettings settings, IWebProxy proxy, @@ -118,8 +119,8 @@ CustomDisposable ResponseGenerator(long instart, long inend, Fi { request.Headers.Add("Accept-Ranges", "bytes"); request.ContentType = MediaTypeNames.Application.Octet; - request.Referer = $"{ConstSettings.CloudDomain}/home/{Uri.EscapeDataString(file.Path)}"; - request.Headers.Add("Origin", ConstSettings.CloudDomain); + request.Referer = $"{HttpSettings.BaseDomain}/home/{Uri.EscapeDataString(file.Path)}"; + request.Headers.Add("Origin", HttpSettings.BaseDomain); } request.Timeout = 15 * 1000; diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/AccountInfoRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/AccountInfoRequest.cs index c4b10403..3a8058dc 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/AccountInfoRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/AccountInfoRequest.cs @@ -9,6 +9,6 @@ public AccountInfoRequest(HttpCommonSettings settings, IAuth auth) : base(settin { } - protected override string RelationalUri => $"{ConstSettings.CloudDomain}/api/v2/user?token={_auth.AccessToken}"; + protected override string RelationalUri => $"{_settings.BaseDomain}/api/v2/user?token={_auth.AccessToken}"; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/CloneItemRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/CloneItemRequest.cs index 7347288d..87b2d8ad 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/CloneItemRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/CloneItemRequest.cs @@ -16,13 +16,6 @@ public CloneItemRequest(HttpCommonSettings settings, IAuth auth, string fromUrl, _toPath = toPath; } - protected override string RelationalUri - { - get - { - var uri = $"{ConstSettings.CloudDomain}/api/v2/clone?conflict=rename&folder={Uri.EscapeDataString(_toPath)}&weblink={Uri.EscapeDataString(_fromUrl)}&token={_auth.AccessToken}"; - return uri; - } - } + protected override string RelationalUri => $"{_settings.BaseDomain}/api/v2/clone?conflict=rename&folder={Uri.EscapeDataString(_toPath)}&weblink={Uri.EscapeDataString(_fromUrl)}&token={_auth.AccessToken}"; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/EnsureSdcCookieRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/EnsureSdcCookieRequest.cs index 7b7f7115..8741151d 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/EnsureSdcCookieRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/EnsureSdcCookieRequest.cs @@ -17,13 +17,6 @@ protected override HttpWebRequest CreateRequest(string baseDomain = null) return request; } - protected override string RelationalUri - { - get - { - const string uri = $"/sdc?from={ConstSettings.CloudDomain}/home"; - return uri; - } - } + protected override string RelationalUri => $"/sdc?from={_settings.BaseDomain}/home"; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/ShardInfoRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/ShardInfoRequest.cs index 43d490ba..b7e54724 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/ShardInfoRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/ShardInfoRequest.cs @@ -14,7 +14,7 @@ protected override string RelationalUri { get { - var uri = $"{ConstSettings.CloudDomain}/api/v2/dispatcher?client_id={_settings.ClientId}"; + var uri = $"{_settings.BaseDomain}/api/v2/dispatcher?client_id={_settings.ClientId}"; if (!_auth.IsAnonymous) uri += $"&access_token={_auth.AccessToken}"; else diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/UploadRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/UploadRequest.cs index de0c2dfb..eecaa98d 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/UploadRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/UploadRequest.cs @@ -1,5 +1,6 @@ using System; using System.Net; +using System.Runtime; using YaR.Clouds.Base.Requests; namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2.Requests @@ -8,28 +9,28 @@ class UploadRequest { public UploadRequest(string shardUrl, File file, IAuth auth, HttpCommonSettings settings) { - Request = CreateRequest(shardUrl, auth, settings.Proxy, file, settings.UserAgent); + Request = CreateRequest(shardUrl, auth, file, settings); } public HttpWebRequest Request { get; } - private static HttpWebRequest CreateRequest(string shardUrl, IAuth auth, IWebProxy proxy, File file, string userAgent) + private static HttpWebRequest CreateRequest(string shardUrl, IAuth auth, File file, HttpCommonSettings settings) { var url = new Uri($"{shardUrl}?cloud_domain=2&{auth.Login}"); #pragma warning disable SYSLIB0014 // Type or member is obsolete var request = (HttpWebRequest)WebRequest.Create(url.OriginalString); #pragma warning restore SYSLIB0014 // Type or member is obsolete - request.Proxy = proxy; + request.Proxy = settings.Proxy; request.CookieContainer = auth.Cookies; request.Method = "PUT"; request.ContentLength = file.OriginalSize; // + boundary.Start.LongLength + boundary.End.LongLength; - request.Referer = $"{ConstSettings.CloudDomain}/home/{Uri.EscapeDataString(file.Path)}"; - request.Headers.Add("Origin", ConstSettings.CloudDomain); + request.Referer = $"{settings.BaseDomain}/home/{Uri.EscapeDataString(file.Path)}"; + request.Headers.Add("Origin", settings.BaseDomain); request.Host = url.Host; //request.ContentType = $"multipart/form-data; boundary=----{boundary.Guid}"; request.Accept = "*/*"; - request.UserAgent = userAgent; + request.UserAgent = settings.UserAgent; request.AllowWriteStreamBuffering = false; return request; } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/WebV2RequestRepo.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/WebV2RequestRepo.cs index 596382e5..db12934c 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/WebV2RequestRepo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/WebV2RequestRepo.cs @@ -22,7 +22,8 @@ class WebV2RequestRepo: MailRuBaseRepo, IRequestRepo public sealed override HttpCommonSettings HttpSettings { get; } = new() { - ClientId = string.Empty + ClientId = string.Empty, + BaseDomain = "https://cloud.mail.ru" }; public WebV2RequestRepo(CloudSettings settings, IBasicCredentials credentials, AuthCodeRequiredDelegate onAuthCodeRequired) diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/FileHashYad.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/FileHashYad.cs index 4e9301f4..3e2011d7 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/FileHashYad.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/FileHashYad.cs @@ -11,20 +11,20 @@ public FileHashYad(byte[] hashSha256, byte[] hashMd5) : this() HashMd5 = new Hash(FileHashType.YadMd5, hashMd5.ToHexString()); } - public Hash Get(FileHashType htype) + public readonly Hash Get(FileHashType htype) { if (htype != FileHashType.YadSha256 && htype != FileHashType.YadMd5 ) - throw new ArgumentException($"Mail.ru Cloud supportd only {FileHashType.YadSha256} and {FileHashType.YadMd5} hash type"); + throw new ArgumentException($"Mail.ru Cloud supported only {FileHashType.YadSha256} and {FileHashType.YadMd5} hash type"); return Hash; } - public Hash Hash => HashSha256; + public readonly Hash Hash => HashSha256; public Hash HashSha256 { get; private set; } public Hash HashMd5 { get; private set; } - public override string ToString() + public override readonly string ToString() { return $"{FileHashType.YadSha256}={HashSha256.Value}, {FileHashType.YadMd5}={HashMd5.Value}"; } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadAuthPreAuthRequestResult.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadAuthPreAuthRequestResult.cs index 5d580bb9..2599ffb2 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadAuthPreAuthRequestResult.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadAuthPreAuthRequestResult.cs @@ -12,7 +12,7 @@ public YadPreAuthRequest(HttpCommonSettings settings, IAuth auth) { } - protected override HttpWebRequest CreateRequest(string baseDomain = null) + protected override HttpWebRequest CreateRequest(string baseDomain) { var request = base.CreateRequest("https://passport.yandex.ru"); return request; diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadUploadRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadUploadRequest.cs index c3496895..d977c661 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadUploadRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadUploadRequest.cs @@ -8,24 +8,24 @@ class YadUploadRequest { public YadUploadRequest(HttpCommonSettings settings, YadWebAuth authenticator, string url, long size) { - Request = CreateRequest(url, authenticator, settings.Proxy, size, settings.UserAgent); + Request = CreateRequest(url, authenticator, size, settings); } public HttpWebRequest Request { get; } - private HttpWebRequest CreateRequest(string url, YadWebAuth authenticator, IWebProxy proxy, long size, string userAgent) + private HttpWebRequest CreateRequest(string url, YadWebAuth authenticator, long size, HttpCommonSettings settings) { #pragma warning disable SYSLIB0014 // Type or member is obsolete var request = (HttpWebRequest)WebRequest.Create(url); #pragma warning restore SYSLIB0014 // Type or member is obsolete - request.Proxy = proxy; + request.Proxy = settings.Proxy; request.CookieContainer = authenticator.Cookies; request.Method = "PUT"; request.ContentLength = size; request.Referer = "https://disk.yandex.ru/client/disk"; - request.Headers.Add("Origin", ConstSettings.CloudDomain); + request.Headers.Add("Origin", settings.BaseDomain); request.Accept = "*/*"; - request.UserAgent = userAgent; + request.UserAgent = settings.UserAgent; request.AllowWriteStreamBuffering = false; return request; } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebRequestRepo.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebRequestRepo.cs index f6bff3d8..d3471834 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebRequestRepo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebRequestRepo.cs @@ -57,6 +57,7 @@ public YadWebRequestRepo(CloudSettings settings, IWebProxy proxy, Credentials cr UserAgent = settings.UserAgent, CloudSettings = settings, Proxy = proxy, + BaseDomain = "https://disk.yandex.ru" }; _credentials = credentials; diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs index d37324d4..4bf886c7 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs @@ -26,9 +26,9 @@ protected BaseRequest(HttpCommonSettings settings, IAuth auth) protected abstract string RelationalUri { get; } - protected virtual HttpWebRequest CreateRequest(string baseDomain = null) + protected virtual HttpWebRequest CreateRequest(string baseDomain) { - string domain = string.IsNullOrEmpty(baseDomain) ? ConstSettings.CloudDomain : baseDomain; + string domain = baseDomain; var uriz = new Uri(new Uri(domain), RelationalUri); // suppressing escaping is obsolete and breaks, for example, Chinese names @@ -40,10 +40,13 @@ protected virtual HttpWebRequest CreateRequest(string baseDomain = null) var request = WebRequest.CreateHttp(uriz); #pragma warning restore SYSLIB0014 // Type or member is obsolete request.Host = uriz.Host; + request.Headers.Add("Origin", $"{uriz.Scheme}://{uriz.Host}"); + request.Referer = $"{uriz.Scheme}://{uriz.Host}"; + request.Headers.Add("sec-ch-ua", _settings.CloudSettings.SecChUa); request.Proxy = _settings.Proxy; request.CookieContainer = _auth?.Cookies; request.Method = "GET"; - request.ContentType = ConstSettings.DefaultRequestType; + request.ContentType = "application/x-www-form-urlencoded; charset=UTF-8"; request.Accept = "application/json"; request.UserAgent = _settings.UserAgent; request.ContinueTimeout = _settings.CloudSettings.Wait100ContinueTimeoutSec * 1000; @@ -118,7 +121,7 @@ public virtual async Task MakeRequestAsync(SemaphoreSlim serverMaxConnectionL try { - httpRequest = CreateRequest(); + httpRequest = CreateRequest(_settings.BaseDomain); var requestContent = CreateHttpContent(); if (requestContent != null) diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/HttpCommonSettings.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/HttpCommonSettings.cs index d0a7e66a..88a4ff63 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/HttpCommonSettings.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/HttpCommonSettings.cs @@ -8,5 +8,6 @@ public class HttpCommonSettings public string ClientId { get; set; } public string UserAgent { get; set; } public CloudSettings CloudSettings { get; set; } + public string BaseDomain { get; set; } } } diff --git a/NWebDav/NWebDav.Server/Handlers/LockHandler.cs b/NWebDav/NWebDav.Server/Handlers/LockHandler.cs index 00a636ca..6b2f28df 100644 --- a/NWebDav/NWebDav.Server/Handlers/LockHandler.cs +++ b/NWebDav/NWebDav.Server/Handlers/LockHandler.cs @@ -40,7 +40,7 @@ public async Task HandleRequestAsync(IHttpContext httpContext, IStore stor // Obtain request and response var request = httpContext.Request; var response = httpContext.Response; - + // Determine the depth and requested timeout(s) var depth = request.GetDepth(); var timeouts = request.GetTimeouts(); diff --git a/NWebDav/NWebDav.Server/WebDavDispatcher.cs b/NWebDav/NWebDav.Server/WebDavDispatcher.cs index 56bd83ea..a6436994 100644 --- a/NWebDav/NWebDav.Server/WebDavDispatcher.cs +++ b/NWebDav/NWebDav.Server/WebDavDispatcher.cs @@ -78,14 +78,12 @@ public async Task DispatchRequestAsync(IHttpContext httpContext) throw new ArgumentNullException(nameof(httpContext)); // Make sure the HTTP context has a request - var request = httpContext.Request; - if (request == null) - throw new ArgumentException("The HTTP context doesn't have a request.", nameof(httpContext)); + var request = httpContext.Request + ?? throw new ArgumentException("The HTTP context doesn't have a request.", nameof(httpContext)); // Make sure the HTTP context has a response - var response = httpContext.Response; - if (response == null) - throw new ArgumentException("The HTTP context doesn't have a response.", nameof(httpContext)); + var response = httpContext.Response + ?? throw new ArgumentException("The HTTP context doesn't have a response.", nameof(httpContext)); // Determine the request log-string var logRequest = $"{request.HttpMethod}:{Uri.UnescapeDataString(request.Url.AbsoluteUri)}:{request.RemoteEndPoint}"; From 8a3577e3c2d77270be893a3b300041c4a7def6f9 Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Sat, 2 Dec 2023 13:31:45 +0300 Subject: [PATCH 47/77] =?UTF-8?q?=D0=9D=D0=B5=D0=BC=D0=BD=D0=BE=D0=B3?= =?UTF-8?q?=D0=BE=20=D1=83=D0=B4=D0=BE=D0=B1=D1=81=D1=82=D0=B2=D0=B0=20?= =?UTF-8?q?=D0=B4=D0=BB=D1=8F=20=D0=BF=D1=80=D0=B8=20=D0=BE=D1=82=D0=BB?= =?UTF-8?q?=D0=BE=D0=B2=D0=B5=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BE=D0=BA.=20?= =?UTF-8?q?=D0=9F=D1=80=D0=B8=20=D0=BE=D1=82=D0=BB=D0=B0=D0=B4=D0=BA=D0=B5?= =?UTF-8?q?=20=D0=BC=D0=BE=D0=B6=D0=BD=D0=BE=20=D0=BF=D0=BE=D1=81=D0=BC?= =?UTF-8?q?=D0=BE=D1=82=D1=80=D0=B5=D1=82=D1=8C=20=D1=87=D1=82=D0=BE=20?= =?UTF-8?q?=D0=B2=D0=B5=D1=80=D0=BD=D1=83=D0=BB=20=D1=81=D0=B5=D1=80=D0=B2?= =?UTF-8?q?=D0=B5=D1=80.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Base/Requests/BaseRequestJson.cs | 29 ++++++++++--------- 1 file changed, 15 insertions(+), 14 deletions(-) diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequestJson.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequestJson.cs index 5e3379d9..53a9e23f 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequestJson.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequestJson.cs @@ -18,6 +18,20 @@ protected override Stream Transport(Stream stream) protected override RequestResponse DeserializeMessage(NameValueCollection responseHeaders, Stream stream) { +#if DEBUG + using (var sr = new StreamReader(stream)) + { + string text = sr.ReadToEnd(); + + var msg = new RequestResponse + { + Ok = true, + Result = JsonConvert.DeserializeObject(text) + }; + return msg; + + } +#else var serializer = new JsonSerializer(); using var sr = new StreamReader(stream); using var jsonTextReader = new JsonTextReader(sr); @@ -28,20 +42,7 @@ protected override RequestResponse DeserializeMessage(NameValueCollection res Result = serializer.Deserialize(jsonTextReader) }; return msg; - - //using (var sr = new StreamReader(stream)) - //{ - // string text = sr.ReadToEnd(); - - // var msg = new RequestResponse - // { - // Ok = true, - // Result = JsonConvert.DeserializeObject(text) - // }; - // return msg; - - //} - +#endif } } } From 1fb86833e1c6128d8fba95e43dd6517dcca9dc96 Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Sat, 2 Dec 2023 13:34:13 +0300 Subject: [PATCH 48/77] =?UTF-8?q?=D0=9E=D0=B1=D1=80=D0=B0=D0=B1=D0=BE?= =?UTF-8?q?=D1=82=D0=BA=D0=B0=20=D0=BD=D0=B5=D0=B4=D0=BE=D0=BF=D1=83=D1=81?= =?UTF-8?q?=D1=82=D0=B8=D0=BC=D1=8B=D1=85=20=D1=81=D0=B8=D0=BC=D0=B2=D0=BE?= =?UTF-8?q?=D0=BB=D0=BE=D0=B2=20=D0=B4=D0=BB=D1=8F=20=D1=84=D0=B0=D0=B9?= =?UTF-8?q?=D0=BB=D0=B0,=20=D1=87=D1=8C=D0=B5=20=D0=B8=D0=BC=D1=8F=20?= =?UTF-8?q?=D0=BE=D0=B1=D1=80=D0=B0=D0=B7=D1=83=D0=B5=D1=82=D1=81=D1=8F=20?= =?UTF-8?q?=D0=B8=D0=B7=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BD=D0=B0=20=D0=BF?= =?UTF-8?q?=D1=80=D0=B8=20=D1=81=D0=BE=D1=85=D1=80=D0=B0=D0=BD=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B8=20=D0=BA=D0=B5=D1=88=D0=B0=20=D0=BA=D1=83=D0=BA?= =?UTF-8?q?=D0=B8=20=D0=BF=D1=80=D0=B8=20=D0=B0=D1=83=D1=82=D0=B5=D0=BD?= =?UTF-8?q?=D1=82=D0=B8=D1=84=D0=B8=D0=BA=D0=B0=D1=86=D0=B8=D0=B8=20=D1=87?= =?UTF-8?q?=D0=B5=D1=80=D0=B5=D0=B7=20=D0=B1=D1=80=D0=B0=D1=83=D0=B7=D0=B5?= =?UTF-8?q?=D1=80.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MailRuCloud/MailRuCloudApi/Base/Credentials.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/MailRuCloud/MailRuCloudApi/Base/Credentials.cs b/MailRuCloud/MailRuCloudApi/Base/Credentials.cs index 13abe34b..e1f70343 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Credentials.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Credentials.cs @@ -252,7 +252,7 @@ private async Task GetBrowserCookiesAsync() try { string fileName = string.IsNullOrWhiteSpace(Login) ? "anonymous" : Login; - path = Path.Combine(_settings.BrowserAuthenticatorCacheDir, fileName); + path = GetPath(_settings.BrowserAuthenticatorCacheDir, fileName); if (System.IO.File.Exists(path)) { @@ -323,7 +323,7 @@ private async Task GetBrowserCookiesAsync() if (!string.IsNullOrEmpty(_settings.BrowserAuthenticatorCacheDir)) { string fileName = string.IsNullOrWhiteSpace(Login) ? "anonymous" : Login; - string path = Path.Combine(_settings.BrowserAuthenticatorCacheDir, fileName); + string path = GetPath(_settings.BrowserAuthenticatorCacheDir, fileName); try { string content = JsonConvert.SerializeObject(response); @@ -389,6 +389,16 @@ private async Task GetBrowserCookiesAsync() } } + private static string GetPath(string folder, string fileName) + { + if (string.IsNullOrEmpty(fileName)) + fileName = "anonymous"; + string[] parts = fileName.Split(Path.GetInvalidFileNameChars(), StringSplitOptions.RemoveEmptyEntries); + fileName = string.Join("_", parts); + string path = Path.Combine(folder, fileName); + return path; + } + /// /// Если аутентификация была через браузер, /// стирает файл с кешем куки и запрашивает повторную аутентификацию через браузер. @@ -401,7 +411,7 @@ public bool Refresh() if (!string.IsNullOrEmpty(_settings.BrowserAuthenticatorCacheDir)) { string fileName = string.IsNullOrWhiteSpace(Login) ? "anonymous" : Login; - string path = Path.Combine(_settings.BrowserAuthenticatorCacheDir, fileName); + string path = GetPath(_settings.BrowserAuthenticatorCacheDir, fileName); try { @@ -505,7 +515,7 @@ private async Task MakeLogin() // Если аутентификация прошла успешно, сохраняем результат в кеш в файл if (!string.IsNullOrEmpty(_settings.BrowserAuthenticatorCacheDir)) { - string path = Path.Combine(_settings.BrowserAuthenticatorCacheDir, Login); + string path = GetPath(_settings.BrowserAuthenticatorCacheDir, Login); try { From 76c26721a04555ba58d16fdf1404db457e2f9321 Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Sat, 2 Dec 2023 13:35:53 +0300 Subject: [PATCH 49/77] =?UTF-8?q?=D0=9D=D0=B5=D0=B1=D0=BE=D0=BB=D1=8C?= =?UTF-8?q?=D1=88=D0=B0=D1=8F=20=D0=BF=D0=BE=D0=BB=D0=B8=D1=80=D0=BE=D0=B2?= =?UTF-8?q?=D0=BA=D0=B0=20=D0=BA=D0=BE=D0=B4=D0=B0=20=D0=B8=20=D1=83=D1=82?= =?UTF-8?q?=D0=BE=D1=87=D0=BD=D0=B5=D0=BD=D0=B8=D1=8F=20=D0=B2=20=D1=81?= =?UTF-8?q?=D0=BB=D1=83=D1=87=D0=B0=D0=B5,=20=D0=BA=D0=BE=D0=B3=D0=B4?= =?UTF-8?q?=D0=B0=20=D0=BA=D0=B5=D1=88=20=D0=B0=D1=83=D1=82=D0=B5=D0=BD?= =?UTF-8?q?=D1=82=D0=B8=D1=84=D0=B8=D0=BA=D0=B0=D1=86=D0=B8=D0=B8=20=D1=87?= =?UTF-8?q?=D0=B5=D1=80=D0=B5=D0=B7=20=D0=B1=D1=80=D0=B0=D1=83=D0=B7=D0=B5?= =?UTF-8?q?=D1=80=20=D1=83=D1=81=D1=82=D0=B0=D1=80=D0=B5=D0=BB.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MailRuCloud/MailRuCloudApi/Cloud.cs | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/MailRuCloud/MailRuCloudApi/Cloud.cs b/MailRuCloud/MailRuCloudApi/Cloud.cs index 4a61278f..2a86644f 100644 --- a/MailRuCloud/MailRuCloudApi/Cloud.cs +++ b/MailRuCloud/MailRuCloudApi/Cloud.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Diagnostics; using System.IO; using System.Linq; using System.Net; @@ -71,13 +72,18 @@ public Cloud(CloudSettings settings, Credentials credentials) AccountInfo = RequestRepo.AccountInfo().Result ?? throw new AuthenticationException("The cloud server rejected the credentials provided"); } - catch (Exception e) when (e.OfType().Any()) + catch (Exception e) when (Credentials.AuthenticationUsingBrowser && + e.OfType().Any()) { - Logger.Warn("Refresh credentials"); + Logger.Warn("Refreshing credentials..."); try { - Credentials.Refresh(); + if (!Credentials.Refresh()) + { + Logger.Warn("Credentials refreshing is failed"); + throw; + } } catch (Exception e2) when (e2.OfType().Any()) { @@ -446,7 +452,7 @@ public string Find(string nameWithoutPathToFind, params string[] folderPaths) if (folderPaths is null || folderPaths.Length == 0 || string.IsNullOrEmpty(nameWithoutPathToFind)) return null; - List paths = new List(); + List paths = []; // Сначала смотрим в кеше, без обращений к серверу foreach (var folderPath in folderPaths) { @@ -1211,8 +1217,7 @@ public async Task GetFileUploadStream(string fullFilePath, long size, Ac ServerFileProcessed = serverFileProcessed }; - var task = await Task.FromResult(f.Create(file, OnFileUploaded, discardEncryption)) - .ConfigureAwait(false); + var task = await Task.FromResult(f.Create(file, OnFileUploaded, discardEncryption)).ConfigureAwait(false); var stream = await task; return stream; @@ -1317,10 +1322,8 @@ protected virtual void Dispose(bool disposing) _disposedValue = true; } - public void Dispose() - { - Dispose(true); - } + public void Dispose() => Dispose(true); + #endregion public async Task LinkItem(Uri url, string path, string name, bool isFile, long size, DateTime? creationDate) From e6d091a7ed51a8d66708a7c47759d1367a1654ca Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Sat, 2 Dec 2023 13:39:03 +0300 Subject: [PATCH 50/77] =?UTF-8?q?=D0=97=D0=B0=D0=B1=D0=B0=D0=B2=D0=BD?= =?UTF-8?q?=D0=BE=D0=B5=20=D0=B4=D0=B5=D0=BB=D0=BE,=20=D0=B5=D1=81=D0=BB?= =?UTF-8?q?=D0=B8=20=D1=82=D0=B0=D0=B9=D0=BC=D0=B5=D1=80=20=D1=80=D0=B5?= =?UTF-8?q?=D0=B3=D1=83=D0=BB=D1=8F=D1=80=D0=BD=D0=BE=D0=B3=D0=BE=20=D0=B2?= =?UTF-8?q?=D1=8B=D0=B7=D0=BE=D0=B2=D0=B0=20=D0=BE=D1=81=D1=82=D0=B0=D0=BD?= =?UTF-8?q?=D0=BE=D0=B2=D0=B8=D1=82=D1=8C,=20=D1=82=D0=BE=20=D0=B2=D1=8B?= =?UTF-8?q?=D0=B7=D0=BE=D0=B2=20=D0=BC=D0=B5=D1=82=D0=BE=D0=B4=D0=B0=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D0=B4=D0=BE=D0=BB=D0=B6=D0=B0=D0=B5=D1=82=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D0=B8=D1=81=D1=85=D0=BE=D0=B4=D0=B8=D1=82?= =?UTF-8?q?=D1=8C,=20=D0=BD=D0=B5=20=D1=81=D0=BC=D0=BE=D1=82=D1=80=D1=8F?= =?UTF-8?q?=20=D0=BD=D0=B0=20Dispose=20=D0=B2=D1=81=D0=B5=D0=B3=D0=BE=20?= =?UTF-8?q?=D0=BE=D0=BA=D1=80=D1=83=D0=B6=D0=B5=D0=BD=D0=B8=D1=8F.=20?= =?UTF-8?q?=D0=9C=D0=B8=D0=BD=D0=B8=D0=BC=D0=B8=D0=B7=D0=B0=D1=86=D0=B8?= =?UTF-8?q?=D1=8F=20=D0=B8=D1=81=D0=BA=D0=BB=D1=8E=D1=87=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B9=20=D0=B8=20=D0=BD=D0=B5=D0=BC=D0=BD=D0=BE=D0=B3=D0=BE=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=BB=D0=B8=D1=80=D0=BE=D0=B2=D0=BA=D0=B8.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MailRuCloudApi/Common/EntryCache.cs | 27 +++++++++++++------ 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/MailRuCloud/MailRuCloudApi/Common/EntryCache.cs b/MailRuCloud/MailRuCloudApi/Common/EntryCache.cs index f8712820..f9df181b 100644 --- a/MailRuCloud/MailRuCloudApi/Common/EntryCache.cs +++ b/MailRuCloud/MailRuCloudApi/Common/EntryCache.cs @@ -41,7 +41,7 @@ public enum GetState //private static readonly TimeSpan _maxCleanUpInterval = new TimeSpan(0, 10 /* минуты */, 0); // По умолчанию очистка кеша от устаревших записей производится каждые 30 секунд - private TimeSpan _cleanUpPeriod = TimeSpan.FromSeconds(30); + private readonly TimeSpan _cleanUpPeriod = TimeSpan.FromSeconds(30); private readonly TimeSpan _expirePeriod; @@ -141,14 +141,22 @@ protected virtual void Dispose(bool disposing) if (_disposedValue) return; if (disposing) { - _checkActiveOperationsTimer?.Stop(); - _checkActiveOperationsTimer?.Dispose(); - _cleanTimer?.Stop(); - _cleanTimer?.Dispose(); + if (_checkActiveOperationsTimer is not null) + { + _checkActiveOperationsTimer.Stop(); + _checkActiveOperationsTimer.Enabled = false; + _checkActiveOperationsTimer.Dispose(); + } + if (_cleanTimer is not null) + { + _cleanTimer.Enabled = false; + _cleanTimer?.Stop(); + _cleanTimer?.Dispose(); + } Clear(); _locker?.Dispose(); - Logger.Debug("EntryCache disposed"); + Logger.Debug("EntryCache is disposed"); } _disposedValue = true; } @@ -250,7 +258,7 @@ public void ResetCheck() private async void CheckActiveOps(object sender, System.Timers.ElapsedEventArgs e) { CheckUpInfo info = await _activeOperationsAsync(); - if (info is null) + if (info is null || _disposedValue) return; CheckUpInfo.CheckInfo? currentValue; @@ -278,7 +286,7 @@ private async void CheckActiveOps(object sender, System.Timers.ElapsedEventArgs } } - List paths = new List(); + List paths = []; foreach (var op in info.ActiveOperations) { if (!string.IsNullOrEmpty(op.SourcePath)) @@ -339,6 +347,7 @@ parentEntry.Entry is Folder parentFolder && // то можно точно сказать, что такого элемента нет // не только в кеше, но и на сервере. Logger.Debug($"Cache says: {fullPath} doesn't exist"); + //System.Diagnostics.Debug.WriteLine($"Cache says: {fullPath} doesn't exist"); return (default, GetState.NotExists); } @@ -349,6 +358,7 @@ parentEntry.Entry is Folder parentFolder && if (cachedEntry.Entry is null) { Logger.Debug($"Cache says: {fullPath} doesn't exist"); + //System.Diagnostics.Debug.WriteLine($"Cache says: {fullPath} doesn't exist"); return (default, GetState.NotExists); } @@ -369,6 +379,7 @@ parentEntry.Entry is Folder parentFolder && if (!cachedEntry.AllDescendantsInCache) { Logger.Debug($"Cache says: {fullPath} folder's content isn't cached"); + //System.Diagnostics.Debug.WriteLine($"Cache says: {fullPath} folder's content isn't cached"); return (default, GetState.EntryWithUnknownContent); } From bc1c778ca6d7c1870e31314ca351838141cad142 Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Sat, 2 Dec 2023 13:40:02 +0300 Subject: [PATCH 51/77] =?UTF-8?q?=D0=94=D0=BB=D1=8F=20=D0=BF=D1=80=D0=BE?= =?UTF-8?q?=D1=82=D0=BE=D0=BA=D0=BE=D0=BB=D0=B0=20YadWeb=20=D0=B2=D0=BE?= =?UTF-8?q?=D1=81=D1=81=D1=82=D0=B0=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D0=B0=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=20=D1=81=D0=BB?= =?UTF-8?q?=D0=BE=D0=B6=D0=BD=D1=8B=D1=85=20=D0=BD=D0=B5=20=D0=B1=D1=83?= =?UTF-8?q?=D0=BA=D0=B2=D0=B5=D0=BD=D0=BD=D1=8B=D1=85=20=D0=BF=D0=B0=D1=80?= =?UTF-8?q?=D0=BE=D0=BB=D0=B5=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../YadWeb/Requests/YadAuthPasswordRequest.cs | 55 ++++++++++++++++--- 1 file changed, 48 insertions(+), 7 deletions(-) diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadAuthPasswordRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadAuthPasswordRequest.cs index d8dd2856..d9dd7ec3 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadAuthPasswordRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadAuthPasswordRequest.cs @@ -32,13 +32,13 @@ protected override HttpWebRequest CreateRequest(string baseDomain = null) request.Accept = "application/json, text/javascript, */*; q=0.01"; request.Referer = "https://passport.yandex.ru/"; - request.ContentType = "application/x-www-form-urlencoded; charset=UTF-8"; + //request.ContentType = "application/x-www-form-urlencoded; charset=UTF-8"; - request.Headers.Add("sec-ch-ua", "\" Not A; Brand\";v=\"99\", \"Chromium\";v=\"99\", \"Google Chrome\";v=\"99\""); + //request.Headers.Add("sec-ch-ua", "\" Not A; Brand\";v=\"99\", \"Chromium\";v=\"99\", \"Google Chrome\";v=\"99\""); request.Headers.Add("X-Requested-With", "XMLHttpRequest"); request.Headers.Add("sec-ch-ua-mobile", "?0"); request.Headers.Add("sec-ch-ua-platform", "\"Windows\""); - request.Headers.Add("Origin", "https://passport.yandex.ru"); + //request.Headers.Add("Origin", "https://passport.yandex.ru"); request.Headers.Add("Sec-Fetch-Site", "same-origin"); request.Headers.Add("Sec-Fetch-Mode", "cors"); request.Headers.Add("Sec-Fetch-Dest", "empty"); @@ -51,12 +51,29 @@ protected override HttpWebRequest CreateRequest(string baseDomain = null) protected override byte[] CreateHttpContent() { #pragma warning disable SYSLIB0013 // Type or member is obsolete + /* + * 29.11.2023 Поскольку ниже стоит FormUrlEncodedContent(keyValues), + * который сам делает кодирование, указание параметров здесь + * должно быть без Uri.EscapeUriString. + * При Uri.EscapeUriString(_auth.Password)) сервер возвращал + * ошибку password.not_matched даже после смены пароля с последующим + * множественным входом с вводом капчи, а затем уже без капчи. + * И все это до тех пор, пока пароль не был задан здесь + * просто в виде _auth.Password, без Uri.EscapeUriString. + */ + //var keyValues = new List> + //{ + // new("csrf_token", Uri.EscapeUriString(_csrf)), + // new("track_id", _trackId), + // new("password", Uri.EscapeUriString(_auth.Password)), + // new("retpath", Uri.EscapeUriString("https://disk.yandex.ru/client/disk")) + //}; var keyValues = new List> { - new("csrf_token", Uri.EscapeUriString(_csrf)), + new("csrf_token", _csrf), new("track_id", _trackId), - new("password", Uri.EscapeUriString(_auth.Password)), - new("retpath", Uri.EscapeUriString("https://disk.yandex.ru/client/disk")) + new("password", _auth.Password), + new("retpath", "https://disk.yandex.ru/client/disk") }; #pragma warning restore SYSLIB0013 // Type or member is obsolete var content = new FormUrlEncodedContent(keyValues); @@ -69,7 +86,31 @@ protected override RequestResponse DeserializeMess var res = base.DeserializeMessage(responseHeaders, stream); if (res.Result.State == "auth_challenge") - throw new AuthenticationException("Browser login required to accept additional confirmations"); + throw new AuthenticationException( + "The account requires browser login with additional confirmation by SMS or QR code. " + + "Please use BrowserAuthenticator application for this account."); + + if (res.Result.Status == "error" && + res.Result.Errors.Count > 0) + { + if (res.Result.Errors[0] == "captcha.required") + { + throw new AuthenticationException( + "Authentication failed: captcha.required. " + + "Use your browser application for several login and logout operations " + + "until site stop asking for captcha during login."); + } + if (res.Result.Errors[0] == "password.not_matched") + { + throw new AuthenticationException( + "Authentication failed: password.not_matched. " + + "The password you used to log in does not match with the main password of the account. " + + "Do not use 'Application Passwords' here, use the main account password only! " + + "In case you a sure you have used the main password, try to renew the main password."); + } + + throw new AuthenticationException("Authentication failed: " + string.Join(", ", res.Result.Errors)); + } var uid = responseHeaders["X-Default-UID"]; if (string.IsNullOrWhiteSpace(uid)) From d135c0fcc5136f12ad22b18b5d1c1cd1d3d7aad6 Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Sat, 2 Dec 2023 15:12:41 +0300 Subject: [PATCH 52/77] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=20target=20=D0=B4=D0=BB=D1=8F=20=D1=81=D0=B1=D0=BE?= =?UTF-8?q?=D1=80=D0=BA=D0=B8=20=D0=BF=D0=BE=D0=B4=20.NET=208.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 7z release.cmd | 14 ++++++++++++++ BrowserAuthenticator/BrowserAuthenticator.csproj | 2 +- Common.targets | 2 +- 3 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 7z release.cmd diff --git a/7z release.cmd b/7z release.cmd new file mode 100644 index 00000000..0ba3cbc7 --- /dev/null +++ b/7z release.cmd @@ -0,0 +1,14 @@ +set ver=1.14.2.0 +set options=-tzip -mx9 -r -sse -x!*.pdb -x!*dev* + +"C:\Program Files\7-Zip\7z.exe" a %options% "BrowserAuthenticator\bin\Release\BrowserAuthenticator-%ver%-net7.0-windows.zip" ".\BrowserAuthenticator\bin\Release\net7.0-windows\*" +"C:\Program Files\7-Zip\7z.exe" a %options% "BrowserAuthenticator\bin\Release\BrowserAuthenticator-%ver%-net8.0-windows.zip" ".\BrowserAuthenticator\bin\Release\net8.0-windows\*" + +"C:\Program Files\7-Zip\7z.exe" a %options% "WDMRC.Console\bin\Release\WebDAVCloudMailRu-%ver%-dotNetCore3.1.zip" ".\WDMRC.Console\bin\Release\netcoreapp3.1\*" +"C:\Program Files\7-Zip\7z.exe" a %options% "WDMRC.Console\bin\Release\WebDAVCloudMailRu-%ver%-dotNet48.zip" ".\WDMRC.Console\bin\Release\net48\*" +"C:\Program Files\7-Zip\7z.exe" a %options% "WDMRC.Console\bin\Release\WebDAVCloudMailRu-%ver%-dotNet5.zip" ".\WDMRC.Console\bin\Release\net5.0\*" +"C:\Program Files\7-Zip\7z.exe" a %options% "WDMRC.Console\bin\Release\WebDAVCloudMailRu-%ver%-dotNet6.zip" ".\WDMRC.Console\bin\Release\net6.0\*" +"C:\Program Files\7-Zip\7z.exe" a %options% "WDMRC.Console\bin\Release\WebDAVCloudMailRu-%ver%-dotNet7.zip" ".\WDMRC.Console\bin\Release\net7.0\*" +"C:\Program Files\7-Zip\7z.exe" a %options% "WDMRC.Console\bin\Release\WebDAVCloudMailRu-%ver%-dotNet7Win.zip" ".\WDMRC.Console\bin\Release\net7.0-windows\*" +"C:\Program Files\7-Zip\7z.exe" a %options% "WDMRC.Console\bin\Release\WebDAVCloudMailRu-%ver%-dotNet8.zip" ".\WDMRC.Console\bin\Release\net8.0\*" +"C:\Program Files\7-Zip\7z.exe" a %options% "WDMRC.Console\bin\Release\WebDAVCloudMailRu-%ver%-dotNet8Win.zip" ".\WDMRC.Console\bin\Release\net8.0-windows\*" diff --git a/BrowserAuthenticator/BrowserAuthenticator.csproj b/BrowserAuthenticator/BrowserAuthenticator.csproj index adaac8ff..6066ac53 100644 --- a/BrowserAuthenticator/BrowserAuthenticator.csproj +++ b/BrowserAuthenticator/BrowserAuthenticator.csproj @@ -3,7 +3,7 @@ WinExe $(CommonLangVersion) - net7.0-windows + net7.0-windows;net8.0-windows enable true enable diff --git a/Common.targets b/Common.targets index e60e1b20..f4ca8dee 100644 --- a/Common.targets +++ b/Common.targets @@ -1,6 +1,6 @@ - net7.0-windows;net48;netcoreapp3.1;net5.0;net6.0;net7.0 + net8.0-windows;net7.0-windows;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0 latest 1.14.2.0 From 55cd33091de0666c2464a0f8285ea26c4e64f1cd Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Sat, 2 Dec 2023 19:35:38 +0300 Subject: [PATCH 53/77] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA?= =?UTF-8?q?=D0=B8=20=D0=B8=20=D1=83=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D0=B7=D0=B0=D0=B2=D0=B8=D1=81=D0=B8=D0=BC=D0=BE=D1=81?= =?UTF-8?q?=D1=82=D0=B8=20=D0=BE=D1=82=20System.Text.Json?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BrowserAuthenticator/AuthForm.cs | 7 +++---- BrowserAuthenticator/BrowserAuthenticator.csproj | 1 - 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/BrowserAuthenticator/AuthForm.cs b/BrowserAuthenticator/AuthForm.cs index ba5455f5..887ad9e5 100644 --- a/BrowserAuthenticator/AuthForm.cs +++ b/BrowserAuthenticator/AuthForm.cs @@ -1,5 +1,4 @@ using System.Text; -using System.Text.Json; using System.Text.RegularExpressions; using Microsoft.Web.WebView2.Core; using Microsoft.Web.WebView2.WinForms; @@ -142,7 +141,7 @@ private async Task InitializeAsync() await _webView2.EnsureCoreWebView2Async(env); WebViewPanel.Controls.Add(_webView2); _webView2.Dock = DockStyle.Fill; - _webView2.CoreWebView2.FrameNavigationCompleted += WebView_NavigationCompleted; + _webView2.CoreWebView2.NavigationCompleted += WebView_NavigationCompleted; ShowWindowDelay.Enabled = true; @@ -248,10 +247,10 @@ private async void WebView_NavigationCompleted(object? sender, CoreWebView2Navig if (e.IsSuccess && _webView2 is not null && - (url.StartsWith("https://disk.yandex.ru/client/disk") || url.StartsWith("https://cloud.mail.ru/home"))) + (url.StartsWith("https://disk.yandex.ru/client/") || url.StartsWith("https://cloud.mail.ru/home"))) { var htmlEncoded = await _webView2!.CoreWebView2.ExecuteScriptAsync("document.body.outerHTML"); - _html = JsonDocument.Parse(htmlEncoded).RootElement.ToString(); + _html = Regex.Unescape(htmlEncoded)?.Trim('"'); _cookieList = await _webView2.CoreWebView2.CookieManager.GetCookiesAsync(url); diff --git a/BrowserAuthenticator/BrowserAuthenticator.csproj b/BrowserAuthenticator/BrowserAuthenticator.csproj index 6066ac53..8fb4ffeb 100644 --- a/BrowserAuthenticator/BrowserAuthenticator.csproj +++ b/BrowserAuthenticator/BrowserAuthenticator.csproj @@ -20,7 +20,6 @@ - From 09662214c3f513c62cedf99101dc8def6546c3ce Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Sat, 2 Dec 2023 20:24:26 +0300 Subject: [PATCH 54/77] =?UTF-8?q?=D0=A3=D1=87=D0=B8=D1=82=D1=8B=D0=B2?= =?UTF-8?q?=D0=B0=D1=8F,=20=D1=87=D1=82=D0=BE=20=D1=81=D0=B5=D1=80=D0=B2?= =?UTF-8?q?=D0=B8=D1=81=20=D1=81=20mail.ru=20=D0=B2=D0=BE=D0=BE=D0=B1?= =?UTF-8?q?=D1=89=D0=B5=20=D0=BF=D0=B5=D1=80=D0=B5=D1=81=D1=82=D0=B0=D0=BB?= =?UTF-8?q?=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=D1=82=D1=8C=20=D0=B4?= =?UTF-8?q?=D0=BE=20=D0=B8=D0=B7=D0=BC=D0=B5=D0=BD=D0=B5=D0=BD=D0=B8=D1=8F?= =?UTF-8?q?=20=D0=BD=D0=B0=20ClientId=20=3D=20"cloud-win",=20=D1=81=D0=B8?= =?UTF-8?q?=D0=BB=D1=8C=D0=BD=D0=BE=20=D0=BC=D0=B0=D0=BB=D0=BE=20=D0=B2?= =?UTF-8?q?=D0=B5=D1=80=D0=BE=D1=8F=D1=82=D0=BD=D0=BE,=20=D1=87=D1=82?= =?UTF-8?q?=D0=BE=20=D0=BA=D1=82=D0=BE-=D1=82=D0=BE=20=D0=B8=D0=B7=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0=D1=82=D0=B5?= =?UTF-8?q?=D0=BB=D0=B5=D0=B9=20=D0=BF=D1=80=D0=BE=D0=B4=D0=BE=D0=BB=D0=B6?= =?UTF-8?q?=D0=B0=D0=B5=D1=82=20=D0=B8=D1=81=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D1=82=D1=8C=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BD?= =?UTF-8?q?=20=D0=B8=20=D0=BF=D0=B0=D1=80=D0=BE=D0=BB=D1=8C=20=D0=B2=20?= =?UTF-8?q?=D0=B2=D0=B8=D0=B4=D0=B5=20=D0=BF=D0=B0=D1=80=D0=B0=D0=BC=D0=B5?= =?UTF-8?q?=D1=82=D1=80=D0=BE=D0=B2.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- WDMRC.Console/CommandLineOptions.cs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/WDMRC.Console/CommandLineOptions.cs b/WDMRC.Console/CommandLineOptions.cs index c7cf7ddc..16ec9e5c 100644 --- a/WDMRC.Console/CommandLineOptions.cs +++ b/WDMRC.Console/CommandLineOptions.cs @@ -16,16 +16,6 @@ class CommandLineOptions [Option('h', "host", Required = false, Default = "http://127.0.0.1", HelpText = "WebDAV server host, including protocol")] public string Host { get; set; } - [Obsolete] - [Option('l', "login", Required = false, HelpText = "Login to Mail.ru Cloud", Hidden = true)] - // ReSharper disable once UnusedMember.Global - public string Login { get; set; } - - [Obsolete] - [Option('s', "password", Required = false, HelpText = "Password to Mail.ru Cloud", Hidden = true)] - // ReSharper disable once UnusedMember.Global - public string Password { get; set; } - [Option("maxthreads", Default = 5, HelpText = "Maximum concurrent listening connections to the service")] public int MaxThreadCount { get; set; } From 6c8bddf02a978b4643dbef111ba7988bc490b0cd Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Sun, 3 Dec 2023 21:02:14 +0300 Subject: [PATCH 55/77] =?UTF-8?q?ReadMe.md=20=D0=B8=20=D0=BF=D0=BE=D0=BB?= =?UTF-8?q?=D0=B8=D1=80=D0=BE=D0=B2=D0=BA=D0=B0=20=D1=88=D0=B5=D1=80=D0=BE?= =?UTF-8?q?=D1=85=D0=BE=D0=B2=D0=B0=D1=82=D0=BE=D1=81=D1=82=D0=B5=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BrowserAuthenticator/AuthForm.cs | 4 - .../BrowserAuthenticator.csproj | 21 +- BrowserAuthenticator/JsonResult.cs | 2 + External/eng.png | Bin 0 -> 7758 bytes External/eng.png source.txt | 2 + External/rus.png | Bin 0 -> 195 bytes External/rus.png source.txt | 2 + WDMRC.Console/CommandLineOptions.cs | 23 +- WDMRC.Console/Program.cs | 2 +- WDMRC.Console/WDMRC.Console.csproj | 6 +- readme.md | 729 ++++++++++++++---- 11 files changed, 624 insertions(+), 167 deletions(-) create mode 100644 External/eng.png create mode 100644 External/eng.png source.txt create mode 100644 External/rus.png create mode 100644 External/rus.png source.txt diff --git a/BrowserAuthenticator/AuthForm.cs b/BrowserAuthenticator/AuthForm.cs index 887ad9e5..d6023508 100644 --- a/BrowserAuthenticator/AuthForm.cs +++ b/BrowserAuthenticator/AuthForm.cs @@ -23,15 +23,12 @@ public partial class AuthForm : Form private string? _desiredLogin = null; private readonly BrowserAppResult _response; private bool _exitting; - //private CoreWebView2ControllerOptions? _options = null; - //private CoreWebView2Controller? _controller = null; private string _profile; private bool _isYandexCloud; private bool _isMailCloud; private bool _manualCommit; private string? _html; private List? _cookieList; - //private Form? _form; private WebView2? _webView2; public AuthForm(string desiredLogin, string profile, bool manualCommit, @@ -39,7 +36,6 @@ public AuthForm(string desiredLogin, string profile, bool manualCommit, { InitializeComponent(); - //_form = null; _webView2 = null; _html = null; _cookieList = null; diff --git a/BrowserAuthenticator/BrowserAuthenticator.csproj b/BrowserAuthenticator/BrowserAuthenticator.csproj index 8fb4ffeb..79a6c3f7 100644 --- a/BrowserAuthenticator/BrowserAuthenticator.csproj +++ b/BrowserAuthenticator/BrowserAuthenticator.csproj @@ -11,12 +11,24 @@ $(ReleaseVersion) $(ReleaseVersion) $(ReleaseVersion) - ZZZConsulting https://github.com/ZZZConsulting/WebDavMailRuCloud readme.md False + YaR229 and Contributors + BrowserAuthenticator for WebDAV emulator + MIT License, Copyright (c) 2023 YaR229 and Contributors + cloud.ico + https://github.com/ZZZConsulting/WebDavMailRuCloud + LICENSE.txt + + + True + \ + + + @@ -37,4 +49,11 @@ + + + True + \ + + + \ No newline at end of file diff --git a/BrowserAuthenticator/JsonResult.cs b/BrowserAuthenticator/JsonResult.cs index 5322dcca..9145bf0a 100644 --- a/BrowserAuthenticator/JsonResult.cs +++ b/BrowserAuthenticator/JsonResult.cs @@ -2,6 +2,7 @@ namespace BrowserAuthenticator; +#pragma warning disable CA1507 // Use nameof to express symbol names public class BrowserAppResult { [JsonProperty("ErrorMessage")] @@ -27,6 +28,7 @@ public string Serialize() return JsonConvert.SerializeObject(this); } } + public class BrowserAppCookieResponse { [JsonProperty("name")] diff --git a/External/eng.png b/External/eng.png new file mode 100644 index 0000000000000000000000000000000000000000..8a2eded83a6ed0fbed24aa40291da5b1df2399c6 GIT binary patch literal 7758 zcmZ{Jbx<5n)a?QbEbi`3u;36}Hn@9mclW^JPJ(-opuv8)J1ic81SbS{UqWzun|iP6 z)%V9&Q`OxwQ{8?0OyASz-igytlgC0QM+X1^Sc(c@EdT&P3f|TNp}8 z*{q^k{JC0P=UPLxq_=9;qU%;uow3NN=fo0^5%88ad|OHixWXqDiYb%2L6fbZfT+m< z6#g1QJ1%sfSdiC!MRfq7IbKX3RvHcALY8F1U(?ieVQ4*99$MNk3pJpZHCuS${DdcJ z*E|xm-Hm7+HFdh!E~|DC_7Pk$5`?C>5^LDlGhM(=o&Y_qpoMe`l1otgk|zlmt4LqU zo0Yt~%9OUsxqIyYxTb7Sf1h5QGgWF4>d+07kjYh+o*(pT=eZ{LJg6Hut6-{l+?ln) zv9@!GR^P~r3oliNEmo%u>~W8;oYkd8%UPT^r?dy%AW^P0JkdR+E6qQ9pb4>X^yh!5 zBLzwlgwXhh+`fxup^8}eF)rf58qa&^L*{tv_xawBGPeSCzwwv`QKpy$q4)Q_6EzMb zpioMa@S7?I-{JLS_GP-XdqYdpP=$tB#3(;_X9NrNtD9~AyHbneW)0q)&*0CAe*E7| zWp|zgxG^xmelk`>pbZ1T2D8W!Br1rAtVtQZPl&19(Y}d&ukT}uj7fF2Vn9*3mVE_{ zm>3lv6%}@hLU?yKetP=6{YppsGIVv7>}p>U3p*wX3Pmq1Tl?8)#Y+alxs_Ll#W_8_ z-^_7OPDzQ0j<)4oEs{kY7#~MrH~l21oknV7)6(u$t}u}}EhKARNK$MC<)NR!SmM3d z4tecLSk&tv9vg&4o5RdmRK5^mHYmjBoaq`HCuC>4w|A^q_=mE5pFkEl`##~sV;MU6L!zl!qM@TxIaga- zi$_c>78!YG`Ky_Gei6N8bIJF6I_sNCId2tVv7WrmgM;Y!coE+5&yz@}tXpPBxKjpl z|Ll7{qyEZOj|NO=4-mi+5Wq^FAQCy-^+a}biW0uo<`om8)GR_x2WS}UBs@KN&MycQ zS9MC)nZR*sL8MEh>H-lEV1wx^O;?YPQSv0eqRFGO6O!nkw6L>ROS`x*)EbXVtEwUq zq9f-x_i$7_9KQWNJKYzxdma=~U@pN7oO0_yzw#*Q^f9-pA;;Neb1&a~yhc5zx2~0fe~kL}Lh|o- zog^az7;Q?k6q`$~wwl^vMOBr4ppBlMEiU?ddzTCfv2R7bD$Gb?)Tr0jaMlpaMN?MR z*3oPI7+}mr!7qX}scKonWA;G;>&h&a5q#``GqZnxRG-5~6#jL($bU{v;SYM(4X7kb zk|)XiI<>rEZxwhdk%t+V?jPC1i+0G*kw+ANtEU>3mFrUX5rUIya~#5yO02-h>0+uU zYM?}a8wTb#n5$W$MUzTR*U zUth-tm>Q8qs0&ePMW|i3t1MRa;sE55sL@&MC@3Y;mv3a*GLOXs0W;Z{GaZz?WPwbm z3MB)M`_`Wo?$Opag0O4bW3QZ&|Wf5Q$1F8O}>9NY^AGdbT{Z51mnH#@x3t1CcePAiINBi#Ry z05<-;8v7Zu|6JUOXKc;%AluYbZTWRAmsC7}6`MK$hlj_?`&Yw?%6}%JBqu@~Q02F( zqb#qc=olE(IqoBOR0hujn&~?8CE_3?EwvkAkS?{wdQ?(9^NoZ1GB7fveDpTyojTpY z)5ug-6DeY^MXx##BH*Ly?>BM8b*81={3Um&-T$)lSJO#FXQ$lh)3if#YpV=L zrscuZT=KYK%W1k2lNx1TBs3g7_PrJyvDn9`P7Lb%dmG{4C;TCLG*8cXkB7fzB4?#q zWW6sBEY05y>9QsotRCO=3==3@|TX=-TzpQcXMH|-3rte^~^RSllGbQlY6@1!Mv z`I4}S`&Dh!*&)T^<}`s+$W_>YgW1r~TI9@l?X=!?jSCb-FDz_OR4h~Ir&7F5@c!d} z8da|K*_f)hRizrFm@*li7%~|Fod&ZspJ;;+AZ(Mc16hq*?xYd(nTE~bxLDETnZ4{+ z1M(=E?Wtqq=vh;0YHS$Hedzuw$0V`Cc}3@R^)@iecdk51&eqm6>*N7G|Nd0M&_Kii z|G8NMJvoMHoQhE;G-&V73?|2OWosJL)~CGDc(AB z{k^H(*Nm!c!Ws|#dt?Cu!NB&*xY^nDDar0cw|^$Q_xG7qu}9`Bi`?tmWb_urpKgq* zI;CbMgoOND8ND69_QB!9mh|G{sQR_``u#4> z9NbQe2A`=!G4WCm+A}|ru&5a?qPIElqx0EDGSK!Dgh@wO*iH>!^p#f*y5!X3Z8xIx zIs_7go=KDX@e;{T88gu`Zl2)5Di2IeA`rcqRV#Jqd3Xq#3Mf#WoV@0j1dbxVvI&te zQzEjnd--GeLR(!f4Pn}Q`&0Q7{m}Pm{W~78Ykd|UC=NhNyAh8_09}Qd3c}83-1DVc z)bGPw=zsqO8gyIp{^!C08$OWt*(WDLXPRQNHNVLNEw6kiu4*)8Sn|Kkz3@2y6p}XX zgb85$XOIKzC{>3|R@a6q_)!4an5*k5?nj~3NfryYV|rZxR-vmXd1{(lVPi4&w{ONh zzUWbimJNitLNsA@Nf?CaWu`{j+SoT|&sgWwiTS-F#10rr%1*GW=~v{mVOoO4WzSHP zxM6Gi@Oqkk(4!@G@_3DSi8=oHdYw$@SByJiX2AQflLO>Oo9RL8 zG4oirjrY}sy;o*~v*`09uh?5rgxt_#l_JL zOF}*eAYx3X{qH5V3i-VPhBc9&tyd)j)Vu$YbrLOXj%qusA#Ol(ii8`3$TGzxQ5G}B zuwZ+Ae+AimUjQF$5~Mf1Hl7533-uwNCl9O{PW`z*Z!&;A1V4Se7cAwhN%{+UtIcY| zFH-ggs#Ek|RlS9i9U6P@8;K`w;x}(rHr+qq3MkJfv1cf;5~9OYI_g^*`cL1b)Nkh& zHE06kz~5?rRgIMXWEB=M99&&RevQo!hi{F-xuawS!+EQ!mp}Km^^LrPzgnhC>I)Gu zQK%-*5^EZ5nsVyf4y?9^Qb6c`Y->Rpg5($<~atyko$;R|N(@4#BK zJx~JAd`+bjbgEr>x$`X^lv(liD%+VkN^LnOP5X1Tz84*_qQ{9Iou`+w_b)|8PT}i0 zGx(}e(xl}fkklzC2w>dpj)g(20?eHR%RqJWrI16bssi%Ve?-Eyl4^n_Bg@8ouB2G- z6TR&eIZ}sl*xlVk{yq~zKO|Nt?=%LVp3DooUfwHm4OpbK5dKXbv503MfQophbKH)N zD@Eb{g?b-P9_5jC$FcUIPNIuW_P6_MFcSxd3PQ6_mP77eLKS~Xe#!bh&lUGucPUpj z9{5Q*Gj`!a$;WEXU28TS)7OUzKW`T3eB?#-^+gp*IdMOgx_dS*0(|6+LjOZLwwd`ta`LgENaBA4SCpCA!L5-nm!Y-Cnav`&&{GiR{10NvxnA0gM$v1G~SB%+n{6Go?Y)ijSLi6qjKV}fxXrnD~ zt}XqxWNqHq7eV*kD9Apsx0I^ab5(dq;ey0XM}{LYaaWT*TY583SY) zUHk6m{`av%oQ(5lHc9FiZ}}3S-S-6C5}rVm4$*%2N%wg3oCp`uoJNw8LL4T7L}6j3 zq}vD47$mh@hd(h4mG#?=1x-zOkm%@oH$7KC*f%3nROOo5C2J(^&`8>$p-2Y%QlZnc z`&dbj4|ZctZquVq>{hs>hf!od)#*5|v3)OI-m~wP{j@h|KO_T#f&IfPD6X!)%g<+T z$CjE}Qz~!*LW9N`bbKlVw;E$d?ZDI7E>8f0w9*Fu6|fcp!CIbOL+T9fe4Mds1ut*+kU z7OdzB?!GZm$m%RlIv)pULhM&xM)>yVx2D;F!Osgwr{5Wj`)m!3PRbIgx5_u9@&euZ zKSmDi%z2q+WA5$}THT(TZJzDSR@PLKkyPuujNV-`^qbH4oR0ti@+?kIxPHV$8Hnxe zzl!TMUT^r|pOUD~bvV0WP*l7%ElprO#7$DHhtk5yspzVc$z@B7;KPSo*knj>bXP&9 z5m>yM#Sr*${%tpTBu)r37D|zldZ)qm#ZF`tpTH-g5PiH|<3ChS{%jecb@{(Yr0w~Y z&c<6_X#mWs=p!pl`T{bxCapz9!DEx0nnG*~cJi8p%qk$TrRBH9Y^o&jkDefW{MjzC zRyag?dCu<70l`@pF@(|&4XDeSje764)xK@RrjNeR@*^hIvvg_2payQ|Gz709ZMd5W zzgCC%LjW^u`0NN8Rov}C3YdThRMElj-!Yv`L$I;1CM#-)=q3A9CKjRLsBUf~;L*sP zU<)Q*-apA18FfFSm5MaNi&=p{f9S#__VgtAr)U&BC8cUjO)ib3%7>FvXC!%fCk|6F zvck|mya=wp+sl2Ni9f}K0@NcDs1YeByjoaRmsw{6B#&t}cXf8>cM0gRfB*?}&(D9x~bw?t`R_{H)vb)1~@bXA#^aA0*5 zIVFXG7CU2me-bfY{0=Tp!SraBR^|Qc1xaWG;q@*?-BUbHPEiJ(-fSoxt5!#|mHr23 z;+@(OoV;*(!~<6|^(!jB$JnF-0uHX)_Urg}nUibW#j*QKL^CNSKXv$41H(IQF)w{1 zE72$lfA%NE14ER6hHOr>Q@t1;WtXb=GVtF-X3h0qUIHct7Aqt}Gvgg&FLq_$q(G0p zDt9%P8)b)`?4V_O-Fhm_X6o&3)XBL@6GaDzT29+xBy2-YAN}3RlQ4iX>6wd##m0aR z<64h|q(t+H@i%fK3&sY2CX;<+UmYX5yR|@$QDeP9G`HyA1v1;3v%s_A;o+W)%(l>x z5lUp_hpQMNH{!zPQt|{<%lst*X5LiFLamKI{>6Rbs0yVu73HSdLb4i_R`ND*wuFb3 zm9NK5ULFnaKYYMib|&^0OYGd9FrTq-aH#5?+-qD&@)P)W?175P4gw}P8vI)MAkjWw zrWMpxR7A#`Lz$SU$I4mrQ^wyJ5=>QA^ri#w<}e8hbas|7F>&z@GJ)sgRC)8~uO~Fu ztruWsrjJQ;9v6W+y;Yjn)8{Nmz^*?zG!(}$U_Hwt)=yvt30ASwo^P;7ufV@# z;W}wfQ?j{pG!kQ0$3{pS?}yXwU*AYIwcfZ*a~il#VR&;Ct#*gt5|!fC_lz8PTN@!m zH=IaS?k#Ad!ImKmVa3}SO$zOJha->ubt&}gDh3ZJl_DH{VF1<{u2?b?=kZH?km5_w z%%tl0_$eF9ptY8i5jz_^XQP}|yR(zDjf=ls?I2$wUF5%P8b4LIbKgN@`7{@1-js8^ zMoM*tg4H-WO7k3gd>jBLK1buBiV~NP9e^g=+R@T+x_s)@fr6yHiN4U>T&lc$7Y!|~ znz_u;oz0|;xq6FdKT^EIG9KpXHLO;v!7}R>$5sB&rEV(S=ZehF_ zY-V2vArDJj*A1>&`{=?s0`r;6-(EZJr_1jn5`%VO_iC%VNdKlVHg>+;cwImCRB6Qh z5(&rJgpL0PPOn6*0lp|Qf4q#VYuprUeq;nOtvr$zj(kRj&sb+c5}%z`b7ng{H&9Wb z3A^@re@7a^orC%GcCU2`gZuuj-F%)^@<#yyg2V>q#||dpz?&+)v5BpEZmSV(5GFY$ zh8H#b82z`VztOAU9#W5FZK?UJ^c)Y8VAT_R_Wj^r9z##kXCev#1+ zbmHRr2M-cuT5;*=tr*3kB8CwIg(p1^#-^rk{J&8y|1zZYFBO8v*gpG@a5-shy-UYG zkHdi2lN{2P-k;V_F0}#@cEI} zx>4`CjOVN7B0Z&#H6ktmAImyuQ%J6dh>OjUaIrR!ni3nMql0PrZUCWXjYi6-#b;-P z33lTVro%Sa)-|N!rxHm6q@+;LM+4n@un}LUG3=1o(u;Z!# z$em`MUKga$rTn|-1y_GI&tRbr8(zpZukX27jnB$N;>E#KK0T!UGPKi=b_IP&l>R*~ zeaQk%h_SS*D_T|6vB&A*`+OKuXmK$crKATLe3!PTnUum+4Q&=XY$xepW@aYx;ei>_ zb@F*#%oI-e@K8YNeH7pTpU)eLV?RN2zHuuGM5J?cj9hNmNmsk8TvxZSK>j!Kp?UH8 zNF`XDMGHvBYv>`&F+`;xWxHKvWc zq*`ue+L0PjW2A2GOhQ%E!R*Xo`j_nVl*7Vpdda}J22bxwc=>vg?02VbEFMGm7^;el zIX(R|9ZU)zEi4XI0St^6VVJLq3p4?{b(l!FS`x68U ze;OZWq~~P@{6SQtIOkfkrfyrca_>e7%R8yneK|lTi?D+-+l5!i0~FK{Qh443U5h3p zPoM$f0!BLM%0uDf1Crr|ePLApXvhRNHMut7@4P$G?K9Av>$fmNMMN%YRnu1N;0XLuwXFRI>xX~t4H6|aahqyze#_Ry@r z>4A+=Gz`1w8xUZcX=5O%TA=4_*jo?rz#6 z6dUPDn(V@#`Bin;a8;zih6hgy4Kjy(UiLvv);G9bw#vc0in{gtYIV0)Qvc;$$Ap-z zvf{h*z9!@)`Qbv#C23Yhm!)6zTe;g2OXWM~Wl6AslQ|35?{pjZ#lh9oiUAGTm_Ksy zK4Uq38=RZ`W+yHo#3J0&pi(#5_J5a(EI(p-oJN2!wSF(xv6t zxQ~dIuRz@TKAa+#!N5p=%m-lHWT0iFCwB+%+romLpOIskcuQV1K%ltT2U-By-2)xS zZ)m_{!4m$XCb*JC3;A4qbG6Tg$MWFwV*ABK6(S3x4TIyjZ^WpE?sQ<8mwKiHaRr(UtLZ0xts#ZLsTPBItQM(@iN)YW+oKjt}v|=qb*t(m}Jp@RD zmVkwspv;bmp3rtCnG`L@b03X&O#_h7gq4}fuIM55zMw;OKaE<+|9%fQrWZ#Fv9}ii zN~2z8;j4oy@quLRgtmGA3U2v>;6KAN;famJ*UYdxNnSMgYA}KB%C;mpAfQT|O{8km?V4{=x9%@4^oe z`@G7qySzF(9`!RWfG`vY0Y)>o=(|^;Q~dCY+wO0<9kzK1N2J2o^bBEuxI=m13$`Qvx8? zFf7mLIms-17#^;Kp1lg#nh@ZuG7es8ef|dsFcOS}3Wo;#uYvvl8_>R4&4(2sT;_6$ zzd#>dzjJR5Z*l}0gsrqnahm%`?lSe>@M_5FFM}+SkJ2$roH}@BHuK54a!Ntwm!S>_--r)p4 SQxM((p!i-5Tq|Q9{(k^i6@7>R literal 0 HcmV?d00001 diff --git a/External/eng.png source.txt b/External/eng.png source.txt new file mode 100644 index 00000000..15f1d398 --- /dev/null +++ b/External/eng.png source.txt @@ -0,0 +1,2 @@ +https://en.wikipedia.org/wiki/File:Flag_of_Russia.svg +This work is not an object of copyright according to article 1259 of Book IV of the Civil Code of the Russian Federation No. 230-FZ of December 18, 2006 \ No newline at end of file diff --git a/External/rus.png b/External/rus.png new file mode 100644 index 0000000000000000000000000000000000000000..89fdf4c0d98633c2806a9983f6a7cb1063103f00 GIT binary patch literal 195 zcmeAS@N?(olHy`uVBq!ia0vp^)j%A?!VDzaytZuuQjEnx?oJHr&dIz4a#+$GeH|GX zHuiJ>Nn{1``2&1HT>t<7KY7kc2Fqntrkz){<-QxRUI$8X7I;J!GcfQS0b$0e+I-SL zL0eB3$B>A_Z%-R?GAIZz9Oz%bdxWKlRp!n7lWcnZ4+^%&GaT5S<<>dxVoB`v(!bZz jk9Ex52xf-ff6cHyV$tSr{;5VlgBUzr{an^LB{Ts5GK)eY literal 0 HcmV?d00001 diff --git a/External/rus.png source.txt b/External/rus.png source.txt new file mode 100644 index 00000000..5276657f --- /dev/null +++ b/External/rus.png source.txt @@ -0,0 +1,2 @@ +https://en.m.wikipedia.org/wiki/File:United_Kingdom_and_United_States_flags.svg +Commons is a freely licensed media file repository. \ No newline at end of file diff --git a/WDMRC.Console/CommandLineOptions.cs b/WDMRC.Console/CommandLineOptions.cs index 16ec9e5c..6e6d1474 100644 --- a/WDMRC.Console/CommandLineOptions.cs +++ b/WDMRC.Console/CommandLineOptions.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using CommandLine; using YaR.Clouds.Base; @@ -19,22 +18,22 @@ class CommandLineOptions [Option("maxthreads", Default = 5, HelpText = "Maximum concurrent listening connections to the service")] public int MaxThreadCount { get; set; } - [Option("maxconnections", Default = 10, HelpText = "Maximum concurrent connections to cloud")] + [Option("maxconnections", Default = 10, HelpText = "Maximum concurrent connections to cloud server per instance")] public int MaxConnectionCount { get; set; } - [Option("user-agent", HelpText = "\"browser\" user-agent")] + [Option("user-agent", HelpText = "Overrides default 'user-agent' header in requests to cloud servers")] public string UserAgent { get; set; } - [Option("sec-ch-ua", HelpText = "\"browser\" sec-ch-ua")] + [Option("sec-ch-ua", HelpText = "Overrides default 'sec-ch-ua' header in requests to cloud servers.")] public string SecChUa { get; set; } - [Option("install", Required = false, HelpText = "install as Windows service with name")] + [Option("install", Required = false, HelpText = "Install as Windows service")] public string ServiceInstall { get; set; } - [Option("install-display", Required = false, HelpText = "display name for Windows service")] + [Option("install-display", Required = false, HelpText = "'Display name' of the service when installed as Windows service")] public string ServiceInstallDisplayName { get; set; } - [Option("uninstall", Required = false, HelpText = "uninstall Windows service")] + [Option("uninstall", Required = false, HelpText = "Uninstall Windows service")] public string ServiceUninstall { get; set; } [Option("service", Required = false, Default = false, HelpText = "Started as a service")] @@ -43,16 +42,18 @@ class CommandLineOptions [Option("protocol", Default = Protocol.Autodetect, HelpText = "Cloud protocol")] public Protocol Protocol { get; set; } - [Option("cache-listing", Default = 30, HelpText = "Folder cache expiration timeout, sec")] + [Option("cache-listing", Default = 30, HelpText = "Timeout of in-memory cache of cloud names of files and folders, sec")] public int CacheListingSec { get; set; } - [Option("cache-listing-depth", Default = 1, HelpText = "List query folder depth, always equals 1 when cache-listing>0")] + [Option("cache-listing-depth", Default = 1, HelpText = "Depth of folders listings, always equals 1 when cache-listing>0")] public int CacheListingDepth { get; set; } [Option("proxy-address", Default = "", HelpText = "Proxy address i.e. http://192.168.1.1:8080")] public string ProxyAddress { get; set; } + [Option("proxy-user", Default = "", HelpText = "Proxy user")] public string ProxyUser { get; set; } + [Option("proxy-password", Default = "", HelpText = "Proxy password")] public string ProxyPassword { get; set; } @@ -62,7 +63,7 @@ class CommandLineOptions [Option("use-deduplicate", Required = false, Default = false, HelpText = "Use cloud deduplicate feature to minimize traffic")] public bool UseDeduplicate { get; set; } - [Option("disable-links", Required = false, Default = false, HelpText = "Disable support for shared folder and stop using /item.links.wdmrc")] + [Option("disable-links", Required = false, Default = false, HelpText = "Disable support for shared folder and links stored in item.links.wdmrc files")] public bool DisableLinkManager { get; set; } [Option("100-continue-timeout-sec", Required = false, Default = 1, HelpText = "Timeout in seconds, to wait until the 100-Continue is received")] diff --git a/WDMRC.Console/Program.cs b/WDMRC.Console/Program.cs index 3227f9e4..c1660f27 100644 --- a/WDMRC.Console/Program.cs +++ b/WDMRC.Console/Program.cs @@ -24,7 +24,7 @@ private static void Main(string[] args) DisplayName = string.IsNullOrEmpty(options.ServiceInstallDisplayName) ? $"WebDavCloud [port {string.Join(", port ", options.Port)}]" : options.ServiceInstallDisplayName, - Description = "WebDAV emulator for cloud.Mail.ru / disk.Yandex.ru", + Description = "WebDAV emulator for cloud.Mail.ru & disk.Yandex.ru", FireStart = () => Payload.Run(options), FireStop = Payload.Stop diff --git a/WDMRC.Console/WDMRC.Console.csproj b/WDMRC.Console/WDMRC.Console.csproj index 085f5c5a..f2fe26c9 100644 --- a/WDMRC.Console/WDMRC.Console.csproj +++ b/WDMRC.Console/WDMRC.Console.csproj @@ -13,8 +13,8 @@ YaR WebDAVCloudMailRu - MIT License, Copyright (c) 2023 YaR - WebDAV emulator for cloud.Mail.ru / disk.Yandex.ru + MIT License, Copyright (c) 2023 YaR229 and Contributors + WebDAV emulator for RU-clouds: Cloud.Mail.Ru " Disk.Yandex.Ru WebDAVCloudMailRu $(ReleaseVersion) $(ReleaseVersion) @@ -26,7 +26,7 @@ https://github.com/yar229/WebDavMailRuCloud readme.md False - WebDAV emulator for cloud.Mail.ru / disk.Yandex.ru + WebDAV emulator for RU-clouds: Cloud.Mail.Ru " Disk.Yandex.Ru diff --git a/readme.md b/readme.md index ab697c7d..1a0f104c 100644 --- a/readme.md +++ b/readme.md @@ -1,56 +1,174 @@ -## **WebDAV emulator for cloud.Mail.ru / disk.Yandex.ru**
+# The **WebDAV emulator** for RU-clouds: Cloud.Mail.Ru & Disk.Yandex.Ru + +--- + +The root project by YaR229 ----- -@ZZZConsulting:
-Дополнительно сделан вход на Яндекс.Диск с помощью браузера.
-Поддерживается всё разнообразие вариантов аутентификации, включая СМС-коды и QR-коды. +--- + +The fork project by ZZZConsulting + + + + +--- +@ZZZConsulting: + +Самые важные изменения с предыдущей версии: + +* Поддержка .NET 8.0 (включая установку сервисом Windows). +* Для одновременного использования обоих облаков Cloud.Mail.Ru и Disk.Yandex.Ru больше нет необходимости в установке двух отдельных экземпляров, облако определяется при подключении. +* Для Cloud.Mail.Ru восстановлена работоспособность, потерянная почти год назад (возможно не для всех или не во всех случаях). +* Проверка показала, что Cloud.Mail.Ru дает прямое подключение по WebDAV, однако, эмулятор WebDAV с включенным кешированием может быть какое-то ускорение для клиентов, не имеющих самостоятельного кеширования - проверяйте у себя. +* Протокола YadWebV2 больше нет, теперь для Яндекса только один протокол - YadWeb, но с вариациями (про них ниже). +* Полностью переписано кеширование названий файлов и папок, значительно сокращено количество и длительность обращений к облачным серверам за названиями. После создания папок, загрузки файлов, удаления файлов больше не перечитываются папки целиком, только отдельные части, что существенно ускорило операции массовых загрузок или чисток файлов. +* От протокола YadWebV2 в протокол YadWeb перенесено ускоренное чтение больших папок, но только при браузерной аутентификации (в целях совместимости). Если количество записей в папке более порогового значения, чтение содержимого папки с сервера происходит в несколько параллельных запросов (максимально до 10), что в разы сокращает общее время на папках в десятки тысяч файлов. +* Для протокола YadWeb исправлена потенциальная проблема с паролями, содержащими символы типа `%`, `"`, `'` и др. -Версия .NET обновлена до 7.0, включая поддержку установки сервисом Windows. +--- +### Это ВАЖНО! -
-
-
+Не смотря на то, что лицензия все обговаривает (да кто ж её читает?), +необходимо напомнить, что за ВАШИ данные несете ответственность только ВЫ! +Программное обеспечение может содержать ошибки. И даже в случае, когда оно прошло самое лучше тестирование, которое может быть, конкретно в Вашей среде, с Вашими настройками, Вашими параметрами, серверами, задержками, файлами и чем угодно еще, программное обеспечение может дать сбой и повести себя не так, как того от него ожидали. +Поэтому, каждый раз, перед тем, как начать использовать новую версию программы на важных для Вас данных, проверьте, что конкретно у Вас и в Вашей среде эта новая версия работает корректно и без критических ошибок, что как минимум она не портит Ваши данные. +Авторы данного программного обеспечения не гарантируют правильность его работы, не гарантируют правильность и сохранность данных, +и не несут ответственность за последствия применения данного программного обеспечения. +Используя данное программное обеспечение, ВЫ берете на себя ответственность за сохранность Ваших данных. +Никто специальным вредительством не занимается, но ошибки в ПО есть всегда! +Тем более, что используется неофициальные API, от чего правильное функционирование программы может прекратиться в любой момент. -#### Requirements -* [Windows](#windows) - .NET Framework 4.8 / [.NET 3.1 / 5.0 / 6.0 / 7.0](https://dotnet.microsoft.com/download?initial-os=windows) -* [Linux](#linux) - Mono 6.8 / [.NET 3.1 / 5.0 / 6.0 / 7.0](https://dotnet.microsoft.com/download?initial-os=linux) -* [OS X](#mac-os-x) - Mono 6.8 / [.NET 3.1 / 5.0 / 6.0 / 7.0](https://dotnet.microsoft.com/download?initial-os=macos) +--- -#### Usage + +### Requirements +* [Windows](#windows) - [.NET Framework 4.8](https://dotnet.microsoft.com/en-us/download/dotnet-framework) / [.NET Core 3.1](https://dotnet.microsoft.com/en-us/download/dotnet/3.1) / [.NET 5.0](https://dotnet.microsoft.com/en-us/download/dotnet/5.0) / [.NET 6.0](https://dotnet.microsoft.com/en-us/download/dotnet/6.0) / [.NET 7.0](https://dotnet.microsoft.com/en-us/download/dotnet/7.0) / [.NET 8.0](https://dotnet.microsoft.com/en-us/download/dotnet/8.0) +* [Linux](#linux) - Mono 6.8 / [.NET Core 3.1](https://dotnet.microsoft.com/en-us/download/dotnet/3.1) / [.NET 5.0](https://dotnet.microsoft.com/en-us/download/dotnet/5.0) / [.NET 6.0](https://dotnet.microsoft.com/en-us/download/dotnet/6.0) / [.NET 7.0](https://dotnet.microsoft.com/en-us/download/dotnet/7.0) / [.NET 8.0](https://dotnet.microsoft.com/en-us/download/dotnet/8.0) +* [OS X](#mac-os-x) - Mono 6.8 / [.NET Core 3.1](https://dotnet.microsoft.com/en-us/download/dotnet/3.1) / [.NET 5.0](https://dotnet.microsoft.com/en-us/download/dotnet/5.0) / [.NET 6.0](https://dotnet.microsoft.com/en-us/download/dotnet/6.0) / [.NET 7.0](https://dotnet.microsoft.com/en-us/download/dotnet/7.0) / [.NET 8.0](https://dotnet.microsoft.com/en-us/download/dotnet/8.0) + +--- + +#### Usage        ENG ``` - -p, --port (Default: 801) WebDAV server port or several ports separated by `,` - -h, --host (Default: "http://127.0.0.1") WebDAV server host with protocol (http://* for http://0.0.0.0) - --maxthreads (Default: 5) Maximum concurrent connections to cloud.mail.ru / disk.yandex.ru - --use-locks use locking feature - --cache-listing (Default: 30) Duration of in-memory cache of folder's listing, sec - --cache-listing-depth (Default: 1) Cache folders listing depth. - If large folder browsing is extremely slow, set to 2 - - --protocol (Default: WebM1Bin) Cloud protocol - * WebM1Bin - (Cloud.Mail.Ru) mix of mobile and DiskO protocols - * WebV2 - (Cloud.Mail.Ru) [deprecated] desktop browser protocol - * YadWeb - (Yandex.Disk) desktop browser protocol, see Yandex.Disk readme section - * YadWebV2 - (Yandex.Disk) desktop browser protocol with browser authentication, see Yandex.Disk readme section - - --use-deduplicate Enable deduplication (upload speedup, put by hash), see Using deduplication readme section - - --install Install as windows service (Windows .Net 4.8/7.0 versions only) - --install-display Display name for Windows service (Windows .Net 4.8/7.0 versions only) - --uninstall Uninstall windows service (Windows .Net 4.8/7.0 versions only) + -p, --port (Default: 801) WebDAV emulator port or several ports separated by `,`. + -h, --host (Default: "http://127.0.0.1") WebDAV emulator host with protocol + (http://* for http://0.0.0.0). + --maxthreads (Default: 5) Maximum concurrent listening connections to the service. + --maxconnections (Default: 10) Maximum concurrent connections to cloud server per instance. + --use-locks (Default: false) Use locking feature. + --cache-listing (Default: 30) Timeout of in-memory cache of file and folder names + of the cloud in seconds. 0 disables the cache. + --cache-listing-depth (Default: 1) Folder hierarchy depth when listing folders content. + Always equals 1 when cache-listing > 0. + To maximize performance + set cache-listing-depth=1 and cache-listing between 600 and 1800. + --use-deduplicate (Default: false) Enable deduplication (upload speedup, put by hash), + see Using deduplication readme section. + --disable-links (Default: false) Disable support for shared folder and links + stored in item.links.wdmrc files. + + --protocol (Default: Autodetect) Cloud protocol + * Autodetect - see Auto-detect protocol readme section + * WebM1Bin - (Cloud.Mail.Ru) mix of mobile and DiskO protocols + * WebV2 - (Cloud.Mail.Ru) [deprecated] desktop browser protocol + * YadWeb - (Disk.Yandex.Ru) desktop browser protocol, + see Disk.Yandex.Ru readme section + + --install Install as Windows service (Windows .Net 4.8/7.0/8.0 versions only). + --install-display 'Display name' of the service when installed as Windows service + (Windows .Net 4.8/7.0/8.0 versions only). + --uninstall Uninstall Windows service (Windows .Net 4.8/7.0/8.0 versions only). --proxy-address ://
: Use proxy --proxy-user Proxy user name --proxy-password Proxy password + --100-continue-timeout-sec (Default: 1) Timeout in seconds, + to wait until the 100-Continue is received if chuck transfer. + --response-timeout-sec (Default: 100) Timeout in seconds, + to wait until 1-st byte from server is received. + --read-write-timeout-sec (Default: 300) Timeout in seconds, + the maximum duration of read or write operation. + If your Internet connection is slow + or you upload/download large files increase the value. + --cloud-instance-timeout (Default: 30) Cloud instance (server+login) expiration timeout in minutes. + On request the service creates one instance per cloud and login. + After specified period of time without requests the instance is recycled. + --help Display this help screen. --version Display version information. - -user-agent overrides default 'user-agent' string in request headers while accessing clouds. - -sec-ch-ua overrides default 'sec-ch-ua' string in request headers while accessing clouds. + -user-agent Overrides default 'user-agent' header in requests to cloud servers. + -sec-ch-ua Overrides default 'sec-ch-ua' header in requests to cloud servers. +``` + +#### Параметры        RUS ``` + -p, --port (По умолчанию: 801) Порт (или список портов через `,`), + на которых эмулятор WebDAV принимает подключения. + -h, --host (По умолчанию: "http://127.0.0.1") адрес и протокол для приема + входящих подключений к эмулятору WebDAV (http://* для http://0.0.0.0). + --maxthreads (По умолчанию: 5) Максимальное количество одновременно + обрабатываемых подключений к эмулятору WebDAV. + --maxconnections (По умолчанию: 10) Максимальное количество соединений + каждого экземпляра эмулятора WebDAV к облачным серверам. + --use-locks (По умолчанию: false) Использовать блокировки + одновременного доступа к файлам. + --cache-listing (По умолчанию: 30) Таймаут в секундах хранения в памяти + списков файлов папок облачных серверов. + 0 для выключения кеширования списков файлов. + --cache-listing-depth (По умолчанию: 1) Сколько уровней вложенности папок + за раз читать с сервера. Всегда равно 1, если cache-listing > 0. + Для максимизации производительности задать + cache-listing-depth = 1 и cache-listing от 600 до 1800. + --use-deduplicate (По умолчанию: false) Включить deduplication + (ускорение загрузки по хэшу), см. раздел deduplication. + --disable-links (По умолчанию: false) Отключить поддержку общих папок и ссылок, + хранимых в файлах item.links.wdmrc. + + --protocol (По умолчанию: Autodetect) Протокол работы с облаком + * Autodetect - см. раздел Auto-detect protocol + * WebM1Bin - (Cloud.Mail.Ru) гибрид для мобильных и DiskO + * WebV2 - (Cloud.Mail.Ru) [устарел] протокол для браузера на ПК + * YadWeb - (Disk.Yandex.Ru) протокол браузера на ПК, + см. раздел Disk.Yandex.Ru readme + + --install Установка сервисом Windows + (только для сборок для Windows версий .Net 4.8/7.0/8.0). + --install-display Отображаемое имя для сервиса + (только для сборок для Windows версий .Net 4.8/7.0/8.0). + --uninstall Удаление сервиса из Windows + (только для сборок для Windows версий .Net 4.8/7.0/8.0). + + --proxy-address ://
: Установка прокси-сервера + --proxy-user Установка user name для прокси-сервера + --proxy-password Установка password для прокси-сервера + + --100-continue-timeout-sec (По умолчанию: 1) Таймаут в секундах + на получение 100-Continue от сервера при блочной передаче. + --response-timeout-sec (По умолчанию: 100) Таймаут в секундах + на получение 1-го байта ответа от сервера. + --read-write-timeout-sec (По умолчанию: 300) Таймаут в секундах + на получение последнего байта данных от сервера. + Увеличьте значение при медленном интернете + или при загрузке/скачивании больших файлов. + --cloud-instance-timeout (По умолчанию: 30) Таймаут в минутах + на прекращение работы экземпляра (по облаку и логину) сервиса. + Эмулятор WebDAV на каждую пару облако+логин создает свой экземпляр сервиса. + При отсутствии обращений к экземпляру сервиса в течение указанного периода, + память освобождается от экземпляра сервиса. + + --help Справка о параметрах на английском языке. + --version Версия программы. + + -user-agent Переопределяет стандартный 'user-agent' в заголовках обращений к облачным серверам. + -sec-ch-ua Переопределяет стандартный заголовок 'sec-ch-ua' в обращениях к облачным серверам. +``` + +--- #### Hasher.exe usage @@ -70,6 +188,8 @@ Calculating hashes for local files --version Display version information. ``` +--- + ### Using deduplication (upload speedup, put by hash) Edit `` section in `wdmrc.config`: @@ -100,94 +220,416 @@ Edit `` section in `wdmrc.config`: ``` Then run with `--use-deduplicate` command line key. +--- + +### Cloud protocol and `Autodetect`        ENG + +Direct use of WebDAV with Cloud.Mail.Ru or Disk.Yandex.Ru is full of problems. +The WebDAV emulator is made to solve the problems using unofficial APIs. +We call the subset of API methods used to achieve a desired result a `protocol`. +When user going to reach a cloud through WebDAV emulator, +the WebDAV emulator must choose: +* what cloud should be used (Cloud.Mail.Ru or Disk.Yandex.ru), +* what protocol (API) must be used, +* what kind of authentication must be used (basic login+password or browser). + +***Autodetect***. How it works + +Step **1**. The Cloud + +If user specified `login` in email style (e.g. John@yandex.ru or John@mail.ru) +the Cloud is determined by email domain. +Otherwise (e.g. `login` is John) the Cloud is determined by `protocol` parameter of the application. + +Step **2**. The Protocol + +In case when the Cloud is determined and the Protocol is not, +the WebDAV emulator uses +* WebM1Bin protocol for Cloud.Mail.Ru and +* YadWeb protocol for Disk.Yandex.Ru. + +Step **3**. The Authentication type + +The WebM1Bin protocol has only one type of authentication - `login` and `password`. + +The YadWeb protocol for Disk.Yandex.Ru has two types of authentication: + +* `login` and `password` +and +* browser authentication using `BrowserAuthenticator` application. + +By default the login+password authentication is used. + +If `BrowserAuthenticator` is configured in `wdmrc.config` and +user `password` equals to `BrowserAuthenticator` password +the browser authentication is used. + +User can give the WebDAV emulator the suggestion which type of authentication to use by appending symbol `!` or `?` in `login` string at leftmost position. +`?` means the browser authentication must be used. +`!` means the browser authentication must **not** be used. + +The leftmost symbols `!` and `?` are removed from login while talking to clouds. + +If browser authentication is not used, a user have to fill `password` with password of cloud account. +The WebDAV emulator with YadWeb emulates browser, so the main password of the account must be used to connect to the cloud! +Application password generated in Yandex account will fail! + +If browser authentication is used, a user have two options. +In both cases `BrowserAuthenticator` application is going to authenticate incoming request by matching incoming password against password on Settings window. +The first options is to pass to WebDAV emulator exact value of password on Settings window of the `BrowserAuthenticator` application. +The second option is to pass empty password to WebDAW emulator. That means WebDAW emulator should take the password from password attribute of the `BrowserAuthenticator` tag in `wdmrc.config`. + +If you change password in Settings window of `BrowserAuthenticator` application quite often, +you may want to setup password once in `wdmrc.config` and use login with empty password. + +If you believe some one except you can try to connect to you `BrowserAuthenticator` application,you can put wrong password into password attribute of the `BrowserAuthenticator` tag in `wdmrc.config` +and use login and correct password (equal to password in Settings window of `BrowserAuthenticator` application) while connecting to WebDAW emulator. + +### Cloud protocol and `Autodetect`        RUS + +Использование WebDAV с облаками Cloud.Mail.Ru и Disk.Yandex.Ru не лишено проблем. +Эмулятор WebDAV создан чтобы решить эти проблемы с использованием неофициального APIs. +Подмножество методов API для работы с облаком называется протоколом. +Когда пользователь использует эмулятор WebDAV, пытаясь подключиться к облаку, +эмулятору WebDAV необходимо решить: +* к какому облаку будет подключение (Cloud.Mail.Ru или Disk.Yandex.ru), +* какой протокол (API) следует использовать, +* какой тип аутентификации должен быть использован (просто login+password или аутентификация через браузер). + +***Autodetect***. Как это работает + +Шаг **1**. Определение облака + +Если пользователь задал `login` в формате email (например, John@yandex.ru or John@mail.ru) +облако определяется по домену из email (часть после символа `@`). +В остальных случаях (например, `login` задан как John) облако определяется протоколом из параметра `protocol` при запуске приложения. + +Шаг **2**. Определение протокола + +Если облако уже определено, а протокол еще нет, эмулятор WebDAV использует +* протокол WebM1Bin для Cloud.Mail.Ru и +* протокол YadWeb для Disk.Yandex.Ru. + +Шаг **3**. Тип аутентификации + +Протокол WebM1Bin имеет только один тип аутентификации - по `login` и `password`. + +Для протокола YadWeb для облака Disk.Yandex.Ru есть два варианта: + +* по `login` и `password` +и +* аутентификация через специальный браузер - `BrowserAuthenticator`. + +По умолчанию YadWeb используется login+password. + +Если в конфигурационном задан `BrowserAuthenticator`, +то есть в файле `wdmrc.config` задан тэг `BrowserAuthenticator` и атрибут `password`, +и переданный пользователем пароль совпадает с паролем в конфигурации `BrowserAuthenticator`, +то считается, что требуется аутентификация через браузер. + +Для большей надежности пользователь может давать эмулятору WebDAV подсказки, добавляя знаки `!` или `?` в `login` в первую позицию. +`?` в начале означает обязательную аутентификацию через браузер. +`!` в начале означает недопустимость аутентификации через браузер. + +Начальные символы `!` и `?` удаляются из логина, передаваемого дальше на облачный сервер. + +Если аутентификация через браузер не используется, `password` заполняется основным паролем учетной записи Яндекса. +`Пароли приложений` создаваемые в учетной записи для доступа сторонних приложений здесь не подходят, +т.к. эмулятор WebDAV работает не как другие приложения, а имитирует доступ браузера к облаку. +Если используется аутентификация через браузер, существует два варианта заполнить `password`. +В обоих случаях приложение `BrowserAuthenticator` при всех входящих подключениях сверяет переданный пароль с паролем, заданным в окне настроек приложения `BrowserAuthenticator`. +Вариант первый: передать в эмулятор WebDAV пароль, в точности соответствующий заданному в окне настроек приложения `BrowserAuthenticator`. +Вариант второй: передать в эмулятор WebDAV пустой пароль. Это заставит эмулятор WebDAV при обращении к приложению `BrowserAuthenticator` +использовать пароль, заданный в `wdmrc.config` в тэге `BrowserAuthenticator`. -### Yandex.Disk +Если пароль в настройках приложения `BrowserAuthenticator` меняется достаточно часть, +можно положиться на вариант с пустым паролем и задавать его только в одном месте - в конфигурационном файле. -(download [latest Release](https://github.com/ZZZConsulting/WebDavMailRuCloud/releases/), use `--protocol YadWebV2` command line key) +Но если есть риски, что к приложению `BrowserAuthenticator` может подключиться кто-то сторонний, +есть смысл указывать пароль от приложения `BrowserAuthenticator` в каждом подключении к WebDAV emulator, +а в `wdmrc.config` в тэге `BrowserAuthenticator` специально установить неправильный пароль. -Yandex.Disk WebDAV issues +--- -* It seems Yandex.Disk WebDAV is limited by speed now. +### Disk.Yandex.Ru        ENG + +Issues of WebDAV by Disk.Yandex.Ru + +* It seems like WebDAV of Disk.Yandex.Ru is limited by speed since 2019. * After file uploading Yandex servers calculating hash. E.g. for a 10GB file it may take ~1..2 minutes depending on server load. So most of WebDAV clients drops connection on timeout. * There's no WebDAV info in official help now. WTF? +* Since 2019 Yandex states that WebDAV is OK for all supported applications made by Yandex, + also Yandex does not support and does not guarantee correct work + of any third party application with their WebDAV. + +To bypass the limit issue the WebDAV emulator uses the unofficial Disk.Yandex.Ru Web API. +The salvation have 2 steps: + +**1**) Disk.Yandex.Ru WebDAV authentication + +To get in to Disk.Yandex.Ru you have to be authenticated by Yandex by any of two ways: + +* By `login` & `password` only. + The account must be configured for log in using login and password only. + Yandex account security have an option to create Application passwords. + Do not use Application password when you connecting to this WebDAW service! + You **must** use the main account password only + because this service emulates you on the web using browser. + Fill the `login` field with email (e.g. John@yandex.ru instead of John). + +* By standard web site authentication using specially designed `browser` called `BrowserAuthenticator`. + The BrowserAuthenticator application is designed to be run at Windows startup. + It hides in system tray and waits for incoming authentication + request from the WebDAV emulator. + When requested, BrowserAuthenticator shows a browser window allowing you to log into cloud + using `login` and `password` or `login` and `password` and SMS or even QR code. + When you successfully logged in, the program takes all data from the browser + and sends it back to the WebDAV emulator, then the service talks + to cloud servers using you authentication information. + Because your authentication information is used, you **must** keep cache with the information **secured**! + For more information read the `BrowserAuthenticator` section. + +**2**) File operations with Disk.Yandex.Ru + +Once authenticated all file operations such as reading, writing, creating, deleting, +and so on are available for use. +Because the WebDAV emulator emulates you using a browser the Issue is not applied. +Unfortunately, there is no guarantee the service is going to work infinitely long. +Time to time Yandex makes unexpected changes in their programs. + +### Disk.Yandex.Ru        RUS + +«Косяк» с WebDAV от Disk.Yandex.Ru + +* С конца 2019 года при загрузке файлов на Disk.Yandex.Ru по WebDAV были введены ограничения. +* После загрузки файла, сервера Яндекса стали столь долго подсчитывать хэши файлов, + что общая скорость оказать значительно ниже приемлемого уровня. + Например, после загрузки 10 ГБ расчет хэша может занять ~1-2 минуты, + из-за чего большинство клиентов отваливаются по таймауту. +* При этом сам Яндекс заявляет, что в WebDAV все хорошо, их же приложения с WebDAV прекрасно работают, + а за работу чужих приложений с их WebDAV они не отвечают. + +При всех `тормозах` с WebDAV Яндекс.Диск очень даже быстро работает через браузер. +В качестве обходного пути эмулятор WebDAV использует неофициальное Web API, прикидываясь браузером. +Проблема решается в 2 шага: + +**1**) Аутентификация на Disk.Yandex.Ru + +Чтобы подключиться к Disk.Yandex.Ru нужно аутентифицироваться любым из двух способов: + +* Только `login` + `password`. + В разделе безопасности учетной записи должен быть настроен вход только по логину и паролю. + `Пароли приложений`, которые позволяет создавать Яндекс для доступа сторонних приложений, + не должны применяется. Эмулятор WebDAV работает не как другие сторонние приложения, + он прикидывается браузером, а потому для него не подходят пароли приложений. + Заполнять `password` нужно только основным паролем учетной записи! + Заполнять `login` необходимо полным email (например, John@yandex.ru, не кратким John), + во избежание проблем при определении облака и т.д. + +* Стандартная аутентификация в специальном созданном браузере `BrowserAuthenticator`. + Приложение BrowserAuthenticator создано так, чтобы запускаться при старте Windows. + При запуске приложение скрывается в области системных иконок чтобы не мешаться. + Будучи запущенным, приложение `BrowserAuthenticator` ожидает входящих подключений от эмулятора WebDAV, + а получив вызов открывает окно браузера. позволяя войти в учетную запись облака. + Если вход успешно состоялся, информация со страницы и куки браузера, + содержащие информацию об аутентификации, приложением `BrowserAuthenticator` передаются обратно в эмулятор WebDAV. + Информации, собираемой приложением `BrowserAuthenticator` и передаваемой эмулятору WebDAV + достаточно чтобы без участия пользователя и от его имени сделать что угодно на облачном сервере! + По этой причине охраняйте доступ к папкам, где расположена программа `BrowserAuthenticator` и кеш эмулятора WebDAV! + Подробности читайте в разделе `BrowserAuthenticator`. + +**2**) Дисковые операции с Disk.Yandex.Ru + +После аутентификации все операции по чтению, записи, созданию и удалению доступны для использования. +И т.к. эмулятор WebDAV имитирует работу пользователя в браузере на облачном сервере, все работает достаточно шустро. +При этом следует помнить, что применяется неофициальное API, из-за чего корректная работа может прекратиться в любой момент. + +--- + +### BrowserAuthenticator        ENG + +is a specially designed `browser` meant be run at Windows startup. +It hides in system tray and waits for incoming authentication +request from the WebDAV emulator. +When requested, BrowserAuthenticator shows a browser window allowing you to log into cloud +using `login` and `password` only +or `login` and `password` and SMS code +or even QR code. +When you successfully logged in, the program takes all data from the browser +and sends it back to the WebDAV emulator, then the service talks +to cloud servers using you authentication information. + +**Remember!** + +The WebDAV emulator impersonates you using you credentials and security cookies taken from the `BrowserAuthenticator` application when you logged in! +Both the WebDAV emulator and the `BrowserAuthenticator` application store the security information on drives. +**Read the following instruction very carefully!** + +***Step-by-step setup of BrowserAuthenticator*** + +1. You need to choose the location for the `BrowserAuthenticator`. +When `BrowserAuthenticator` runs it creates subfolders below folder where it is placed. It means it should have enough rights to create and delete folders and files. +One of subfolders is going to have security information enough to connect to your cloud without your interaction, so the place **must** be secured! + +One of the best options for hosting `BrowserAuthenticator` is +`%userprofile%\AppData\Local\Applications\BrowserAuthenticator` +(you need to create the folders yourself in `%userprofile%\AppData\Local`) +or any other folder in `%userprofile%\AppData\Local` of your choice. + +2. Download BrowserAuthenticator-*-windows.zip package and extract it's content to the `BrowserAuthenticator` folder. + +3. Press Win+R and run `shell:startup`. Put shortcut of the `BrowserAuthenticator` program in startup folder, +so the `BrowserAuthenticator` will be started everytime you log in to Windows. +Don't make copy of the `BrowserAuthenticator` in startup folder, the shortcut only! +Start the `BrowserAuthenticator` manually for the first time. + +4. Since it started the `BrowserAuthenticator` stays in system tray until you system reboot or you manually exit it using menu on tray icon. +Use mouse double click on the `BrowserAuthenticator` tray icon + to open up Settings window of the application. + +5. On the Setting Window setup then `port` and the `password` for incoming connections. +Type in an email and press Test to check the browser is functional. +For the `password` you can use any text string. To get things easy you can generate password by clicking the blue text under the field. + +6. Go to WebDAV emulator application folder. Open the `wdmrc.config` and edit the `` tag (add the tag if it's missing). + +Attributes of ``: + +* `Url`="http://`localhost`:``/" - type in the address of the PC running BrowserAuthenticator application, + `port` is the `port` you set in Settings window on the previous step. + `localhost` could be replaced by any wold wide IP address reachable by WebDAV emulator. + +* `Password` is the text string you set in Settings window on the previous step. + You should keep the `Password` in secret. Otherwise someone could connect to you `BrowserAuthenticator` application and ***steal*** you browser cookies, you credentials and you cloud data! + +* `CacheDir` is the full path to a folder where WebDAW emulator is going to keep information received from `BrowserAuthenticator` application. + It contains browser cookies, the information enough to connect to your cloud impersonating you! + Keep the folder secured as much as possible! + +On step 1, you have created a folder for `BrowserAuthenticator` application somewhere in `%userprofile%\AppData\Local` +Make another subfolder in `BrowserAuthenticator` application folder and put it's path into `CacheDir` attribute, so this way you have to keep in secret only one location not two. + +In cage you don't want to cache information received from `BrowserAuthenticator` application remove the `CacheDir` attribute or put an empty string as a value. + +### BrowserAuthenticator        RUS + +-- это специальный браузер, предназначенный к запуску вместе с Windows, +чья иконка располагается в системной области внизу экрана среди иконок массы других запущенный программ. + +BrowserAuthenticator ожидает запросов на аутентификацию от эмулятора WebDAV, а получив такой, +открывает окно браузера и ждет, когда пользователь войдет в нужную облачную учетную запись. +Используя браузер, пользователь может войти в учетную запись, у которой могут быть установлены любые настройки входа: +только `login` и `password`, +`login` + `password` + код из СМС, +или даже вход по even QR-коду или ключу. + +Как только вход в нужную учетную запись состоялся, `BrowserAuthenticator` собирает со страницы необходимые данные, +добавляет к ним куки с информацией об аутентификации, +и отправляет обратно в эмулятор WebDAV, который используя полученную информацию а значит, +представляясь вошедшим в учетную запись пользователем, совершает затребованные действия на облачном сервере. + +**Помните!** + +Позволяя получить ваши данные, Вы даете передаете достаточно сведений, чтобы без Вашего участия +можно было сделать на облачном сервере что угодно от Вашего имени! +Поэтому охраняйте доступ к папкам, где расположено приложение `BrowserAuthenticator`, +а также где расположен эмулятором WebDAV кеш данных, полученных от `BrowserAuthenticator`, +от посторонних! + +**Внимательно прочитайте следующую инструкцию по настройке приложения `BrowserAuthenticator`!** + +***Установка и настройка BrowserAuthenticator*** + +1. Сначала выберите место для приложения `BrowserAuthenticator`. +Когда приложение работает, оно создает папки и файлы рядом со своим исполняемым файлом. +По этой причине у приложения должно быть достаточно прав на +создание и удаление папок и файлов внутри выбранной под приложения папке. +Кроме того, в создаваемых папках будет секретная информация, позволяющая получить доступ +к облачным данным пользователей, которые будут аутентифицироваться через `BrowserAuthenticator`, +поэтому к папкам должен быть ограничен доступ. + +Одним из лучших будет создание папки где-нибудь в `%userprofile%\AppData\Local`, +например, `%userprofile%\AppData\Local\Applications\BrowserAuthenticator` +(необходимые папки нужно создать вручную). + +2. Скачайте пакет BrowserAuthenticator-*-windows.zip, затем распакуйте его содержимое в папку, выбранную под приложение `BrowserAuthenticator`. + +3. Нажмите Win+R и запустите `shell:startup`. Поместите ярлык приложения `BrowserAuthenticator` в открывшейся папке автозапуска. +Это позволит приложению `BrowserAuthenticator` запускаться каждый раз при запуске Windows, при входе в учетную запись. +Не делайте копию приложения в папку автозапуска, помещайте туда только ярлык на приложение! +Для первого раза запустите приложение `BrowserAuthenticator` вручную. + +4. При запуске приложение `BrowserAuthenticator` помещает свою иконку в системной области внизу экрана среди иконок других приложений. +Приложение остается запущенным до перезагрузки или выхода из учетной записи. Приложение может быть закрыто через меню у иконки приложения в системной области. +Дважды щелкните мышью по иконке приложения `BrowserAuthenticator` для открытия окна с Настройками приложения.. + +5. В окне Настроек приложения задайте порт для входящих соединений и пароль. +Введите email облачной учетной записи и нажмите Test для проверки работоспособности встроенного браузера. +В качестве пароля можно использовать любую не пустую текстовую строку. Для простоты под полем ввода +можно щелкнуть по голубой надписи чтобы создать новый Guid, который и будет новым паролем. + +6. Перейдите в папку с установленным эмулятором WebDAV. Откройте `wdmrc.config` и отредактируйте тэг `` (добавьте тэг, если он отсутствует). -This solution allow to bypass that limits using unofficial Yandex.Disk Web API. - -Yandex.Disk WebDAV authentication - -There are 2 ways to get into Yandex.Disk: -* Use login & password only. - The option to use login & password only must be selected in account settings. - In this case login (e.g. John or John@yandex.ru) - and main account password should be used in authentication fields. - Do not use an Application password or any other special password generated by Yandex, - the main original account password must be used. And no codes from SMS! -* Use standard browser authentication. - Use the YandexAuthBrowser application. - In this case you can use any available Yandex authentication - including SMS and QR codes. - For more details read the YandexAuthBrowser section. - - -***YandexAuthBrowser***
- -1. Download the package with YandexAuthBrowser. - -2. Choose a folder for the program. -* The access to folder must be __unrestricted__ for the program!
-The program writes into sub-folders, so it must have full unrestricted rights on the folder and sub-folders.
-C:\\, C:\Program Files and so on is not a good choice, the program will not work properly. -The better choice is "%userprofile%\AppData\Local\YandexAuthBrowser\" folder. -* The folder must be __secured__ from anyone else!
-The sub-folders contain cookies and other sensitive information -for easy access any of your data inside on your Yandex.Disk!
-Please keep program sub-folders from other's dirty hands! - -3. Unpack YandexAuthBrowser into selected secured folder. -Run the YandexAuthBrowser.
-Choose a port number to listen to incoming authentication request on.
-Set a password or use link to generate GUID as a new password.
-Also you can test the authentication process using Test button. - -4. Open `wdmrc.config` and edit `` tag -(add the tag if it's missing).
-Attributes:
- `Url`="http://localhost:``/" - address of the PC running YandexAuthBrowser application, - `port` means port number you selected on previous step in YandexAuthBrowser application.
- `Password`="`E86A63FC-9BF0-4351-AD51-A5F806BA38EF`" - password or GUID - you selected on previous step in YandexAuthBrowser application.
- The `E86A63FC-9BF0-4351-AD51-A5F806BA38EF` is an example of password, use your own please. - The privacy of your password prevents intruders from accessing your private data on Yandex.Disk!
- `CacheDir`="`full path to a secured folder`" - full path to a folder with cache files - containing browser cookies after authenticated access to your Yandex.Disk. - This folder must be placed in restricted location. - Only you and your BrowserAuthenticator must have access to the folder! - __If anyone gets files from the folder he or she may easily take your data on Yandex.Disk!__ - So keep the folder secured! - If you do not trust or if you do not want to keep cache, keep `CacheDir` attribute empty. - Also you may delete any sub-folder but 'runtimes' in the BrowserAuthenticator application's folder. - If you trust you PC enough, you can use a folder like "%userprofile%\AppData\Local\YandexAuthBrowser\" - for the cache. - Replace the %userprofile% with actual path. - -5. When you connect to a webdav: -* Fill the `login` field with short (John) or full (John@yandex.ru) name of the account.
-* Leave the `password` field empty (or put one or more spaces in case your program disallows empty passwords).
-If `password` is empty or space(s) only the `Password` from `` tag is used -to authenticate the request to the BrowserAuthenticator program. -If passwords are not matched the BrowserAuthenticator rejects the request.
-* Fill the `password` field with `Password` taken from BrowserAuthenticator application.
-In this case the `Password` from `` tag is not used. -Also you may remove `Password` attribute in `` tag. - -6. Press Win+R and run `shell:startup`. Put shortcut of the BrowserAuthenticator program in startup folder, -so the BrowserAuthenticator program will be started everytime you login the Windows. -The program stays in system tray until you reboot or manually exit it using menu on tray icon. +Атрибуты тэга ``: +* `Url`="http://`localhost`:``/" - URL, включающий адрес и порт ПК с запущенным приложением `BrowserAuthenticator`, + `port` - номер порта, заданный в коне Настроек приложения `BrowserAuthenticator` на предыдущем шаге инструкции. + `localhost` может быть заменен на любой иной IP, расположенный в любом конце света, главное чтобы к нему имел возможность подключиться эмулятор WebDAV. +* `Password` - пароль, тестовая строка, заданная в качестве пароля в окне Настроек приложения `BrowserAuthenticator` на предыдущем шаге инструкции. + Этот пароль следует хранить в секрете, т.к. зная пароль этот пароль и email можно получить доступ ко всем данным пользователя в облаке! + +* `CacheDir` - полный путь к папке, куда эмулятор WebDAW сохраняет полученную от приложения `BrowserAuthenticator` информацию для доступа к облаку. + Папка и сохраняемые данные должны храниться в секрете и быть недоступными посторонним, т.к. хранимой в папке информации достаточно для полного доступа к данным в облаке! + +На 1-м шаге инструкции создавалась папка для приложения `BrowserAuthenticator` где-то внутри `%userprofile%\AppData\Local`. +Внутри папки с приложением `BrowserAuthenticator` можно создать еще одну под кеш, и указать ее путь в атрибуте `CacheDir`. +Таким образом в безопасности и недоступности нужно будет хранить всего одну папку - ту, где установлено приложение `BrowserAuthenticator`. + +В случае, если кеш для эмулятора WebDAW с информацией от приложения `BrowserAuthenticator` не нужен или не желателен, +нужно удалить атрибут `CacheDir` или задать пустую строку вместо пути к папке. + +--- + +#### Using as Windows service        ENG + +Using as Windows service (**dotNet48 package only**). +* Run `cmd` with Administrator rights +* Then, for example, `wdmrc.exe --install wdmrc -p 801 --maxthreads 15`
+* `net start wdmrc` + +Using as Windows service (**dotNet7Win/dotNet8Win packages only**). + +* For install: Run `cmd` with Administrator rights, + change parameters and values, then run + `wdmrc.exe --install WebDavService --maxthreads 10 --maxconnections 20 --port 801 --cache-listing 600 --read-write-timeout-sec 600` + +* For uninstall: Run `cmd` with Administrator rights, type in and run + `wdmrc.exe --uninstall WebDavService` + +#### Установка сервисом Windows        RUS + +Установка сервисом Windows (**для пакета dotNet48**). +* Запустить `cmd` в режиме `Запуск от имени администратора` +* Затем ввести, например, `wdmrc.exe --install wdmrc -p 801 --maxthreads 15`
+* `net start wdmrc` + +Установка сервисом Windows (**для пакетов dotNet7Win/dotNet8Win**). + +* Установка: Запустить `cmd` в режиме `Запуск от имени администратора`, + затем откорректировать параметры и запустить + `wdmrc.exe --install WebDavService --maxthreads 10 --maxconnections 20 --port 801 --cache-listing 600 --read-write-timeout-sec 600` + +* Удаление: Запустить `cmd` в режиме `Запуск от имени администратора`, + затем запустить + `wdmrc.exe --uninstall WebDavService` + +--- + +### Features ***How to use encryption*** @@ -195,7 +637,7 @@ Using XTS AES-256 on-the-fly encryption/decryption * Set (en/de)cryption password * with `>>crypt passwd` special command
- or + or * Add `#` and separator string to your login: `login@mail.ru#_SEP_` * After your mail.ru password add separator string and password for encrypting: `MyLoginPassword_SEP_MyCryptingPassword` @@ -228,8 +670,8 @@ Parameters with spaces must be screened by quotes. ***Settings*** in `wdmrc.exe.config` * Logging
- ``
- It's standard [Apache log4net](https://logging.apache.org/log4net/) configurations, take a look for [examples](https://logging.apache.org/log4net/release/config-examples.html) + ``
+ It's standard [Apache log4net](https://logging.apache.org/log4net/) configurations, take a look for [examples](https://logging.apache.org/log4net/release/config-examples.html) Additionally you can use `protocol` and `port` properties taken from command-line parameters. * Default video resolution for generated m3u playlists ``
@@ -241,31 +683,31 @@ Parameters with spaces must be screened by quotes. `720p` ~ 1280 x 720 `1080p` ~ 1920 x 1080 * Default User-Agent
- ``
- Default user-agent for web requests to cloud. + ``
+ Default user-agent for web requests to cloud. * Special command prefix
- ``
- custom special command prefix instead of `>>`. Make possible to use special commands if client doesn't allow `>>`. + ``
+ custom special command prefix instead of `>>`. Make possible to use special commands if client doesn't allow `>>`. * Enable/disable WebDAV properties
- ``
- set `false` on properties you don't need to speedup listing on large catalogs / slow connections. + ``
+ set `false` on properties you don't need to speedup listing on large catalogs / slow connections. * 2 Factor Authentication
- At this time you can use - * `` - asks for authcode in application console - * `` - asks for authcode in GUI window (only for .NET Framework releases) - * - ``` - - - - - ``` - user must write authcode to file. For example, user `test@mail.ru` writes code to `d:\wdmrc_2FA_test@mail.ru`. - - - Be careful, this methods does not usable when application started as a service/daemon.
- You can make your own 2FA handlers inherited from `ITwoFaHandler` and put it in separate dll which name starts with `MailRuCloudApi.TwoFA` - + At this time you can use + * `` - asks for authcode in application console + * `` - asks for authcode in GUI window (only for .NET Framework releases) + * + ``` + + + + + ``` + user must write authcode to file. For example, user `test@mail.ru` writes code to `d:\wdmrc_2FA_test@mail.ru`. + + + Be careful, this methods does not usable when application started as a service/daemon.
+ You can make your own 2FA handlers inherited from `ITwoFaHandler` and put it in separate dll which name starts with `MailRuCloudApi.TwoFA` + Connect with (almost any) file manager that supports WebDAV using Basic authentication with no encryption and * your cloud.mail.ru email and password * or `anonymous` login if only public links list/download required ([WinSCP script example](https://github.com/yar229/WebDavMailRuCloud/issues/146#issuecomment-448978833)) @@ -275,20 +717,13 @@ Automatically split/join when uploading/downloading files larger than cloud allo [Russian FAQ](https://gist.github.com/yar229/4b702af114503546be1fe221bb098f27)
[geektimes.ru - Снова про WebDAV и Облако Mail.Ru](https://geektimes.ru/post/285520/)
[glashkoff.com - Как бесплатно подключить Облако Mail.Ru через WebDAV](https://glashkoff.com/blog/manual/webdav-cloudmailru/)
-[manjaro.ru - Облако Mail.Ru подключаем через эмулятор WebDav как сетевой диск](https://manjaro.ru/how-to/oblako-mailru-podklyuchaem-cherez-emulyator-webdav-kak-setevoy-disk.html)
- - -#### Windows +[manjaro.ru - Облако Mail.Ru подключаем через эмулятор WebDAV как сетевой диск](https://manjaro.ru/how-to/oblako-mailru-podklyuchaem-cherez-emulyator-webdav-kak-setevoy-disk.html)
-Using as windows service -* Run `cmd` with Administrator rights -* Then, for example, `wdmrc.exe --install wdmrc -p 801 --maxthreads 15`
-* `net start wdmrc`
-Using from explorer requires enabled Basic Auth for WebDAV +Using from Windows Explorer requires enabled Basic Auth for WebDAV * Press Win+R, type `regedit`, click OK * HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\WebClient\Parameters * Right click on the BasicAuthLevel and click Modify @@ -297,7 +732,7 @@ Using as windows service
-Use as Windows disk +Use as Windows disk ``` net use ^disk^: http://^address^:^port^ ^your_mailru_password^ /USER:^your_mailru_email^ ``` @@ -412,4 +847,4 @@ Use any client supports webdav. - [Buy me a beer](https://www.donationalerts.com/r/yar229) + [A beer for YaR229](https://www.donationalerts.com/r/yar229) From 6af4393cd1f220cc63c4059cf895eade5f10d6e4 Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Sun, 3 Dec 2023 21:47:52 +0300 Subject: [PATCH 56/77] =?UTF-8?q?=D0=95=D1=89=D0=B5=20=D0=BF=D0=B0=D1=80?= =?UTF-8?q?=D0=B0=20=D0=BF=D1=83=D0=BD=D0=BA=D1=82=D0=BE=D0=B2=20=D0=BA=20?= =?UTF-8?q?=D1=81=D0=BF=D0=B8=D1=81=D0=BA=D1=83=20=D0=B8=D0=B7=D0=BC=D0=B5?= =?UTF-8?q?=D0=BD=D0=B5=D0=BD=D0=B8=D0=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- readme.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/readme.md b/readme.md index 1a0f104c..4b4ff224 100644 --- a/readme.md +++ b/readme.md @@ -26,6 +26,8 @@ The fork project Date: Sun, 3 Dec 2023 22:15:25 +0300 Subject: [PATCH 57/77] =?UTF-8?q?=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D0=B0?= =?UTF-8?q?=20=D1=81=20=D1=83=D1=81=D1=82=D0=B0=D0=BD=D0=BE=D0=B2=D0=BA?= =?UTF-8?q?=D0=BE=D0=B9=20=D1=81=D0=B5=D1=80=D0=B2=D0=B8=D1=81=D0=BE=D0=BC?= =?UTF-8?q?=20Windows=20=D0=B4=D0=BB=D1=8F=20.NET=208?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- WinServiceInstaller/MyServiceInstaller.cs | 2 +- WinServiceInstaller/ServiceConfigurator.cs | 10 +++++----- WinServiceInstaller/StubService.cs | 4 ++-- WinServiceInstaller/WinServiceInstaller.csproj | 10 +++++++++- 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/WinServiceInstaller/MyServiceInstaller.cs b/WinServiceInstaller/MyServiceInstaller.cs index 7fab0c11..1c1f524b 100644 --- a/WinServiceInstaller/MyServiceInstaller.cs +++ b/WinServiceInstaller/MyServiceInstaller.cs @@ -1,4 +1,4 @@ -#if NET48 || NET7_0_WINDOWS +#if NET48 || NET7_0_WINDOWS || NET8_0_WINDOWS using System.ComponentModel; using System.Configuration.Install; using System.ServiceProcess; diff --git a/WinServiceInstaller/ServiceConfigurator.cs b/WinServiceInstaller/ServiceConfigurator.cs index 94561d4b..a2591df1 100644 --- a/WinServiceInstaller/ServiceConfigurator.cs +++ b/WinServiceInstaller/ServiceConfigurator.cs @@ -1,12 +1,12 @@ using System; using System.Reflection; -#if NET7_0_WINDOWS +#if NET7_0_WINDOWS || NET8_0_WINDOWS using System.Text; using System.Diagnostics; using System.Security; #endif -#if NET48 || NET7_0_WINDOWS +#if NET48 || NET7_0_WINDOWS || NET8_0_WINDOWS using System.Configuration.Install; using System.ServiceProcess; using Microsoft.Win32; @@ -65,7 +65,7 @@ public void Install() cmd = string.Concat(cmd, " ", CommandLine); SetCommandLine(cmd); #endif -#if NET7_0_WINDOWS +#if NET7_0_WINDOWS || NET8_0_WINDOWS string exePath = GetExePath(); var serviceKey = GetRegistryKey(); @@ -160,7 +160,7 @@ public void Uninstall() SetCommandLine(cmd); ManagedInstallerClass.InstallHelper(new[] { "/u", cmd }); #endif -#if NET7_0_WINDOWS +#if NET7_0_WINDOWS || NET8_0_WINDOWS string exePath = GetExePath(); var serviceKey = GetRegistryKey(); @@ -205,7 +205,7 @@ private void SetCommandLine(string cmd) serviceKey.SetValue("ImagePath", cmd, Microsoft.Win32.RegistryValueKind.String); } -#if NET7_0_WINDOWS +#if NET7_0_WINDOWS || NET8_0_WINDOWS private static bool NeedWaitSc(string serviceName) { diff --git a/WinServiceInstaller/StubService.cs b/WinServiceInstaller/StubService.cs index 5b7edb77..b3a43458 100644 --- a/WinServiceInstaller/StubService.cs +++ b/WinServiceInstaller/StubService.cs @@ -1,4 +1,4 @@ -#if NET48 || NET7_0_WINDOWS +#if NET48 || NET7_0_WINDOWS || NET8_0_WINDOWS using System; using System.ServiceProcess; using System.Threading.Tasks; @@ -15,7 +15,7 @@ protected override void OnStart(string[] args) { FireStart?.Invoke(); }); - + } protected override void OnStop() diff --git a/WinServiceInstaller/WinServiceInstaller.csproj b/WinServiceInstaller/WinServiceInstaller.csproj index ea75c1e6..d4bbbe13 100644 --- a/WinServiceInstaller/WinServiceInstaller.csproj +++ b/WinServiceInstaller/WinServiceInstaller.csproj @@ -19,6 +19,9 @@ $(DefineConstants);NET7_0_WINDOWS + + $(DefineConstants);NET8_0_WINDOWS + $(DefineConstants);NETCOREAPP3_0 @@ -37,13 +40,18 @@ false + + none + false + + none false - + From 9fda1c90b15fb8e8bb7f44eff1d569af89e70afa Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Tue, 5 Dec 2023 15:55:12 +0300 Subject: [PATCH 58/77] =?UTF-8?q?=D0=AF=D0=BD=D0=B4=D0=B5=D0=BA=D1=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- readme.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/readme.md b/readme.md index 4b4ff224..d5947f69 100644 --- a/readme.md +++ b/readme.md @@ -43,6 +43,17 @@ The fork project Date: Sun, 10 Dec 2023 20:25:52 +0300 Subject: [PATCH 59/77] =?UTF-8?q?*=20=D0=A1=D0=B5=D1=80=D0=B2=D0=B5=D1=80?= =?UTF-8?q?=20=D0=AF=D0=BD=D0=B4=D0=B5=D0=BA=D1=81=D0=B0=20=D0=BF=D1=80?= =?UTF-8?q?=D0=B8=20=D0=B2=D1=85=D0=BE=D0=B4=D0=B5=20=D0=BF=D0=BE=20=D0=BB?= =?UTF-8?q?=D0=BE=D0=B3=D0=B8=D0=BD=D1=83=20=D0=BC=D0=BE=D0=B6=D0=B5=D1=82?= =?UTF-8?q?=20=D0=B7=D0=B0=D1=82=D1=80=D0=B5=D0=B1=D0=BE=D0=B2=D0=B0=D1=82?= =?UTF-8?q?=D1=8C=20=D0=B4=D0=BE=D0=BF=D0=BE=D0=BB=D0=BD=D0=B8=D1=82=D0=B5?= =?UTF-8?q?=D0=BB=D1=8C=D0=BD=D0=BE=D0=B5=20=D0=BF=D0=BE=D0=B4=D1=82=D0=B2?= =?UTF-8?q?=D0=B5=D1=80=D0=B6=D0=B4=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BA=D0=BE?= =?UTF-8?q?=D0=B4=D0=BE=D0=BC=20=D0=B8=D0=B7=20email=20=D0=B8=D0=BB=D0=B8?= =?UTF-8?q?=20=D0=A1=D0=9C=D0=A1,=20=20=20=D0=BF=D0=BE=D1=81=D0=BB=D0=B5?= =?UTF-8?q?=20=D1=8D=D1=82=D0=BE=D0=B3=D0=BE=20=D0=BE=D0=B1=D1=8B=D1=87?= =?UTF-8?q?=D0=BD=D0=BE=20=D0=BD=D0=B0=D1=87=D0=B8=D0=BD=D0=B0=D0=BB=D0=B8?= =?UTF-8?q?=D1=81=D1=8C=20=D1=82=D0=B0=D0=BD=D1=86=D1=8B=20=D1=81=20=D0=B1?= =?UTF-8?q?=D1=83=D0=B1=D0=BD=D0=BE=D0=BC.=20=D0=A2=D0=B5=D0=BF=D0=B5?= =?UTF-8?q?=D1=80=D1=8C=20=D0=B1=D1=83=D0=B1=D0=B5=D0=BD=20=D0=B2=D1=81?= =?UTF-8?q?=D1=82=D1=80=D0=BE=D0=B5=D0=BD.=20=20=20=D0=95=D1=81=D0=BB?= =?UTF-8?q?=D0=B8=20=D0=BD=D0=B5=20=D1=83=D1=81=D1=82=D0=B0=D0=BD=D0=BE?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D0=BD=20=D0=B7=D0=B0=D0=BF=D1=80=D0=B5=D1=82?= =?UTF-8?q?=20=D0=B8=D1=81=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7=D0=BE=D0=B2=D0=B0?= =?UTF-8?q?=D0=BD=D0=B8=D1=8F=20=D0=B1=D1=80=D0=B0=D1=83=D0=B7=D0=B5=D1=80?= =?UTF-8?q?=D0=B0=20=D0=B4=D0=BB=D1=8F=20=D0=B0=D1=83=D1=82=D0=B5=D0=BD?= =?UTF-8?q?=D1=82=D0=B8=D1=84=D0=B8=D0=BA=D0=B0=D1=86=D0=B8=D0=B8=20(?= =?UTF-8?q?=D0=B7=D0=B0=D0=BF=D1=80=D0=B5=D1=82=20=D0=BF=D0=BE=20=D0=B7?= =?UTF-8?q?=D0=BD=D0=B0=D0=BA=D1=83=20`!`=20=D0=BF=D0=B5=D1=80=D0=B5=D0=B4?= =?UTF-8?q?=20=D0=BB=D0=BE=D0=B3=D0=B8=D0=BD=D0=BE=D0=BC),=20=20=20=D0=B4?= =?UTF-8?q?=D0=B5=D0=BB=D0=B0=D0=B5=D1=82=D1=81=D1=8F=20=D0=B2=D1=85=D0=BE?= =?UTF-8?q?=D0=B4=20=D1=87=D0=B5=D1=80=D0=B5=D0=B7=20BrowserAuthenticator,?= =?UTF-8?q?=20=D0=BF=D0=B0=D1=80=D0=BE=D0=BB=D1=8C=20=D0=B4=D0=BB=D1=8F=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=B4=D0=BA=D0=BB=D1=8E=D1=87=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20=D0=BA=20BrowserAuthenticator=20=D0=B1=D0=B5=D1=80?= =?UTF-8?q?=D0=B5=D1=82=D1=81=D1=8F=20=D0=B8=D0=B7=20`wdmrc.config`.=20=20?= =?UTF-8?q?=20=D0=95=D1=81=D0=BB=D0=B8=20=D1=81=D0=B5=D1=80=D0=B2=D0=B5?= =?UTF-8?q?=D1=80=20=D0=AF=D0=BD=D0=B4=D0=B5=D0=BA=D1=81=D0=B0=20=D0=BE?= =?UTF-8?q?=D0=BA=D0=B0=D0=B7=D0=B0=D0=BB=D1=81=D1=8F=20=D1=83=D0=B4=D0=BE?= =?UTF-8?q?=D0=B2=D0=BB=D0=B5=D1=82=D0=B2=D0=BE=D1=80=D0=B5=D0=BD,=20?= =?UTF-8?q?=D1=82=D0=BE=20=D0=BF=D0=BE=D1=82=D0=BE=D0=BC=20=D0=B2=D1=81?= =?UTF-8?q?=D0=B5=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=D0=B5=D1=82=20?= =?UTF-8?q?=D1=82=D0=BE=D0=BB=D1=8C=D0=BA=D0=BE=20=D1=81=20=D0=BB=D0=BE?= =?UTF-8?q?=D0=B3=D0=B8=D0=BD=D0=BE=D0=BC=20=D0=B8=20=D0=BF=D0=B0=D1=80?= =?UTF-8?q?=D0=BE=D0=BB=D0=B5=D0=BC.=20*=20=D0=AD=D0=BC=D1=83=D0=BB=D1=8F?= =?UTF-8?q?=D1=82=D0=BE=D1=80=20WebDAV=20=D1=81=20=D0=B7=D0=B0=D0=BF=D1=80?= =?UTF-8?q?=D0=BE=D1=81=D0=BE=D0=BC=20=D0=BA=20BrowserAuthenticator=20?= =?UTF-8?q?=D1=82=D0=B5=D0=BF=D0=B5=D1=80=D1=8C=20=D0=BF=D0=B5=D1=80=D0=B5?= =?UTF-8?q?=D0=B4=D0=B0=D0=B5=D1=82=20=D0=B7=D0=B0=D0=B4=D0=B0=D0=BD=D0=BD?= =?UTF-8?q?=D1=8B=D0=B5=20=D0=B2=20`wdmrc.config`=20=D0=B7=D0=B0=D0=B3?= =?UTF-8?q?=D0=BE=D0=BB=D0=BE=D0=B2=D0=BA=D0=B8=20user-agent=20=D0=B8=20se?= =?UTF-8?q?c-ch-ua,=20=20=20=D0=BF=D0=BE=D0=B7=D0=B2=D0=BE=D0=BB=D1=8F?= =?UTF-8?q?=D1=8F=20BrowserAuthenticator=20=D0=BF=D0=BE=D0=BB=D0=BD=D0=BE?= =?UTF-8?q?=D1=81=D1=82=D1=8C=D1=8E=20=D0=BD=D0=B5=20=D0=BE=D1=82=D0=BB?= =?UTF-8?q?=D0=B8=D1=87=D0=B0=D1=82=D1=8C=D1=81=D1=8F=20=D0=BE=D1=82=20?= =?UTF-8?q?=D0=B1=D1=80=D0=B0=D1=83=D0=B7=D0=B5=D1=80=D0=B0,=20=D1=87?= =?UTF-8?q?=D1=82=D0=BE=20=D1=83=D0=BC=D0=B5=D0=BD=D1=8C=D1=88=D0=B0=D0=B5?= =?UTF-8?q?=D1=82=20=D0=B2=D0=B5=D1=80=D0=BE=D1=8F=D1=82=D0=BD=D0=BE=D1=81?= =?UTF-8?q?=D1=82=D1=8C=20=D0=BF=D0=BE=D0=B7=D0=B6=D0=B5=20=D1=81=D0=B5?= =?UTF-8?q?=D1=80=D0=B2=D0=B5=D1=80=D1=83=20=D0=B7=D0=B0=D1=85=D0=BE=D1=82?= =?UTF-8?q?=D0=B5=D1=82=D1=8C=20=D0=BF=D0=BE=D0=B2=D1=82=D0=BE=D1=80=D0=BD?= =?UTF-8?q?=D0=BE=D0=B5=20=20=20=D0=BF=D0=BE=D0=B4=D1=82=D0=B2=D0=B5=D1=80?= =?UTF-8?q?=D0=B6=D0=B4=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BF=D0=BE=20=D0=BA?= =?UTF-8?q?=D0=BE=D0=B4=D1=83=20=D0=B8=D0=B7=20email=20=D0=B8=D0=BB=D0=B8?= =?UTF-8?q?=20=D0=A1=D0=9C=D0=A1.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BrowserAuthenticator/AuthForm.cs | 26 ++++- BrowserAuthenticator/JsonResult.cs | 22 ++++ BrowserAuthenticator/ResidentForm.cs | 45 ++++---- .../MailRuCloudApi/Base/Credentials.cs | 41 ++++--- .../YadWeb/Requests/YadAuthPasswordRequest.cs | 15 ++- .../Base/Requests/Types/BrowserAppResult.cs | 20 ++++ MailRuCloud/MailRuCloudApi/Cloud.cs | 50 ++++++++- .../MailRuCloudApi/Extensions/Extensions.cs | 103 ++++++++++++------ WDMRC.Console/WDMRC.Console.csproj | 4 +- readme.md | 27 ++++- 10 files changed, 269 insertions(+), 84 deletions(-) diff --git a/BrowserAuthenticator/AuthForm.cs b/BrowserAuthenticator/AuthForm.cs index d6023508..7c285967 100644 --- a/BrowserAuthenticator/AuthForm.cs +++ b/BrowserAuthenticator/AuthForm.cs @@ -30,8 +30,9 @@ public partial class AuthForm : Form private string? _html; private List? _cookieList; private WebView2? _webView2; + private Dictionary _headers; - public AuthForm(string desiredLogin, string profile, bool manualCommit, + public AuthForm(Dictionary headers, string desiredLogin, string profile, bool manualCommit, BrowserAppResult response, bool isYandexCloud, bool isMailCloud) { InitializeComponent(); @@ -46,11 +47,12 @@ public AuthForm(string desiredLogin, string profile, bool manualCommit, _isYandexCloud = isYandexCloud; _isMailCloud = isMailCloud; _desiredLogin = desiredLogin; + _headers = headers; Text = $" WebDavMailRuCloud \x2022 : {_profile}"; StringBuilder sb = new StringBuilder(" WebDavMailRuCloud . " + " , .\r\n"); - if (string.IsNullOrWhiteSpace(desiredLogin)) + if (string.IsNullOrWhiteSpace(_desiredLogin)) { sb.Append(" ( email) " + " . " + @@ -59,9 +61,9 @@ public AuthForm(string desiredLogin, string profile, bool manualCommit, } else { - Text += $", : {desiredLogin}"; + Text += $", : {_desiredLogin}"; - sb.Append($" login ( email): {desiredLogin}. "); + sb.Append($" login ( email): {_desiredLogin}. "); if (_isYandexCloud || _isMailCloud) { sb.Append( @@ -139,6 +141,12 @@ private async Task InitializeAsync() _webView2.Dock = DockStyle.Fill; _webView2.CoreWebView2.NavigationCompleted += WebView_NavigationCompleted; + if (_headers.TryGetValue("user-agent", out string? userAgent) && !string.IsNullOrEmpty(userAgent)) + _webView2.CoreWebView2.Settings.UserAgent = userAgent; + + _webView2.CoreWebView2.AddWebResourceRequestedFilter("*", CoreWebView2WebResourceContext.All); + _webView2.CoreWebView2.WebResourceRequested += WebView_WebResourceRequested; + ShowWindowDelay.Enabled = true; @@ -151,6 +159,16 @@ private async Task InitializeAsync() _webView2.CoreWebView2.NavigateToString(Resources.start); } + private void WebView_WebResourceRequested(object? sender, CoreWebView2WebResourceRequestedEventArgs e) + { + foreach (var headerPair in _headers) + { + if (headerPair.Key.Equals("user-agent", StringComparison.InvariantCultureIgnoreCase)) + continue; + e.Request.Headers.SetHeader(headerPair.Key, headerPair.Value); + } + } + private void AuthForm_Load(object sender, EventArgs e) { // - , diff --git a/BrowserAuthenticator/JsonResult.cs b/BrowserAuthenticator/JsonResult.cs index 9145bf0a..6cd6e245 100644 --- a/BrowserAuthenticator/JsonResult.cs +++ b/BrowserAuthenticator/JsonResult.cs @@ -3,6 +3,28 @@ namespace BrowserAuthenticator; #pragma warning disable CA1507 // Use nameof to express symbol names + + +public class BrowserAppRequest +{ + [JsonProperty("login")] + public string? Login { get; set; } + + [JsonProperty("password")] + public string? Password { get; set; } + + [JsonProperty("user-agent")] + public string? UserAgent { get; set; } + + [JsonProperty("sec-ch-ua")] + public string? SecChUa { get; set; } + + public string Serialize() + { + return JsonConvert.SerializeObject(this); + } +} + public class BrowserAppResult { [JsonProperty("ErrorMessage")] diff --git a/BrowserAuthenticator/ResidentForm.cs b/BrowserAuthenticator/ResidentForm.cs index e11a5997..57ed4883 100644 --- a/BrowserAuthenticator/ResidentForm.cs +++ b/BrowserAuthenticator/ResidentForm.cs @@ -2,12 +2,7 @@ using System.Configuration; using System.Net; using System.Text; -using System.Text.RegularExpressions; - -/* - * Частично код взят отсюда: - * https://gist.github.com/define-private-public/d05bc52dd0bed1c4699d49e2737e80e7 - */ +using Newtonsoft.Json; namespace BrowserAuthenticator; @@ -16,14 +11,10 @@ public partial class ResidentForm : Form public static readonly string[] YandexDomains = { "yandex", "ya" }; public static readonly string[] MailDomains = { "mail", "inbox", "bk", "list", "vk", "internet" }; - [GeneratedRegex("https?://[^/]*/(?.*?)/(?.*?)/", RegexOptions.IgnoreCase)] - private static partial Regex CompiledUrlRegex(); - private static readonly Regex UrlRegex = CompiledUrlRegex(); - private HttpListener? Listener; private bool RunServer = false; private string PreviousPort; - public delegate void Execute(string desiredLogin, BrowserAppResult response); + public delegate void Execute(string login, BrowserAppResult response, Dictionary headers); public Execute AuthExecuteDelegate; private readonly int? SavedTop = null; private readonly int? SavedLeft = null; @@ -223,8 +214,8 @@ private void SaveConfig() // Get the current configuration file. Configuration config = - ConfigurationManager.OpenExeConfiguration( - ConfigurationUserLevel.None); + ConfigurationManager.OpenExeConfiguration( + ConfigurationUserLevel.None); config.AppSettings.Settings.Remove("port"); config.AppSettings.Settings.Remove("password"); @@ -286,10 +277,15 @@ private void TestButton_Click(object sender, EventArgs e) { SaveConfig(); BrowserAppResult response = new BrowserAppResult(); - OpenDialog(TestLogin.Text, response); + Dictionary headers = new Dictionary() + { + { "user-agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36"}, + { "sec-ch-ua", "\"Google Chrome\";v=\"119\", \"Chromium\";v=\"119\", \"Not?A_Brand\";v=\"24\""}, + }; + OpenDialog(TestLogin.Text, response, headers); } - private void OpenDialog(string desiredLogin, BrowserAppResult response) + private void OpenDialog(string desiredLogin, BrowserAppResult response, Dictionary headers) { bool isYandexCloud = false; bool isMailCloud = false; @@ -312,7 +308,7 @@ private void OpenDialog(string desiredLogin, BrowserAppResult response) //{ // new AuthForm( desiredLogin, response ).ShowDialog(); //}, null ); - new AuthForm(desiredLogin, profile, ManualCommit.Checked, response, isYandexCloud, isMailCloud).ShowDialog(); + new AuthForm(headers, desiredLogin, profile, ManualCommit.Checked, response, isYandexCloud, isMailCloud).ShowDialog(); if (response.Cookies != null) AuthenticationOkCounter++; @@ -340,10 +336,17 @@ public async Task HandleIncomingConnections() HttpListenerRequest req = ctx.Request; using HttpListenerResponse resp = ctx.Response; - var match = UrlRegex.Match(req.Url?.AbsoluteUri ?? ""); + using StreamReader reader = new StreamReader(req.InputStream); + var request = JsonConvert.DeserializeObject(reader.ReadToEnd()); + + var login = request?.Login; + var password = request?.Password; - var login = Uri.UnescapeDataString(match.Success ? match.Groups["login"].Value : string.Empty); - var password = Uri.UnescapeDataString(match.Success ? match.Groups["password"].Value : string.Empty); + Dictionary headers = new(); + if (request?.UserAgent is not null ) + headers.Add("user-agent", request.UserAgent); + if (request?.SecChUa is not null) + headers.Add("sec-ch-ua", request.SecChUa); BrowserAppResult response = new BrowserAppResult(); @@ -360,9 +363,9 @@ public async Task HandleIncomingConnections() _showBrowserLocker.Wait(); // Окно с браузером нужно открыть в потоке, обрабатывающем UI if (TestButton.InvokeRequired) - TestButton.Invoke(AuthExecuteDelegate, login, response); + TestButton.Invoke(AuthExecuteDelegate, login, response, headers); else - AuthExecuteDelegate(login, response); + AuthExecuteDelegate(login, response, headers); _showBrowserLocker.Release(); } diff --git a/MailRuCloud/MailRuCloudApi/Base/Credentials.cs b/MailRuCloud/MailRuCloudApi/Base/Credentials.cs index e1f70343..a4f2f893 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Credentials.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Credentials.cs @@ -4,6 +4,7 @@ using System.Net; using System.Net.Http; using System.Security.Authentication; +using System.Text; using System.Threading.Tasks; using Newtonsoft.Json; using YaR.Clouds.Base.Repos.MailRuCloud; @@ -32,6 +33,7 @@ public partial class Credentials : IBasicCredentials public bool CanCrypt => !string.IsNullOrEmpty(PasswordCrypt); public bool AuthenticationUsingBrowser { get; private set; } + public bool AuthenticationUsingBrowserDisabled { get; private set; } #region На текущий момент специфично только для Янднекс.Диска @@ -167,6 +169,7 @@ private void ParseLoginPassword(string login, string password) Login = Login.Remove(0, 1).Trim(); AuthenticationUsingBrowser = false; + AuthenticationUsingBrowserDisabled = browserAuthenticatorDisabled; if (browserAuthenticatorAsked && CloudType != CloudType.Mail) { @@ -293,7 +296,7 @@ private async Task GetBrowserCookiesAsync() { response = await MakeLogin().ConfigureAwait(false); } - catch (Exception e) when (e.OfType().Any()) + catch (Exception e) when (e.Contains()) { Logger.Error("Browser authentication failed! " + "Please check browser authentication component is running!"); @@ -302,7 +305,7 @@ private async Task GetBrowserCookiesAsync() } catch (Exception e) { - if (e.OfType() is AuthenticationException ae) + if (e.FirstOfType() is AuthenticationException ae) { string txt = string.Concat("Browser authentication failed! ", ae.Message); Logger.Error(txt); @@ -320,7 +323,8 @@ private async Task GetBrowserCookiesAsync() Logger.Info($"Browser authentication successful"); // Сохраняем новый куки, если задан путь для кеша - if (!string.IsNullOrEmpty(_settings.BrowserAuthenticatorCacheDir)) + if (!string.IsNullOrEmpty(_settings.BrowserAuthenticatorCacheDir) && + AuthenticationUsingBrowser) { string fileName = string.IsNullOrWhiteSpace(Login) ? "anonymous" : Login; string path = GetPath(_settings.BrowserAuthenticatorCacheDir, fileName); @@ -406,7 +410,7 @@ private static string GetPath(string folder, string fileName) /// Возвращает false, если обновление не прошло и надо отправить исключение. ///
/// - public bool Refresh() + public bool Refresh(bool forceBrowserAuthentication = false) { if (!string.IsNullOrEmpty(_settings.BrowserAuthenticatorCacheDir)) { @@ -423,7 +427,8 @@ public bool Refresh() Logger.Error($"Error deleting cookie file {path}: {ex.Message}"); } } - if (AuthenticationUsingBrowser) + if ((AuthenticationUsingBrowser || forceBrowserAuthentication) && + !AuthenticationUsingBrowserDisabled) { Protocol saveProtocol = Protocol; CloudType saveCloudType = CloudType; @@ -451,25 +456,33 @@ private static string GetNameOnly(string value) private async Task<(BrowserAppResult, string)> ConnectToBrowserApp() { string url = _settings.BrowserAuthenticatorUrl; - string password = string.IsNullOrWhiteSpace(Password) - ? _settings.BrowserAuthenticatorPassword - : Password; if (string.IsNullOrEmpty(url)) { - throw new AuthenticationException("Error connecting to browser authenticator application. " + + throw new AuthenticationException( + "Error connecting to browser authenticator application. " + "Check the BrowserAuthenticator is running and have correct port."); } using var client = new HttpClient { BaseAddress = new Uri(url) }; + var req = new BrowserAppRequest() + { + Login = Login, + Password = string.IsNullOrWhiteSpace(Password) || !AuthenticationUsingBrowser + ? _settings.BrowserAuthenticatorPassword + : Password, + UserAgent = _settings.UserAgent, + SecChUa = _settings.SecChUa, + }; var httpRequestMessage = new HttpRequestMessage { - Method = HttpMethod.Get, - RequestUri = new Uri($"/{Uri.EscapeDataString(Login)}/{Uri.EscapeDataString(password)}/", UriKind.Relative), + Method = HttpMethod.Post, + RequestUri = new Uri("/", UriKind.Relative), Headers = { { HttpRequestHeader.Accept.ToString(), "application/json" }, { HttpRequestHeader.ContentType.ToString(), "application/json" }, }, + Content = new StringContent(req.Serialize(), Encoding.UTF8, "application/json") }; client.Timeout = new TimeSpan(0, 5, 0); @@ -512,8 +525,10 @@ private async Task MakeLogin() Cookies.Add(cookie); } - // Если аутентификация прошла успешно, сохраняем результат в кеш в файл - if (!string.IsNullOrEmpty(_settings.BrowserAuthenticatorCacheDir)) + // Если аутентификация прошла успешно, сохраняем результат в кеш в файл, + // но только если запрошена аутентификация через браузер + if (!string.IsNullOrEmpty(_settings.BrowserAuthenticatorCacheDir) && + AuthenticationUsingBrowser) { string path = GetPath(_settings.BrowserAuthenticatorCacheDir, Login); diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadAuthPasswordRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadAuthPasswordRequest.cs index d9dd7ec3..8084e840 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadAuthPasswordRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YadAuthPasswordRequest.cs @@ -87,8 +87,10 @@ protected override RequestResponse DeserializeMess if (res.Result.State == "auth_challenge") throw new AuthenticationException( - "The account requires browser login with additional confirmation by SMS or QR code. " + - "Please use BrowserAuthenticator application for this account."); + "The account requires browser login with additional confirmation by SMS or QR code", + // Добавление исключение данного типа является признаком чтобы попробовать аутентификацию через + // BrowserAuthenticator, если нет явного запрета по наличию знака `!` перед логином. + new InvalidCredentialException("Use the BrowserAuthenticator application for this account please")); if (res.Result.Status == "error" && res.Result.Errors.Count > 0) @@ -96,15 +98,16 @@ protected override RequestResponse DeserializeMess if (res.Result.Errors[0] == "captcha.required") { throw new AuthenticationException( - "Authentication failed: captcha.required. " + - "Use your browser application for several login and logout operations " + - "until site stop asking for captcha during login."); + "Authentication failed: captcha.required", + // Добавление исключение данного типа является признаком чтобы попробовать аутентификацию через + // BrowserAuthenticator, если нет явного запрета по наличию знака `!` перед логином. + new InvalidCredentialException("Use the BrowserAuthenticator application for this account please")); } if (res.Result.Errors[0] == "password.not_matched") { throw new AuthenticationException( "Authentication failed: password.not_matched. " + - "The password you used to log in does not match with the main password of the account. " + + "The password used to log in does not match with the main password of the account. " + "Do not use 'Application Passwords' here, use the main account password only! " + "In case you a sure you have used the main password, try to renew the main password."); } diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/BrowserAppResult.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/BrowserAppResult.cs index 3ffb278e..98b9ad7c 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/BrowserAppResult.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/BrowserAppResult.cs @@ -3,6 +3,26 @@ namespace YaR.Clouds.Base.Requests.Types; +public class BrowserAppRequest +{ + [JsonProperty("login")] + public string Login { get; set; } + + [JsonProperty("password")] + public string Password { get; set; } + + [JsonProperty("user-agent")] + public string UserAgent { get; set; } + + [JsonProperty("sec-ch-ua")] + public string SecChUa { get; set; } + + public string Serialize() + { + return JsonConvert.SerializeObject(this); + } +} + public class BrowserAppResult { [JsonProperty("ErrorMessage")] diff --git a/MailRuCloud/MailRuCloudApi/Cloud.cs b/MailRuCloud/MailRuCloudApi/Cloud.cs index 2a86644f..80665287 100644 --- a/MailRuCloud/MailRuCloudApi/Cloud.cs +++ b/MailRuCloud/MailRuCloudApi/Cloud.cs @@ -72,8 +72,48 @@ public Cloud(CloudSettings settings, Credentials credentials) AccountInfo = RequestRepo.AccountInfo().Result ?? throw new AuthenticationException("The cloud server rejected the credentials provided"); } + catch (Exception e) when (!Credentials.AuthenticationUsingBrowser && + !Credentials.AuthenticationUsingBrowserDisabled && + e.Contains() && + e.Contains()) + { + Logger.Warn("Cloud server requested for additional authentication for login & password, " + + "browser authentication is not disabled, so going to ask the user to log in using BrowserAuthenticator."); + + try + { + if (!Credentials.Refresh(forceBrowserAuthentication: true)) + { + Logger.Warn("Credentials refreshing is failed"); + throw; + } + } + catch (Exception e2) when (e2.Contains()) + { + Exception ae = e2.FirstOfType(); + Logger.Error("Failed to refresh credentials"); + throw new AuthenticationException( + "The cloud server rejected the credentials provided. Then failed to refresh credentials.", ae); + } + + // Проверка результата + try + { + AccountInfo = RequestRepo.AccountInfo().Result + ?? throw new AuthenticationException("The cloud server rejected the credentials provided"); + } + catch (Exception e2) when (e2.Contains()) + { + Exception ae = e2.FirstOfType(); + Logger.Error("The server rejected the credentials provided"); + throw new AuthenticationException( + "The cloud server rejected the credentials provided. " + + "Credentials have been updated. " + + "Then the server rejected the credentials again. ", ae); + } + } catch (Exception e) when (Credentials.AuthenticationUsingBrowser && - e.OfType().Any()) + e.Contains()) { Logger.Warn("Refreshing credentials..."); @@ -85,9 +125,9 @@ public Cloud(CloudSettings settings, Credentials credentials) throw; } } - catch (Exception e2) when (e2.OfType().Any()) + catch (Exception e2) when (e2.Contains()) { - Exception ae = e2.OfType().FirstOrDefault(); + Exception ae = e2.FirstOfType(); Logger.Error("Failed to refresh credentials"); throw new AuthenticationException( "The cloud server rejected the credentials provided. Then failed to refresh credentials.", ae); @@ -99,9 +139,9 @@ public Cloud(CloudSettings settings, Credentials credentials) AccountInfo = RequestRepo.AccountInfo().Result ?? throw new AuthenticationException("The cloud server rejected the credentials provided"); } - catch (Exception e2) when (e2.OfType().Any()) + catch (Exception e2) when (e2.Contains()) { - Exception ae = e2.OfType().FirstOrDefault(); + Exception ae = e2.FirstOfType(); Logger.Error("The server rejected the credentials provided"); throw new AuthenticationException( "The cloud server rejected the credentials provided. " + diff --git a/MailRuCloud/MailRuCloudApi/Extensions/Extensions.cs b/MailRuCloud/MailRuCloudApi/Extensions/Extensions.cs index 136f9269..9562edba 100644 --- a/MailRuCloud/MailRuCloudApi/Extensions/Extensions.cs +++ b/MailRuCloud/MailRuCloudApi/Extensions/Extensions.cs @@ -182,26 +182,48 @@ public static IEnumerable OfType(this Exception exception) where T : Excep /// public static IEnumerable OfType(this Exception exception, bool throwIfNotNotFound = false) where T : Exception { - if (exception is AggregateException ae) - { - AggregateException flatten = ae.Flatten(); - IEnumerable result = flatten.InnerExceptions.OfType(); - if (throwIfNotNotFound && !result.Any()) - throw flatten; - return result; - } - else + bool found = false; + if (exception is not null) { - if (exception is T item) + if (exception is T ex1) { - T[] array = new T[] { item }; - return array; + found = true; + yield return ex1; } - if (throwIfNotNotFound) - throw exception; + if (exception is AggregateException ae) + { + for (int index = 0; index < ae.InnerExceptions.Count; index++) + { + Exception innerEx = ae.InnerExceptions[index]; + if (innerEx is T ex2) + { + found = true; + yield return ex2; + } + if (innerEx.InnerException is not null) + { + foreach (var item in innerEx.OfType(throwIfNotNotFound)) + { + found = true; + yield return item; + }; + } + } + } + else + if (exception.InnerException.InnerException is not null) + { + foreach (var item in exception.InnerException.OfType(throwIfNotNotFound)) + { + found = true; + yield return item; + }; + } } - return Enumerable.Empty(); + + if (!found && throwIfNotNotFound) + throw exception; } /// Finds first occurrence of exception of the requested type. @@ -232,24 +254,28 @@ public static T FirstOfType(this Exception exception) where T : Exception /// public static T FirstOfType(this Exception exception, bool throwIfNotNotFound = false) where T : Exception { + if (exception is null) + return null; + + if (exception is T ex1) + return ex1; + if (exception is AggregateException ae) { - AggregateException flatten = ae.Flatten(); - T result = (T)flatten.InnerExceptions.FirstOrDefault(x => x is T); - if (throwIfNotNotFound && result is null) - throw flatten; - return result; - } - else - { - if (exception is T item) + for (int index = 0; index < ae.InnerExceptions.Count; index++) { - return item; + Exception innerEx = ae.InnerExceptions[index]; + if (innerEx.FirstOfType() is T ex2) + return ex2; } - - if (throwIfNotNotFound) - throw exception; } + else + if (exception.InnerException.FirstOfType() is T ex3) + return ex3; + + if (throwIfNotNotFound) + throw exception; + return null; } @@ -263,13 +289,26 @@ public static T FirstOfType(this Exception exception, bool throwIfNotNotFound /// public static bool Contains(this Exception exception) where T : Exception { + if(exception is null ) + return false; + + if (exception is T) + return true; + if (exception is AggregateException ae) { - AggregateException flatten = ae.Flatten(); - if (flatten.InnerExceptions.Any(x => x is T)) - return true; + for (int index = 0; index < ae.InnerExceptions.Count; index++) + { + Exception innerEx = ae.InnerExceptions[index]; + if (innerEx.Contains()) + return true; + } } - return exception is T; + else + if (exception.InnerException.Contains()) + return true; + + return false; } } } diff --git a/WDMRC.Console/WDMRC.Console.csproj b/WDMRC.Console/WDMRC.Console.csproj index f2fe26c9..4f96fe2f 100644 --- a/WDMRC.Console/WDMRC.Console.csproj +++ b/WDMRC.Console/WDMRC.Console.csproj @@ -14,7 +14,7 @@ WebDAVCloudMailRu MIT License, Copyright (c) 2023 YaR229 and Contributors - WebDAV emulator for RU-clouds: Cloud.Mail.Ru " Disk.Yandex.Ru + WebDAV emulator for RU-clouds: Cloud.Mail.Ru & Disk.Yandex.Ru WebDAVCloudMailRu $(ReleaseVersion) $(ReleaseVersion) @@ -26,7 +26,7 @@ https://github.com/yar229/WebDavMailRuCloud readme.md False - WebDAV emulator for RU-clouds: Cloud.Mail.Ru " Disk.Yandex.Ru + WebDAV emulator for RU-clouds: Cloud.Mail.Ru & Disk.Yandex.Ru diff --git a/readme.md b/readme.md index d5947f69..a962eb03 100644 --- a/readme.md +++ b/readme.md @@ -15,9 +15,21 @@ The fork project --- + @ZZZConsulting: -Самые важные изменения с предыдущей версии: +Самые важные изменения последних версий: + +* Сервер Яндекса при входе по логину может затребовать дополнительное подтверждение кодом из email или СМС, + после этого обычно начинались танцы с бубном. Теперь бубен встроен. + Если не установлен запрет использования браузера для аутентификации (запрет по знаку `!` перед логином), + делается вход через BrowserAuthenticator, пароль для подключения к BrowserAuthenticator берется из `wdmrc.config`. + Если сервер Яндекса оказался удовлетворен, то потом все работает только с логином и паролем. +* Эмулятор WebDAV с запросом к BrowserAuthenticator теперь передает заданные в `wdmrc.config` заголовки user-agent и sec-ch-ua, + позволяя BrowserAuthenticator полностью не отличаться от браузера, что уменьшает вероятность позже серверу захотеть повторное + подтверждение по коду из email или СМС. + +--- * Поддержка .NET 8.0 (включая установку сервисом Windows). * Для одновременного использования обоих облаков Cloud.Mail.Ru и Disk.Yandex.Ru больше нет необходимости в установке двух отдельных экземпляров, облако определяется при подключении. @@ -31,6 +43,19 @@ The fork project Date: Sun, 10 Dec 2023 20:30:07 +0300 Subject: [PATCH 60/77] =?UTF-8?q?=D0=A3=D0=B2=D0=B5=D0=BB=D0=B8=D1=87?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B2=D0=B5=D1=80=D1=81=D0=B8=D0=B8?= =?UTF-8?q?=20=D0=B4=D0=BE=201.14.2.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Common.targets | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Common.targets b/Common.targets index f4ca8dee..203662c6 100644 --- a/Common.targets +++ b/Common.targets @@ -2,6 +2,6 @@ net8.0-windows;net7.0-windows;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0 latest - 1.14.2.0 + 1.14.2.1 \ No newline at end of file From 03d485be9007283ec736c1dd7f40e2c800198e10 Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Sun, 10 Dec 2023 21:18:16 +0300 Subject: [PATCH 61/77] =?UTF-8?q?=D0=9F=D0=BE=D0=BB=D0=B8=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=D0=BA=D0=B0=20readme.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 7z release.cmd | 2 +- readme.md | 12 +++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/7z release.cmd b/7z release.cmd index 0ba3cbc7..c43ee567 100644 --- a/7z release.cmd +++ b/7z release.cmd @@ -1,4 +1,4 @@ -set ver=1.14.2.0 +set ver=1.14.2.1 set options=-tzip -mx9 -r -sse -x!*.pdb -x!*dev* "C:\Program Files\7-Zip\7z.exe" a %options% "BrowserAuthenticator\bin\Release\BrowserAuthenticator-%ver%-net7.0-windows.zip" ".\BrowserAuthenticator\bin\Release\net7.0-windows\*" diff --git a/readme.md b/readme.md index a962eb03..a2b2a8e3 100644 --- a/readme.md +++ b/readme.md @@ -20,14 +20,16 @@ The fork project Date: Sat, 28 Sep 2024 20:14:03 +0300 Subject: [PATCH 62/77] =?UTF-8?q?1)=20=D0=98=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BB=D0=B5=D0=BD=D0=BE=20=D1=83=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5,=20=D0=BA=D0=BE=D1=82=D0=BE=D1=80=D0=BE=D0=B5=20?= =?UTF-8?q?=D0=BF=D0=B5=D1=80=D0=B5=D1=81=D1=82=D0=B0=D0=BB=D0=BE=20=D1=80?= =?UTF-8?q?=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=D1=82=D1=8C=20=D0=BA=D0=B0=D0=BA?= =?UTF-8?q?=D0=BE=D0=B5-=D1=82=D0=BE=20=D0=B2=D1=80=D0=B5=D0=BC=D1=8F=20?= =?UTF-8?q?=D0=BD=D0=B0=D0=B7=D0=B0=D0=B4.=20=D0=98=D0=B7-=D0=B7=D0=B0=20?= =?UTF-8?q?=D0=BD=D0=B5=D0=B3=D0=BE=20=D0=BF=D1=80=D0=B8=D1=88=D0=BB=D0=BE?= =?UTF-8?q?=D1=81=D1=8C=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D1=82=D1=8C?= =?UTF-8?q?=20=D0=BD=D0=BE=D0=B2=D1=83=D1=8E=20(V2)=20=D0=B2=D0=B5=D1=80?= =?UTF-8?q?=D1=81=D0=B8=D1=8E=20=D0=BE=D0=BF=D0=B5=D1=80=D0=B0=D1=86=D0=B8?= =?UTF-8?q?=D0=B9.=202)=20=D0=97=D0=BD=D0=B0=D1=87=D0=B8=D1=82=D0=B5=D0=BB?= =?UTF-8?q?=D1=8C=D0=BD=D0=BE=20=D0=BF=D0=B5=D1=80=D0=B5=D1=80=D0=B0=D0=B1?= =?UTF-8?q?=D0=BE=D1=82=D0=B0=D0=BB=D0=BE=20=D0=BA=D0=B5=D1=88=D0=B8=D1=80?= =?UTF-8?q?=D0=BE=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5.=20=D0=92=20=D1=87=D0=B0?= =?UTF-8?q?=D1=81=D1=82=D0=BD=D0=BE=D1=81=D1=82=D0=B8,=20=D0=BF=D0=BE?= =?UTF-8?q?=D1=81=D0=BB=D0=B5=20=D1=83=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D1=8F=20=D1=84=D0=B0=D0=B9=D0=BB=D0=B0=20=D0=B8=D0=B7=20=D0=BF?= =?UTF-8?q?=D0=B0=D0=BF=D0=BA=D0=B8,=20=D0=BA=D0=B5=D1=88=20=D0=BF=D0=B0?= =?UTF-8?q?=D0=BF=D0=BA=D0=B8=20=D0=BD=D0=B5=20=D1=81=D0=B1=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D1=8B=D0=B2=D0=B0=D0=B5=D1=82=D1=81=D1=8F=20=D0=B8=20?= =?UTF-8?q?=D0=BD=D0=B5=D1=82=20=D0=BD=D0=B5=D0=BE=D0=B1=D1=85=D0=BE=D0=B4?= =?UTF-8?q?=D0=B8=D0=BC=D0=BE=D1=81=D1=82=D0=B8=20=D0=B7=D0=B0=D0=B3=D1=80?= =?UTF-8?q?=D1=83=D0=B6=D0=B0=D1=82=D1=8C=20=D1=81=20=D1=81=D0=B5=D1=80?= =?UTF-8?q?=D0=B2=D0=B5=D1=80=D0=B0=20=D0=B2=D1=81=D0=B5=20=D1=82=D1=8B?= =?UTF-8?q?=D1=81=D1=8F=D1=87=D0=B8=20entry=20=D0=B1=D0=BE=D0=BB=D1=8C?= =?UTF-8?q?=D1=88=D0=BE=D0=B9=20=D0=BF=D0=B0=D0=BF=D0=BA=D0=B8,=20=D0=B7?= =?UTF-8?q?=D0=B0=D1=82=D1=80=D0=B0=D1=87=D0=B8=D0=B2=D0=B0=D1=8F=20=D0=BF?= =?UTF-8?q?=D0=BE=20=D0=BC=D0=B8=D0=BD=D1=83=D1=82=D0=B5=20=D0=BF=D0=BE?= =?UTF-8?q?=D1=81=D0=BB=D0=B5=20=D0=BE=D0=BF=D0=B5=D1=80=D0=B0=D1=86=D0=B8?= =?UTF-8?q?=D0=B8=20=D1=81=20=D0=BA=D0=B0=D0=B6=D0=B4=D1=8B=D0=BC=20=D1=84?= =?UTF-8?q?=D0=B0=D0=B9=D0=BB=D0=BE=D0=BC.=20=D0=9E=D0=B1=D1=80=D0=B0?= =?UTF-8?q?=D1=82=D0=BD=D0=BE=D0=B9=20=D1=81=D1=82=D0=BE=D1=80=D0=BE=D0=BD?= =?UTF-8?q?=D0=BE=D0=B9=20=D1=81=D1=82=D0=B0=D0=BB=D0=BE=20=D1=85=D1=80?= =?UTF-8?q?=D0=B0=D0=BD=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=B2=20=D0=BA=D0=B5?= =?UTF-8?q?=D1=88=D0=B5=20=D1=84=D0=B0=D0=BD=D1=82=D0=BE=D0=BC=D0=BE=D0=B2?= =?UTF-8?q?.=203)=20=D0=A3=D0=BB=D1=83=D1=87=D1=88=D0=B5=D0=BD=D0=B0=20?= =?UTF-8?q?=D0=BF=D1=80=D0=BE=D0=B2=D0=B5=D1=80=D0=BA=D0=B0=20=D0=B2=D0=BD?= =?UTF-8?q?=D0=B5=D1=88=D0=BD=D0=B8=D1=85=20=D0=BE=D0=BF=D0=B5=D1=80=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D0=B9=20=D0=BD=D0=B0=20=D0=94=D0=B8=D1=81=D0=BA?= =?UTF-8?q?=D0=B5,=20=D0=BA=D0=BE=D1=82=D0=BE=D1=80=D1=8B=D0=B5=20=D0=B8?= =?UTF-8?q?=D0=B4=D1=83=D1=82=20=D0=BC=D0=B8=D0=BC=D0=BE=20=D0=B4=D0=B0?= =?UTF-8?q?=D0=BD=D0=BD=D0=BE=D0=B3=D0=BE=20=D1=81=D0=B5=D1=80=D0=B2=D0=B8?= =?UTF-8?q?=D1=81=D0=B0.=20=D0=98=D1=81=D0=BF=D0=BE=D0=BB=D1=8C=D0=B7?= =?UTF-8?q?=D1=83=D1=8E=D1=82=D1=81=D1=8F=20=D1=81=D1=87=D0=B5=D1=82=D1=87?= =?UTF-8?q?=D0=B8=D0=BA=D0=B8=20=D0=B6=D1=83=D1=80=D0=BD=D0=B0=D0=BB=D0=B0?= =?UTF-8?q?=20=D0=BE=D0=BF=D0=B5=D1=80=D0=B0=D1=86=D0=B8=D0=B9=20=D0=94?= =?UTF-8?q?=D0=B8=D1=81=D0=BA=D0=B0.=20=D0=9F=D1=80=D0=B8=20=D0=BE=D0=B1?= =?UTF-8?q?=D0=BD=D0=B0=D1=80=D1=83=D0=B6=D0=B5=D0=BD=D0=B8=D0=B8=20=D0=B4?= =?UTF-8?q?=D0=B5=D0=B9=D1=81=D1=82=D0=B2=D0=B8=D0=B9,=20=D0=BA=D0=BE?= =?UTF-8?q?=D1=82=D0=BE=D1=80=D1=8B=D0=B5=20=D0=BF=D1=80=D0=BE=D1=88=D0=BB?= =?UTF-8?q?=D0=B8=20=D0=BC=D0=B8=D0=BC=D0=BE=20=D0=B4=D0=B0=D0=BD=D0=BD?= =?UTF-8?q?=D0=BE=D0=B3=D0=BE=20=D1=81=D0=B5=D1=80=D0=B2=D0=B8=D1=81=D0=B0?= =?UTF-8?q?,=20=D0=BA=D0=B5=D1=88=20=D0=BF=D0=BE=D0=BB=D0=BD=D0=BE=D1=81?= =?UTF-8?q?=D1=82=D1=8C=D1=8E=20=D1=81=D0=B1=D1=80=D0=B0=D1=81=D1=8B=D0=B2?= =?UTF-8?q?=D0=B0=D0=B5=D1=82=D1=81=D1=8F.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../BrowserAuthenticator.csproj | 2 +- LICENSE.txt | 2 +- .../Repos/YandexDisk/YadWeb/Models/BaseV2.cs | 110 ++++ .../YandexDisk/YadWeb/Models/DeleteV2.cs | 21 + .../YadWeb/Models/JournalCounters.cs | 116 ++++ .../YadWeb/Models/OperationStatus.cs | 2 +- .../YadWeb/Models/OperationStatusV2.cs | 26 + .../YadWeb/Requests/YaDCommonRequest.cs | 21 +- .../YadWeb/Requests/YaDCommonRequestV2.cs | 133 +++++ .../YandexDisk/YadWeb/YadWebRequestRepo.cs | 38 +- .../YandexDisk/YadWeb/YadWebRequestRepo2.cs | 95 ++- .../Base/Requests/BaseRequest.cs | 10 +- .../Base/Requests/Types/CheckUpInfo.cs | 180 +++++- .../Base/Streams/UploadStreamHttpClient.cs | 48 +- MailRuCloud/MailRuCloudApi/Cloud.cs | 342 +++++++---- .../MailRuCloudApi/Common/EntryCache.cs | 534 ++++++++++++----- .../MailRuCloudApi/Extensions/Extensions.cs | 547 +++++++++--------- .../MailRuCloudApi/Links/LinkManager.cs | 2 +- NWebDav/NWebDav.Server/NWebDav.Server.csproj | 64 ++ WDMRC.Console/WDMRC.Console.csproj | 6 +- WebDAV.Uploader/UploadStub.cs | 14 +- WebDavMailRuCloudStore/Extensions.cs | 97 ++-- .../StoreBase/LocalStoreItem.cs | 14 +- 23 files changed, 1763 insertions(+), 661 deletions(-) create mode 100644 MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/BaseV2.cs create mode 100644 MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/DeleteV2.cs create mode 100644 MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/JournalCounters.cs create mode 100644 MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/OperationStatusV2.cs create mode 100644 MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YaDCommonRequestV2.cs diff --git a/BrowserAuthenticator/BrowserAuthenticator.csproj b/BrowserAuthenticator/BrowserAuthenticator.csproj index 79a6c3f7..f1c731e2 100644 --- a/BrowserAuthenticator/BrowserAuthenticator.csproj +++ b/BrowserAuthenticator/BrowserAuthenticator.csproj @@ -16,7 +16,7 @@ False YaR229 and Contributors BrowserAuthenticator for WebDAV emulator - MIT License, Copyright (c) 2023 YaR229 and Contributors + MIT License, Copyright (c) 2024 YaR229 and Contributors cloud.ico https://github.com/ZZZConsulting/WebDavMailRuCloud LICENSE.txt diff --git a/LICENSE.txt b/LICENSE.txt index 960cf2ff..77931e6a 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 YaR +Copyright (c) 2024 YaR Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/BaseV2.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/BaseV2.cs new file mode 100644 index 00000000..83a32c4d --- /dev/null +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/BaseV2.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace YaR.Clouds.Base.Repos.YandexDisk.YadWeb.Models; + +public class YadPostDataV2 +{ + public YadRequestV2 Request { get; set; } + + public YadPostDataV2(string sk, string idClient) + { + Request = new YadRequestV2() + { + Sk = sk, + IdClient = idClient + }; + } + + public byte[] CreateHttpContent() + => System.Text.Encoding.UTF8.GetBytes( + JsonConvert.SerializeObject( + Request)); +} + +public abstract class YadModelV2 +{ + public string APIMethod { get; set; } + public YadRequestV2Parameter RequestParameter { get; set; } + + public Action Deserialize = null; + + public List Errors { get; set; } = []; + internal object ResultObject { get; set; } + internal Type ResultType { get; set; } + + public string SourceJsonForDebug { get; set; } +} + + +public class YadResponseV2ErrorDescription +{ + [JsonProperty("type")] + public string Type { get; set; } + + [JsonProperty("statusCode")] + public int StatusCode { get; set; } + + [JsonProperty("code")] + public int Code { get; set; } + + [JsonProperty("title")] + public string Title { get; set; } +} + +// [{"error":{"type":"mpfs","statusCode":404,"code":77,"title":"Wrong path"}}] +public class YadResponseV2Error +{ + [JsonProperty("error")] + public YadResponseV2ErrorDescription Error { get; set; } +} + +public class YadOperationStatusResultV2 : YadResponseV2Error +{ + [JsonProperty("status")] + public string Status { get; set; } + + [JsonProperty("type")] + public string Type { get; set; } + + [JsonProperty("state")] + public string State { get; set; } + + [JsonProperty("at_version")] + public long AtVersion { get; set; } + + [JsonProperty("oid")] + public string Oid { get; set; } +} + +public class YadRequestV2 +{ + [JsonProperty("sk")] + public string Sk { get; set; } + + [JsonProperty("connection_id")] + public string IdClient { get; set; } + + [JsonProperty("apiMethod")] + public string APIMethod { get; set; } + + [JsonProperty("requestParams")] + public YadRequestV2Parameter RequestParameter { get; set; } +} + +public class YadRequestV2Parameter +{ +} + +public class YadRequestV2ParameterOperation : YadRequestV2Parameter +{ + [JsonProperty("operations")] + public List Operations { get; set; } +} + +public class YadRequestV2Operation +{ + [JsonProperty("src")] + public string Src { get; set; } +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/DeleteV2.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/DeleteV2.cs new file mode 100644 index 00000000..78ae0df3 --- /dev/null +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/DeleteV2.cs @@ -0,0 +1,21 @@ +using System.Collections.Generic; +using System.Linq; + +namespace YaR.Clouds.Base.Repos.YandexDisk.YadWeb.Models; + +class YadDeletePostModelV2 : YadModelV2 +{ + public YadDeletePostModelV2(string path) + { + APIMethod = "mpfs/bulk-async-delete"; + ResultType = typeof(List); + RequestParameter = new YadRequestV2ParameterOperation() + { + Operations = [new YadRequestV2Operation() { Src = WebDavPath.Combine("/disk", path) }] + }; + } + + /// Oid of operation. + public string Result + => ((List)ResultObject)?.FirstOrDefault()?.Oid; +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/JournalCounters.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/JournalCounters.cs new file mode 100644 index 00000000..d654eae2 --- /dev/null +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/JournalCounters.cs @@ -0,0 +1,116 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace YaR.Clouds.Base.Repos.YandexDisk.YadWeb.Models; + +internal class JournalCountersV2 : YadModelV2 +{ + public JournalCountersV2() + { + APIMethod = "cloud/virtual-disk-journal-counters"; + ResultType = typeof(YadJournalCountersV2); + RequestParameter = new YadRequestV2JournalCounters() + { + Hash = null, + Offset = 0, + Limit = 40, + LimitPerGroup = 20, + Text = "", + EventType = "", + }; + } + + public YadJournalCountersV2 Result + => (YadJournalCountersV2)ResultObject; +} + +public class YadRequestV2JournalCounters : YadRequestV2Parameter +{ + /* + "requestParams": { + "vd_hash": null, + "page_load_date": "2024-09-24T20:59:59.999Z", + "offset": 0, + "text": "", + "limit": 40, + "event_type": "", + "limit_per_group": 20, + "counters_date": "2024-09-24T16:51:26.720Z", + "tz_offset": -10800000 + } + */ + [JsonProperty("vd_hash")] + public string Hash { get; set; } + + //[JsonProperty("page_load_date")] + //public string Date { get; set; } + + [JsonProperty("offset")] + public int Offset { get; set; } + + [JsonProperty("limit_per_group")] + public int LimitPerGroup { get; set; } + + [JsonProperty("limit")] + public int Limit { get; set; } + + [JsonProperty("string")] + public string Text { get; set; } + + [JsonProperty("event_type")] + public string EventType { get; set; } + + //[JsonProperty("counters_date")] + //public string CountersDate { get; set; } + + //[JsonProperty("tz_offset")] + //public long TzOffset { get; set; } +} + +public class YadJournalCountersV2 +{ + /* + { + "eventTypes": { + "fs-rm": 123, + "share-remove-invite": 123, + "album-change-cover": 123, + "fs-set-public": 123, + "space-promo-enlarge": 123, + "fs-store-download": 123, + "share-change-rights": 123, + "album-create": 123, + "share-invite-user": 123, + "album-items-append": 123, + "space-promo-reduce": 123, + "fs-store-update": 123, + "fs-trash-drop": 123, + "fs-set-private": 123, + "album-change-title": 123, + "fs-move": 123, + "fs-store": 123, + "fs-mkdir": 123, + "album-change-publicity": 123, + "fs-trash-drop-all": 123, + "share-leave-group": 123, + "fs-rename": 123, + "fs-store-photounlim": 123, + "album-remove": 123, + "fs-trash-restore": 123, + "fs-trash-append": 123, + "share-activate-invite": 123 + }, + "platforms": { + "web": 123, + "andr": 123, + "rest": 123, + "win": 123 + } + } + */ + [JsonProperty("eventTypes")] + public Dictionary EventTypes { get; set; } + + [JsonProperty("platforms")] + public Dictionary Platforms { get; set; } +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/OperationStatus.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/OperationStatus.cs index 7a3a90ec..3c02e98b 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/OperationStatus.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/OperationStatus.cs @@ -23,7 +23,7 @@ public override IEnumerable> ToKvp(int index) } - internal class YadOperationStatusData : YadModelDataBase + public class YadOperationStatusData : YadModelDataBase { [JsonProperty("status")] public string Status { get; set; } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/OperationStatusV2.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/OperationStatusV2.cs new file mode 100644 index 00000000..00e48cf4 --- /dev/null +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/OperationStatusV2.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using Newtonsoft.Json; + +namespace YaR.Clouds.Base.Repos.YandexDisk.YadWeb.Models; + +internal class YadOperationStatusV2 : YadModelV2 +{ + public YadOperationStatusV2(string opId) + { + APIMethod = "mpfs/bulk-operation-status"; + ResultType = typeof(Dictionary); + RequestParameter = new YadRequestV2ParameterOids() + { + Oids = [opId] + }; + } + + public Dictionary Result + => (Dictionary)ResultObject; +} + +public class YadRequestV2ParameterOids : YadRequestV2Parameter +{ + [JsonProperty("oids")] + public List Oids { get; set; } +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YaDCommonRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YaDCommonRequest.cs index cf9b352a..5b151769 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YaDCommonRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YaDCommonRequest.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Collections.Specialized; using System.IO; using System.Linq; @@ -15,7 +16,7 @@ class YaDCommonRequest : BaseRequestJson private readonly YadPostData _postData = new(); - private readonly List _outData = new(); + private readonly List _outData = []; private YadWebAuth YadAuth { get; } @@ -39,20 +40,23 @@ protected override byte[] CreateHttpContent() return _postData.CreateHttpContent(); } - public YaDCommonRequest With(T model, out TOut resOUt) + public YaDCommonRequest With(T model, out TOut resOut) where T : YadPostModel where TOut : YadResponseModel, new() { _postData.Models.Add(model); - _outData.Add(resOUt = new TOut()); + _outData.Add(resOut = new TOut()); return this; } protected override string RelationalUri - => string.Concat("/models/?_m=", _postData.Models - .Select(m => m.Name) - .Aggregate((current, next) => current + "," + next)); + => string.Concat( + "/models/?_m=", + _postData + .Models + .Select(m => m.Name) + .Aggregate((current, next) => current + "," + next)); protected override RequestResponse DeserializeMessage( NameValueCollection responseHeaders, System.IO.Stream stream) @@ -65,8 +69,7 @@ protected override RequestResponse DeserializeMessage( var msg = new RequestResponse { Ok = true, - Result = JsonConvert.DeserializeObject( - text, new KnownYadModelConverter(_outData)) + Result = JsonConvert.DeserializeObject(text, new KnownYadModelConverter(_outData)) }; if (YadAuth.Credentials.AuthenticationUsingBrowser) diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YaDCommonRequestV2.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YaDCommonRequestV2.cs new file mode 100644 index 00000000..7426ecf6 --- /dev/null +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YaDCommonRequestV2.cs @@ -0,0 +1,133 @@ +using System; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.IO; +using System.Linq; +using System.Net; +using Newtonsoft.Json; +using YaR.Clouds.Base.Repos.YandexDisk.YadWeb.Models; +using YaR.Clouds.Base.Requests; + +namespace YaR.Clouds.Base.Repos.YandexDisk.YadWeb.Requests +{ + class YaDCommonRequestV2 : BaseRequestJson + { + private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(YaDCommonRequest)); + + private YadModelV2 Model { get; set; } + private YadPostDataV2 Request { get; } + private YadWebAuth YadAuth { get; } + + public YaDCommonRequestV2(HttpCommonSettings settings, YadWebAuth auth) : base(settings, auth) + { + YadAuth = auth; + Request = new YadPostDataV2(YadAuth.DiskSk, YadAuth.Uuid); + } + + protected override HttpWebRequest CreateRequest(string baseDomain = null) + { + var request = base.CreateRequest("https://disk.yandex.ru"); + request.Referer = "https://disk.yandex.ru/client/disk"; + return request; + } + + protected override byte[] CreateHttpContent() => Request.CreateHttpContent(); + + public YaDCommonRequestV2 With(T model) + where T : YadModelV2 + { + Model = model; + Request.Request.APIMethod = model.APIMethod; + Request.Request.RequestParameter = model.RequestParameter; + + return this; + } + + public YaDCommonRequestV2 With(T model, out T sameModel) + where T : YadModelV2 + { + Model = model; + sameModel = model; + Request.Request.APIMethod = model.APIMethod; + Request.Request.RequestParameter = model.RequestParameter; + + return this; + } + + protected override string RelationalUri => "/models-v2?_m=" + Request.Request.APIMethod; + + protected override RequestResponse DeserializeMessage( + NameValueCollection responseHeaders, System.IO.Stream stream) + { + using var sr = new StreamReader(stream); + + string text = sr.ReadToEnd(); + //Logger.Debug(text); + + Model.Deserialize ??= new Action((string text) => + { +//#if NET48 +// bool multiple = text.StartsWith("["); +//#else +// bool multiple = text.StartsWith('['); +//#endif + Model.ResultObject = null; + + if (text.StartsWith("[{\"error\"")) + { + Model.Errors = JsonConvert.DeserializeObject>(text); + } + else + if (text.StartsWith("{[\"object Object\"]")) + { + var errorObject = JsonConvert.DeserializeObject>(text); + Model.Errors = errorObject.Values.ToList(); + } + else + //if (multiple) + //{ + // var list = JsonConvert.DeserializeObject>(text); + // Model.ResultObject = []; + // int index = 0; + // foreach (var item in list) + // { + // Model.ResultObject.Add(index.ToString(), item); + // index++; + // } + //} + //else + //if (text.StartsWith("{\"")) + //{ + // Model.ResultObject = JsonConvert.DeserializeObject>(text); + //} + //else + { + Model.ResultObject = JsonConvert.DeserializeObject(text, Model.ResultType); + } + + //if ((Model.Errors?.Count ?? 0) == 0 && + // (Model.ResultObject?.Values?.Any(x => x.Error is not null) ?? false)) + //{ + // Model.Errors = Model.ResultObject.Values.Where(x => x.Error is not null).Cast().ToList(); + //} + }); + + Model.Deserialize(text); + Model.SourceJsonForDebug = text; + + if (Model.Errors is not null && Model.Errors.Count == 0) + Model.Errors = null; + + var error = Model.Errors?.FirstOrDefault()?.Error; + var msg = new RequestResponse + { + Ok = Model.Errors is null, + Result = Model, + Description = $"{Model.APIMethod} -> {error?.Title}", + ErrorCode = error?.StatusCode, + }; + + return msg; + } + } +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebRequestRepo.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebRequestRepo.cs index d3471834..ae97d534 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebRequestRepo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebRequestRepo.cs @@ -161,6 +161,9 @@ public ICloudHasher GetHasher() hash?.HashMd5.Value ?? string.Empty), out YadResponseModel itemInfo) .MakeRequestAsync(_connectionLimiter).Result; + + if (itemInfo.Error is not null) + throw new Exception($"Error: {itemInfo.Error.Id}, {itemInfo.Error.Message})"); var url = itemInfo.Data.UploadUrl; var request = new HttpRequestMessage @@ -172,8 +175,16 @@ public ICloudHasher GetHasher() request.Headers.Add("Accept", "*/*"); request.Headers.TryAddWithoutValidation("User-Agent", HttpSettings.UserAgent); + /* + * Пробуем разные варианты решения ошибки вида + * Sent 3014656 request content bytes, but Content-Length promised 117811349. + * в методе DoUpload в строке var responseMessage = await client.SendAsync(request); + * Включаем Chunked и выключаем установки ContentLength, пусть framework сам считает. + */ + request.Headers.TransferEncodingChunked = true; + request.Content = content; - request.Content.Headers.ContentLength = file.OriginalSize; + //request.Content.Headers.ContentLength = file.OriginalSize; return (request, itemInfo?.Data?.OpId); } @@ -473,14 +484,29 @@ public async Task Remove(string fullPath) //var req = await new YadDeleteRequest(HttpSettings, (YadWebAuth)Authenticator, fullPath) // .MakeRequestAsync(_connectionLimiter); - await new YaDCommonRequest(HttpSettings, (YadWebAuth)Auth) - .With(new YadDeletePostModel(fullPath), - out YadResponseModel itemInfo) + //await new YaDCommonRequest(HttpSettings, (YadWebAuth)Auth) + // .With(new YadDeletePostModel(fullPath), + // out YadResponseModel itemInfo) + // .MakeRequestAsync(_connectionLimiter); + await new YaDCommonRequestV2(HttpSettings, (YadWebAuth)Auth) + .With(new YadDeletePostModelV2(fullPath), out var itemInfo) .MakeRequestAsync(_connectionLimiter); - var res = itemInfo.ToRemoveResult(); + //var res = itemInfo.ToRemoveResult(); + RemoveResult res = new RemoveResult() + { + DateTime = DateTime.Now, + IsSuccess = itemInfo?.Errors is null, + Path = fullPath + }; + + if (itemInfo?.Errors is null && itemInfo?.Result is not null) + { + string oid = itemInfo.Result; + Logger.Debug($"{fullPath} уделен, Oid={oid}"); - OnRemoveCompleted(res, itemInfo?.Data?.OpId); + OnRemoveCompleted(res, oid); + } return res; } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebRequestRepo2.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebRequestRepo2.cs index 80f6db3d..0428a3be 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebRequestRepo2.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebRequestRepo2.cs @@ -15,6 +15,8 @@ using YaR.Clouds.Common; using Stream = System.IO.Stream; +// Yandex has API version 378.1.0 + namespace YaR.Clouds.Base.Repos.YandexDisk.YadWeb { class YadWebRequestRepo2 : YadWebRequestRepo @@ -291,7 +293,7 @@ public override async Task FolderInfo(RemotePath path, int offset = 0, i protected override void OnMoveCompleted(CopyResult res, string operationOpId) => WaitForOperation(operationOpId); - protected override void OnRemoveCompleted(RemoveResult res, string operationOpId) => WaitForOperation(operationOpId); + protected override void OnRemoveCompleted(RemoveResult res, string operationOpId) => WaitForOperation2(operationOpId); protected override void OnRenameCompleted(RenameResult res, string operationOpId) => WaitForOperation(operationOpId); @@ -324,7 +326,73 @@ protected override void WaitForOperation(string operationOpId) * "target": "12-it's_uid-34:/disk/destination-folder" * }, */ - var doAgain = itemInfo.Data.Error is null && itemInfo.Data.State != "COMPLETED"; + var doAgain = itemInfo?.Data?.Error is null && itemInfo?.Data?.State != "COMPLETED"; + if (doAgain) + { + if (flagWatch.Elapsed > TimeSpan.FromSeconds(30)) + { + Logger.Debug("Operation is still in progress, let's wait..."); + flagWatch.Restart(); + } + } + return doAgain; + }, + TimeSpan.FromMilliseconds(OperationStatusCheckIntervalMs), OperationStatusCheckRetryTimeout); + } + + protected void WaitForOperation2(string operationOpId) + { + if (string.IsNullOrWhiteSpace(operationOpId)) + return; + + var flagWatch = Stopwatch.StartNew(); + + //YadResponseModel itemInfo = null; + YadOperationStatusV2 itemInfo = new YadOperationStatusV2(operationOpId); + Retry.Do( + () => TimeSpan.Zero, + () => new YaDCommonRequestV2(HttpSettings, (YadWebAuth)Auth) + .With(itemInfo) + .MakeRequestAsync(_connectionLimiter) + .Result, + _ => + { + /* + * Яндекс повторяет проверку при переносе папки каждый 9 секунд. + * Когда операция завершилась: "status": "DONE", "state": "COMPLETED", "type": "move" + * "params": { + * "source": "12-it's_uid-34:/disk/source-folder", + * "target": "12-it's_uid-34:/disk/destination-folder" + * }, + * Когда операция еще в процессе: "status": "EXECUTING", "state": "EXECUTING", "type": "move" + * "params": { + * "source": "12-it's_uid-34:/disk/source-folder", + * "target": "12-it's_uid-34:/disk/destination-folder" + * }, + */ + + if (itemInfo is null) + throw new NullReferenceException("WaitForOperation2 itemInfo is null"); + + if (itemInfo.Errors is not null) + { + foreach (var error in itemInfo.Errors) + { + Logger.Error($"WaitForOperation2 error: {error.Error.Code} {error.Error.Title}"); + } + return true; + } + + if (!itemInfo.Result.TryGetValue(operationOpId, out var state)) + { + Logger.Error($"WaitForOperation2 failure: operation {operationOpId} is not registered"); + return true; + } + + var doAgain = state.State != "COMPLETED"; + + //Logger.Debug($"WaitForOperation2: doAgain={doAgain}, Oid={operationOpId}"); + if (doAgain) { if (flagWatch.Elapsed > TimeSpan.FromSeconds(30)) @@ -342,12 +410,20 @@ public override async Task DetectOutsideChanges() { YadResponseModel, YadActiveOperationsParams> itemInfo = null; - _ = await new YaDCommonRequest(HttpSettings, (YadWebAuth)Auth) + var task1 = new YaDCommonRequest(HttpSettings, (YadWebAuth)Auth) .With(new YadActiveOperationsPostModel(), out itemInfo) - .With(new YadAccountInfoPostModel(), - out YadResponseModel accountInfo) + //.With(new YadAccountInfoPostModel(), + // out YadResponseModel accountInfo) + .MakeRequestAsync(_connectionLimiter); + + // Надо учитывать, что счетчики обновляются через 10-15 секунд после операции + var task2 = new YaDCommonRequestV2(HttpSettings, (YadWebAuth)Auth) + .With(new JournalCountersV2(), out var journalCounters) .MakeRequestAsync(_connectionLimiter); + Task.WaitAll(task1, task2); + + var list = itemInfo?.Data? .Select(x => new ActiveOperation { @@ -358,13 +434,16 @@ public override async Task DetectOutsideChanges() TargetPath = DtoImportYadWeb.GetOpPath(x.Data.Target), })?.ToList(); + var counters = journalCounters?.Result?.EventTypes; + var info = new CheckUpInfo { AccountInfo = new CheckUpInfo.CheckInfo { - FilesCount = accountInfo?.Data?.FilesCount ?? 0, - Free = accountInfo?.Data?.Free ?? 0, - Trash = accountInfo?.Data?.Trash ?? 0, + //FilesCount = accountInfo?.Data?.FilesCount ?? 0, + //Free = accountInfo?.Data?.Free ?? 0, + //Trash = accountInfo?.Data?.Trash ?? 0, + JournalCounters = new JournalCounters(journalCounters.Result) }, ActiveOperations = list, }; diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs index 4bf886c7..fbdc4378 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs @@ -7,7 +7,6 @@ using System.Threading; using System.Threading.Tasks; using YaR.Clouds.Base.Repos; -using YaR.Clouds.Base.Repos.MailRuCloud; namespace YaR.Clouds.Base.Requests { @@ -32,7 +31,7 @@ protected virtual HttpWebRequest CreateRequest(string baseDomain) var uriz = new Uri(new Uri(domain), RelationalUri); // suppressing escaping is obsolete and breaks, for example, Chinese names - // url generated for %E2%80%8E and %E2%80%8F seems ok, but mail.ru replies error + // url generated for %E2%80%8E and %E2%80%8F seems OK, but mail.ru replies error // https://stackoverflow.com/questions/20211496/uri-ignore-special-characters //var udriz = new Uri(new Uri(domain), RelationalUri, true); @@ -46,7 +45,11 @@ protected virtual HttpWebRequest CreateRequest(string baseDomain) request.Proxy = _settings.Proxy; request.CookieContainer = _auth?.Cookies; request.Method = "GET"; - request.ContentType = "application/x-www-form-urlencoded; charset=UTF-8"; + // 21.09.2024 Заглушка для проверки, надо переделать + //request.ContentType = "application/x-www-form-urlencoded; charset=UTF-8"; + request.ContentType = RelationalUri.Contains("/models-v2?") + ? "application/json; charset=UTF-8" + : "application/x-www-form-urlencoded; charset=UTF-8"; request.Accept = "application/json"; request.UserAgent = _settings.UserAgent; request.ContinueTimeout = _settings.CloudSettings.Wait100ContinueTimeoutSec * 1000; @@ -166,6 +169,7 @@ public virtual async Task MakeRequestAsync(SemaphoreSlim serverMaxConnectionL if (!result.Ok || response.StatusCode != HttpStatusCode.OK) { + Logger.Debug($"Original request: {System.Text.Encoding.UTF8.GetString(requestContent)}"); var exceptionMessage = $"Request failed (status code {(int)response.StatusCode}): {result.Description}"; throw new RequestException(exceptionMessage) diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/CheckUpInfo.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/CheckUpInfo.cs index 1204c82f..e2da6d44 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/CheckUpInfo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/CheckUpInfo.cs @@ -1,23 +1,181 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using YaR.Clouds.Base.Repos.YandexDisk.YadWeb.Models; namespace YaR.Clouds.Base.Requests.Types; public class CheckUpInfo { - public struct CheckInfo + public class CheckInfo { - public long FilesCount; - public long Free; - public long Trash; + //public long FilesCount; + //public long Free; + //public long Trash; + + /// Набор счетчиков, возвращаемых методом cloud/virtual-disk-journal-counters + public JournalCounters JournalCounters; }; - /// - /// Набор информации для проверки изменений на диске, минуя данный сервис. - /// + /// Набор информации для проверки изменений на диске, минуя данный сервис. public CheckInfo AccountInfo { get; set; } - /// - /// Список активных операций на сервере. - /// + /// Список активных операций на сервере. public List ActiveOperations { get; set; } } + +public enum CounterOperation +{ + None = 0, + Remove, + Rename, + Move, + Copy, + Upload, + TakeSomeonesFolder, + NewFolder, + //Update, + RemoveToTrash, + RestoreFromTrash, + TrashDropItem, + TrashDropAll +} + +public class JournalCounters +{ + /// Удаление навсегда + public long RemoveCounter = 0; + public const string RemoveCounterStr = "fs-rm"; + + /// Переименование + public long RenameCounter = 0; + public const string RenameCounterStr = "fs-rename"; + + /// Перемещение + public long MoveCounter = 0; + public const string MoveCounterStr = "fs-move"; + + /// Копирование + public long CopyCounter = 0; + public const string CopyCounterStr = "fs-copy"; + + /// Загрузка + public long UploadCounter = 0; + public const string UploadCounterStr = "fs-store"; + + /// Сохранение на Диск + public long TakeSomeonesFolderCounter = 0; + public const string TakeSomeonesFolderCounterStr = "fs-store-download"; + + /// Создание папки + public long NewFolderCounter = 0; + public const string NewFolderCounterStr = "fs-mkdir"; + + /// Изменение файла + /* + * Не используется, не понятно что делать с этим счетчиком. + * При удалении файла и тут же загрузке этого же файла, + * счетчик увеличивается. Но если править какой-нибудь + * документ онлайн на Диске, то счетчик не меняется. + */ + //public long UpdateCounter = 0; + //public const string UpdateCounterStr = "fs-store-update"; + + /// Удаление в Корзину + public long RemoveToTrashCounter = 0; + public const string RemoveToTrashCounterStr = "fs-trash-append"; + + /// Удаление в Корзину + public long RestoreFromTrashCounter = 0; + public const string RestoreFromTrashCounterStr = "fs-trash-restore"; + + /// Удаление из Корзины + public long TrashDropItemCounter = 0; + public const string TrashDropItemCounterStr = "fs-trash-drop"; + + /// Очистка Корзины + public long TrashDropAllCounter = 0; + public const string TrashDropAllCounterStr = "fs-trash-drop-all"; + + public JournalCounters() + { + } + + public JournalCounters(YadJournalCountersV2 data) + { + long val; + var dict = data.EventTypes; + RemoveCounter = dict.TryGetValue(RemoveCounterStr, out val) ? val : 0; + RenameCounter = dict.TryGetValue(RenameCounterStr, out val) ? val : 0; + MoveCounter = dict.TryGetValue(MoveCounterStr, out val) ? val : 0; + CopyCounter = dict.TryGetValue(CopyCounterStr, out val) ? val : 0; + UploadCounter = dict.TryGetValue(UploadCounterStr, out val) ? val : 0; + TakeSomeonesFolderCounter = dict.TryGetValue(TakeSomeonesFolderCounterStr, out val) ? val : 0; + NewFolderCounter = dict.TryGetValue(NewFolderCounterStr, out val) ? val : 0; + //UpdateCounter = dict.TryGetValue(UpdateCounterStr, out val) ? val : 0; + RemoveToTrashCounter = dict.TryGetValue(RemoveToTrashCounterStr, out val) ? val : 0; + RestoreFromTrashCounter = dict.TryGetValue(RestoreFromTrashCounterStr, out val) ? val : 0; + TrashDropItemCounter = dict.TryGetValue(TrashDropItemCounterStr, out val) ? val : 0; + TrashDropAllCounter = dict.TryGetValue(TrashDropAllCounterStr, out val) ? val : 0; + } + + public void Increment(CounterOperation operation) + { + switch (operation) + { + case CounterOperation.None: + break; + case CounterOperation.Remove: + RemoveCounter++; + break; + case CounterOperation.Rename: + RenameCounter++; + break; + case CounterOperation.Move: + MoveCounter++; + break; + case CounterOperation.Copy: + CopyCounter++; + break; + case CounterOperation.Upload: + UploadCounter++; + break; + case CounterOperation.TakeSomeonesFolder: + TakeSomeonesFolderCounter++; + break; + case CounterOperation.NewFolder: + NewFolderCounter++; + break; + //case CounterOperation.Update: + // UpdateCounter++; + // break; + case CounterOperation.RemoveToTrash: + RemoveToTrashCounter++; + break; + case CounterOperation.RestoreFromTrash: + RestoreFromTrashCounter++; + break; + case CounterOperation.TrashDropItem: + TrashDropItemCounter++; + break; + case CounterOperation.TrashDropAll: + TrashDropAllCounter++; + break; + } + } + + public void TakeMax(JournalCounters src) + { + RemoveCounter = Math.Max(RemoveCounter, src.RemoveCounter); + RenameCounter = Math.Max(RenameCounter, src.RenameCounter); + MoveCounter = Math.Max(MoveCounter, src.MoveCounter); + CopyCounter = Math.Max(CopyCounter, src.CopyCounter); + UploadCounter = Math.Max(UploadCounter, src.UploadCounter); + TakeSomeonesFolderCounter = Math.Max(TakeSomeonesFolderCounter, src.TakeSomeonesFolderCounter); + NewFolderCounter = Math.Max(NewFolderCounter, src.NewFolderCounter); + //UpdateCounter = Math.Max(UpdateCounter, src.UpdateCounter); + RemoveToTrashCounter = Math.Max(RemoveToTrashCounter, src.RemoveToTrashCounter); + RestoreFromTrashCounter = Math.Max(RestoreFromTrashCounter, src.RestoreFromTrashCounter); + TrashDropItemCounter = Math.Max(TrashDropItemCounter, src.TrashDropItemCounter); + TrashDropAllCounter = Math.Max(TrashDropAllCounter, src.TrashDropAllCounter); + } +}; diff --git a/MailRuCloud/MailRuCloudApi/Base/Streams/UploadStreamHttpClient.cs b/MailRuCloud/MailRuCloudApi/Base/Streams/UploadStreamHttpClient.cs index e9d3955b..0a969201 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Streams/UploadStreamHttpClient.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Streams/UploadStreamHttpClient.cs @@ -115,29 +115,39 @@ private void UploadFull(Stream sourceStream, bool doInvokeFileStreamSent = true) }); var client = HttpClientFabric.Instance[_cloud]; - var uploadFileResult = _cloud.RequestRepo.DoUpload(client, pushContent, _file).Result; + DateTime timestampBeforeOperation = DateTime.Now; + try + { + _cloud.OnBeforeUpload(_file.FullPath); - if (uploadFileResult.HttpStatusCode != HttpStatusCode.Created && - uploadFileResult.HttpStatusCode != HttpStatusCode.OK) - throw new Exception("Cannot upload file, status " + uploadFileResult.HttpStatusCode); + var uploadFileResult = _cloud.RequestRepo.DoUpload(client, pushContent, _file).Result; - // 2020-10-26 mail.ru does not return file size now - //if (uploadFileResult.HasReturnedData && _file.OriginalSize != uploadFileResult.Size) - // throw new Exception("Local and remote file size does not match"); + if (uploadFileResult.HttpStatusCode != HttpStatusCode.Created && + uploadFileResult.HttpStatusCode != HttpStatusCode.OK) + throw new Exception("Cannot upload file, status " + uploadFileResult.HttpStatusCode); - _file.Hash = uploadFileResult.HasReturnedData switch + // 2020-10-26 mail.ru does not return file size now + //if (uploadFileResult.HasReturnedData && _file.OriginalSize != uploadFileResult.Size) + // throw new Exception("Local and remote file size does not match"); + + _file.Hash = uploadFileResult.HasReturnedData switch + { + true when CheckHashes && null != uploadFileResult.Hash && _cloudFileHasher != null && + _cloudFileHasher.Hash.Hash.Value != uploadFileResult.Hash.Hash.Value => throw + new HashMatchException(_cloudFileHasher.Hash.ToString(), uploadFileResult.Hash.ToString()), + true => uploadFileResult.Hash, + _ => _file.Hash + }; + + if (uploadFileResult.NeedToAddFile) + _cloud.AddFileInCloud(_file, ConflictResolver.Rewrite) + .Result + .ThrowIf(r => !r.Success, _ => new Exception($"Cannot add file {_file.FullPath}")); + } + finally { - true when CheckHashes && null != uploadFileResult.Hash && _cloudFileHasher != null && - _cloudFileHasher.Hash.Hash.Value != uploadFileResult.Hash.Hash.Value => throw - new HashMatchException(_cloudFileHasher.Hash.ToString(), uploadFileResult.Hash.ToString()), - true => uploadFileResult.Hash, - _ => _file.Hash - }; - - if (uploadFileResult.NeedToAddFile) - _cloud.AddFileInCloud(_file, ConflictResolver.Rewrite) - .Result - .ThrowIf(r => !r.Success, _ => new Exception($"Cannot add file {_file.FullPath}")); + _cloud.OnAfterUpload(_file.FullPath, timestampBeforeOperation); + } } public bool CheckHashes { get; set; } = true; diff --git a/MailRuCloud/MailRuCloudApi/Cloud.cs b/MailRuCloud/MailRuCloudApi/Cloud.cs index 80665287..bccf3dc9 100644 --- a/MailRuCloud/MailRuCloudApi/Cloud.cs +++ b/MailRuCloud/MailRuCloudApi/Cloud.cs @@ -5,6 +5,7 @@ using System.IO; using System.Linq; using System.Net; +using System.Runtime.InteropServices; using System.Security.Authentication; using System.Text; using System.Text.RegularExpressions; @@ -13,6 +14,7 @@ using Newtonsoft.Json; using YaR.Clouds.Base; using YaR.Clouds.Base.Repos; +using YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests.Types; using YaR.Clouds.Base.Requests.Types; using YaR.Clouds.Common; using YaR.Clouds.Extensions; @@ -317,12 +319,10 @@ private async Task GetItemInternalAsync(string path, bool resolveLinks, // //_itemCache.Add(cachefolder.Files); //} remotePath = ulink is null ? RemotePath.Get(path) : RemotePath.Get(ulink); + DateTime timestamp = DateTime.Now; var cloudResult = await RequestRepo.FolderInfo(remotePath, depth: Settings.ListDepth); if (cloudResult is null) { - // Если обратились к серверу, а в ответ пустота, - // надо прочистить кеш, на случай, если в кеше что-то есть, - // а в облаке параллельно удалили папку. // Если с сервера получено состояние, что папки нет, // а кеш ранее говорил, что папка в кеше есть, но без наполнения, // то папку удалили и надо безотлагательно очистить кеш. @@ -330,7 +330,7 @@ private async Task GetItemInternalAsync(string path, bool resolveLinks, { Logger.Debug("Папка была удалена, делается чистка кеша"); } - _entryCache.OnRemoveTreeAsync(remotePath.Path, null); + _entryCache.OnRemoveTree(timestamp, remotePath.Path, null); return null; } @@ -556,14 +556,13 @@ public async Task Unpublish(File file) await Unpublish(innerFile.GetPublicLinks(this).FirstOrDefault().Uri, innerFile.FullPath); innerFile.PublicLinks.Clear(); } - _entryCache.OnRemoveTreeAsync(file.FullPath, GetItemAsync(file.FullPath, fastGetFromCloud: true)); } private async Task Publish(string fullPath) { var res = (await RequestRepo.Publish(fullPath)) - .ThrowIf(r => !r.IsSuccess, _ => new Exception($"Publish error, path = {fullPath}")); + .ThrowIf(r => !r.IsSuccess, _ => new Exception($"Publish error, path = {fullPath}")); var uri = new Uri(res.Url, UriKind.RelativeOrAbsolute); if (!uri.IsAbsoluteUri) @@ -653,6 +652,7 @@ public async Task Publish(IEntry entry, bool makeShareFile = true, /// True or false operation result. public async Task Copy(Folder folder, string destinationPath) { + DateTime timestampBeforeOperation = DateTime.Now; destinationPath = WebDavPath.Clean(destinationPath); // if it linked - just clone @@ -669,14 +669,27 @@ public async Task Copy(Folder folder, string destinationPath) } } - //var copyRes = await new CopyRequest(CloudApi, folder.FullPath, destinationPath).MakeRequestAsync(_connectionLimiter); - var copyRes = await RequestRepo.Copy(folder.FullPath, destinationPath); - if (!copyRes.IsSuccess) - return false; - - _entryCache.ResetCheck(); - _entryCache.OnCreateAsync(destinationPath, GetItemAsync(destinationPath, fastGetFromCloud: true)); - _entryCache.OnRemoveTreeAsync(folder.FullPath, GetItemAsync(folder.FullPath, fastGetFromCloud: true)); + try + { + _entryCache.RegisterOperation(folder.FullPath, CounterOperation.Copy); + _entryCache.RegisterOperation(destinationPath, CounterOperation.None); + + //var copyRes = await new CopyRequest(CloudApi, folder.FullPath, destinationPath).MakeRequestAsync(_connectionLimiter); + var copyRes = await RequestRepo.Copy(folder.FullPath, destinationPath); + if (!copyRes.IsSuccess) + return false; + + // OnRemove делать до OnCreate + _entryCache.OnRemoveTree(timestampBeforeOperation, + folder.FullPath, GetItemAsync(folder.FullPath, fastGetFromCloud: true)); + _entryCache.OnCreate(timestampBeforeOperation, + destinationPath, GetItemAsync(destinationPath, fastGetFromCloud: true), folder.FullPath); + } + finally + { + _entryCache.UnregisterOperation(folder.FullPath); + _entryCache.UnregisterOperation(destinationPath); + } //clone all inner links if (LinkManager is not null) @@ -746,6 +759,7 @@ public async Task Copy(IEntry source, string destinationPath, string newNa /// True or false operation result. public async Task Copy(File file, string destinationPath, string newName) { + DateTime timestamp = DateTime.Now; string destPath = destinationPath; newName = string.IsNullOrEmpty(newName) ? file.Name : newName; bool doRename = file.Name != newName; @@ -756,7 +770,11 @@ public async Task Copy(File file, string destinationPath, string newName) // копируем не саму ссылку, а её содержимое if (link is not null) { + var cloneRes = await CloneItem(destPath, link.Href.OriginalString); + if (!cloneRes.IsSuccess) + return false; + if (doRename || WebDavPath.Name(cloneRes.Path) != newName) { string newFullPath = WebDavPath.Combine(destPath, WebDavPath.Name(cloneRes.Path)); @@ -765,40 +783,42 @@ public async Task Copy(File file, string destinationPath, string newName) return false; } - if (!cloneRes.IsSuccess) - return false; - - _entryCache.ResetCheck(); - _entryCache.OnCreateAsync(destPath, GetItemAsync(destPath, fastGetFromCloud: true)); - _entryCache.OnRemoveTreeAsync(link.Href.OriginalString, GetItemAsync(link.Href.OriginalString, fastGetFromCloud: true)); - return true; } } - var qry = file.Files + var qry = file + .Files .AsParallel() .WithDegreeOfParallelism(file.Files.Count) .Select(async pfile => { - //var copyRes = await new CopyRequest(CloudApi, pfile.FullPath, destPath, ConflictResolver.Rewrite).MakeRequestAsync(_connectionLimiter); - var copyRes = await RequestRepo.Copy(pfile.FullPath, destPath, ConflictResolver.Rewrite); - if (!copyRes.IsSuccess) return false; + try + { + _entryCache.RegisterOperation(pfile.FullPath, CounterOperation.Copy); + _entryCache.RegisterOperation(destPath, CounterOperation.None); + + //var copyRes = await new CopyRequest(CloudApi, pfile.FullPath, destPath, ConflictResolver.Rewrite).MakeRequestAsync(_connectionLimiter); + var copyRes = await RequestRepo.Copy(pfile.FullPath, destPath, ConflictResolver.Rewrite); + if (!copyRes.IsSuccess) + return false; - if (!doRename && WebDavPath.Name(copyRes.NewName) == newName) - return true; + if (!doRename && WebDavPath.Name(copyRes.NewName) == newName) + return true; - string newFullPath = WebDavPath.Combine(destPath, WebDavPath.Name(copyRes.NewName)); - return await Rename(newFullPath, pfile.Name.Replace(file.Name, newName)); + string newFullPath = WebDavPath.Combine(destPath, WebDavPath.Name(copyRes.NewName)); + return await Rename(newFullPath, pfile.Name.Replace(file.Name, newName)); + } + finally + { + _entryCache.UnregisterOperation(pfile.FullPath); + _entryCache.UnregisterOperation(destPath); + } }); bool res = (await Task.WhenAll(qry)) .All(r => r); - _entryCache.ResetCheck(); - _entryCache.OnCreateAsync(destinationPath, GetItemAsync(destinationPath, fastGetFromCloud: true)); - _entryCache.OnRemoveTreeAsync(file.FullPath, GetItemAsync(file.FullPath, fastGetFromCloud: true)); - return res; } @@ -871,30 +891,39 @@ private async Task Rename(string fullPath, string newName) //rename item if (link is null) { - var data = await RequestRepo.Rename(fullPath, newName); + string newNamePath = WebDavPath.Combine(WebDavPath.Parent(fullPath), newName); + try + { + DateTime timestampBeforeOperation = DateTime.Now; + _entryCache.RegisterOperation(fullPath, CounterOperation.Rename); + _entryCache.RegisterOperation(newNamePath, CounterOperation.None); - if (!data.IsSuccess) - return data.IsSuccess; + var data = await RequestRepo.Rename(fullPath, newName); - LinkManager?.ProcessRename(fullPath, newName); - string newNamePath = WebDavPath.Combine(WebDavPath.Parent(fullPath), newName); - _entryCache.ResetCheck(); - _entryCache.OnCreateAsync(newNamePath, GetItemAsync(newNamePath, fastGetFromCloud: true)); - _entryCache.OnRemoveTreeAsync(fullPath, GetItemAsync(fullPath, fastGetFromCloud: true)); + if (!data.IsSuccess) + return data.IsSuccess; + + LinkManager?.ProcessRename(fullPath, newName); - return data.IsSuccess; + // OnRemove делать до OnCreate + _entryCache.OnRemoveTree(timestampBeforeOperation, + fullPath, GetItemAsync(fullPath, fastGetFromCloud: true)); + _entryCache.OnCreate(timestampBeforeOperation, + newNamePath, GetItemAsync(newNamePath, fastGetFromCloud: true), fullPath); + + return data.IsSuccess; + } + finally + { + _entryCache.UnregisterOperation(fullPath); + _entryCache.UnregisterOperation(newNamePath); + } } //rename link if (LinkManager is not null) { bool res = LinkManager.RenameLink(link, newName); - if (res) - { - _entryCache.ResetCheck(); - _entryCache.OnCreateAsync(newName, GetItemAsync(newName, fastGetFromCloud: true)); - _entryCache.OnRemoveTreeAsync(fullPath, GetItemAsync(fullPath, fastGetFromCloud: true)); - } return res; } return false; @@ -950,22 +979,31 @@ public async Task MoveAsync(Folder folder, string destinationPath) if (link is not null) { var remapped = await LinkManager.RemapLink(link, destinationPath); - if (remapped) - { - _entryCache.ResetCheck(); - _entryCache.OnCreateAsync(destinationPath, GetItemAsync(destinationPath, fastGetFromCloud: true)); - _entryCache.OnRemoveTreeAsync(folder.FullPath, GetItemAsync(folder.FullPath, fastGetFromCloud: true)); - } return remapped; } - var res = await RequestRepo.Move(folder.FullPath, destinationPath); - _entryCache.ResetCheck(); - _entryCache.OnCreateAsync(destinationPath, GetItemAsync(destinationPath, fastGetFromCloud: true)); - _entryCache.OnRemoveTreeAsync(folder.FullPath, GetItemAsync(folder.FullPath, fastGetFromCloud: true)); + try + { + DateTime timestampBeforeOperation = DateTime.Now; + _entryCache.RegisterOperation(folder.FullPath,CounterOperation.Move); + _entryCache.RegisterOperation(destinationPath, CounterOperation.None); - if (!res.IsSuccess) - return false; + var res = await RequestRepo.Move(folder.FullPath, destinationPath); + + if (!res.IsSuccess) + return false; + + // OnRemove делать до OnCreate + _entryCache.OnRemoveTree(timestampBeforeOperation, + folder.FullPath, GetItemAsync(folder.FullPath, fastGetFromCloud: true)); + _entryCache.OnCreate(timestampBeforeOperation, + destinationPath, GetItemAsync(destinationPath, fastGetFromCloud: true), folder.FullPath); + } + finally + { + _entryCache.UnregisterOperation(folder.FullPath); + _entryCache.UnregisterOperation(destinationPath); + } //clone all inner links if (LinkManager is not null) @@ -1004,16 +1042,11 @@ public async Task MoveAsync(Folder folder, string destinationPath) /// True or false operation result. public async Task MoveAsync(File file, string destinationPath) { + DateTime timestampBeforeOperation = DateTime.Now; var link = LinkManager is null ? null : await LinkManager.GetItemLink(file.FullPath, false); if (link is not null) { var remapped = await LinkManager.RemapLink(link, destinationPath); - if (remapped) - { - _entryCache.ResetCheck(); - _entryCache.OnCreateAsync(destinationPath, GetItemAsync(destinationPath, fastGetFromCloud: true)); - _entryCache.OnRemoveTreeAsync(file.FullPath, GetItemAsync(file.FullPath, fastGetFromCloud: true)); - } return remapped; } @@ -1022,17 +1055,35 @@ public async Task MoveAsync(File file, string destinationPath) .WithDegreeOfParallelism(file.Files.Count) .Select(async pfile => { - return await RequestRepo.Move(pfile.FullPath, destinationPath); + try + { + _entryCache.RegisterOperation(pfile.FullPath, CounterOperation.Move); + _entryCache.RegisterOperation(destinationPath, CounterOperation.None); + + var moveRes = await RequestRepo.Move(pfile.FullPath, destinationPath); + + if (!moveRes.IsSuccess) + return moveRes; + + // OnRemove делать до OnCreate + _entryCache.OnRemoveTree(timestampBeforeOperation, + file.FullPath, GetItemAsync(file.FullPath, fastGetFromCloud: true)); + _entryCache.OnCreate(timestampBeforeOperation, + destinationPath, GetItemAsync(destinationPath, fastGetFromCloud: true), file.FullPath); + + return moveRes; + } + finally + { + _entryCache.UnregisterOperation(pfile.FullPath); + _entryCache.UnregisterOperation(destinationPath); + } }); bool res = (await Task.WhenAll(qry)) .All(r => r.IsSuccess); - _entryCache.ResetCheck(); - _entryCache.OnCreateAsync(destinationPath, GetItemAsync(destinationPath, fastGetFromCloud: true)); - _entryCache.OnRemoveTreeAsync(file.FullPath, GetItemAsync(file.FullPath, fastGetFromCloud: true)); - return res; } @@ -1130,6 +1181,8 @@ await GetItemAsync(foundFullPath) is File sharefile) /// True or false result operation. public async Task Remove(string fullPath) { + DateTime timestamp = DateTime.Now; + if (LinkManager is not null) { var link = await LinkManager.GetItemLink(fullPath, false); @@ -1137,16 +1190,25 @@ public async Task Remove(string fullPath) { // if folder is linked - do not delete inner files/folders // if client deleting recursively just try to unlink folder - LinkManager.RemoveLink(fullPath); - _entryCache.ResetCheck(); - _entryCache.OnRemoveTreeAsync(fullPath, GetItemAsync(fullPath, fastGetFromCloud: true)); - return true; + return LinkManager.RemoveLink(fullPath); } } - var res = await RequestRepo.Remove(fullPath); - if (!res.IsSuccess) - return false; + try + { + _entryCache.RegisterOperation(fullPath, CounterOperation.RemoveToTrash); + + var res = await RequestRepo.Remove(fullPath); + + if (!res.IsSuccess) + return false; + + _entryCache.OnRemoveTree(timestamp, fullPath, GetItemAsync(fullPath, fastGetFromCloud: true)); + } + finally + { + _entryCache.UnregisterOperation(fullPath); + } // remove inner links if (LinkManager is not null) @@ -1155,10 +1217,7 @@ public async Task Remove(string fullPath) LinkManager.RemoveLinks(innerLinks); } - _entryCache.ResetCheck(); - _entryCache.OnRemoveTreeAsync(fullPath, GetItemAsync(fullPath, fastGetFromCloud: true)); - - return res.IsSuccess; + return true; } #endregion == Remove ======================================================================================================================== @@ -1209,15 +1268,26 @@ public bool CreateFolder(string name, string basePath) public async Task CreateFolderAsync(string fullPath) { - var res = await RequestRepo.CreateFolder(fullPath); + try + { + DateTime timestampBeforeOperation = DateTime.Now; + + _entryCache.RegisterOperation(fullPath, CounterOperation.NewFolder); + + var res = await RequestRepo.CreateFolder(fullPath); - if (res.IsSuccess) + if (!res.IsSuccess) + return false; + + _entryCache.OnCreate(timestampBeforeOperation, + fullPath, GetItemAsync(fullPath, fastGetFromCloud: true), null); + } + finally { - _entryCache.ResetCheck(); - _entryCache.OnCreateAsync(fullPath, GetItemAsync(fullPath, fastGetFromCloud: true)); + _entryCache.UnregisterOperation(fullPath); } - return res.IsSuccess; + return true; } //public bool CreateFolder(string fullPath) @@ -1228,14 +1298,27 @@ public async Task CreateFolderAsync(string fullPath) public async Task CloneItem(string toPath, string fromUrl) { - var res = await RequestRepo.CloneItem(fromUrl, toPath); + try + { + DateTime timestampBeforeOperation = DateTime.Now; + _entryCache.RegisterOperation(toPath, CounterOperation.Copy); + _entryCache.RegisterOperation(fromUrl, CounterOperation.None); + + var res = await RequestRepo.CloneItem(fromUrl, toPath); - if (res.IsSuccess) + if (!res.IsSuccess) + return res; + + _entryCache.OnCreate(timestampBeforeOperation, + toPath, GetItemAsync(toPath, fastGetFromCloud: true), null); + + return res; + } + finally { - _entryCache.ResetCheck(); - _entryCache.OnCreateAsync(toPath, GetItemAsync(toPath, fastGetFromCloud: true)); + _entryCache.UnregisterOperation(toPath); + _entryCache.UnregisterOperation(fromUrl); } - return res; } public async Task GetFileDownloadStream(File file, long? start, long? end) @@ -1268,14 +1351,20 @@ public async Task GetFileUploadStream(string fullFilePath, long size, Ac private void OnFileUploaded(IEnumerable files) { var lst = files.ToList(); - foreach (var file in lst) - { - _entryCache.OnCreateAsync(file.FullPath, GetItemAsync(file.FullPath, fastGetFromCloud: true)); - } - _entryCache.ResetCheck(); FileUploaded?.Invoke(lst); } + public void OnBeforeUpload(string path) + { + _entryCache.RegisterOperation(path, CounterOperation.Upload); + } + + public void OnAfterUpload(string path, DateTime timestampBeforeOperation) + { + _entryCache.OnCreate(timestampBeforeOperation, path, GetItemAsync(path, fastGetFromCloud: true), null); + _entryCache.UnregisterOperation(path); + } + public T DownloadFileAsJson(File file) { using var stream = RequestRepo.GetDownloadStream(file); @@ -1323,14 +1412,9 @@ e is WebException wee && public bool UploadFile(string path, byte[] content, bool discardEncryption = false) { - using (var stream = GetFileUploadStream(path, content.Length, null, null, discardEncryption).Result) - { - stream.Write(content, 0, content.Length); - } - - _entryCache.ResetCheck(); - _entryCache.OnCreateAsync(path, GetItemAsync(path, fastGetFromCloud: true)); - + DateTime timestamp = DateTime.Now; + using var stream = GetFileUploadStream(path, content.Length, null, null, discardEncryption).Result; + stream.Write(content, 0, content.Length); return true; } @@ -1371,11 +1455,13 @@ public async Task LinkItem(Uri url, string path, string name, bool isFile, if (LinkManager is null) return false; + DateTime timestampBeforeOperation = DateTime.Now; + var res = await LinkManager.Add(url, path, name, isFile, size, creationDate); if (res) { LinkManager.Save(); - _entryCache.OnCreateAsync(path, GetItemAsync(path, fastGetFromCloud: true)); + _entryCache.OnCreate(timestampBeforeOperation, path, GetItemAsync(path, fastGetFromCloud: true), null); } return res; } @@ -1392,15 +1478,24 @@ public async void RemoveDeadLinks() public async Task AddFile(IFileHash hash, string fullFilePath, long size, ConflictResolver? conflict = null) { - var res = await RequestRepo.AddFile(fullFilePath, hash, size, DateTime.Now, conflict); + try + { + DateTime timestampBeforeOperation = DateTime.Now; + _entryCache.RegisterOperation(fullFilePath, CounterOperation.Upload); + + var res = await RequestRepo.AddFile(fullFilePath, hash, size, DateTime.Now, conflict); + + if (!res.Success) + return res; + + _entryCache.OnCreate(timestampBeforeOperation, fullFilePath, GetItemAsync(fullFilePath, fastGetFromCloud: true), null); - if (res.Success) + return res; + } + finally { - _entryCache.ResetCheck(); - _entryCache.OnCreateAsync(fullFilePath, GetItemAsync(fullFilePath, fastGetFromCloud: true)); + _entryCache.UnregisterOperation(fullFilePath); } - - return res; } public async Task AddFileInCloud(File fileInfo, ConflictResolver? conflict = null) @@ -1415,15 +1510,26 @@ public async Task SetFileDateTime(File file, DateTime dateTime) if (file.LastWriteTimeUtc == dateTime) return true; - var added = await RequestRepo.AddFile(file.FullPath, file.Hash, file.Size, dateTime, ConflictResolver.Rename); - bool res = added.Success; - if (res) + try { + DateTime timestampBeforeOperation = DateTime.Now; + _entryCache.RegisterOperation(file.FullPath, CounterOperation.Upload); + + var res = await RequestRepo.AddFile(file.FullPath, file.Hash, file.Size, dateTime, ConflictResolver.Rename); + + if (!res.Success) + return false; + file.LastWriteTimeUtc = dateTime; - _entryCache.OnCreateAsync(file.FullPath, GetItemAsync(file.FullPath, fastGetFromCloud: true)); - } - return res; + _entryCache.OnCreate(timestampBeforeOperation, file.FullPath, GetItemAsync(file.FullPath, fastGetFromCloud: true), null); + + return true; + } + finally + { + _entryCache.UnregisterOperation(file.FullPath); + } } /// diff --git a/MailRuCloud/MailRuCloudApi/Common/EntryCache.cs b/MailRuCloud/MailRuCloudApi/Common/EntryCache.cs index f9df181b..8827c70b 100644 --- a/MailRuCloud/MailRuCloudApi/Common/EntryCache.cs +++ b/MailRuCloud/MailRuCloudApi/Common/EntryCache.cs @@ -8,6 +8,7 @@ using YaR.Clouds.Links; using YaR.Clouds.Base.Requests.Types; using System.Linq; +using static YaR.Clouds.Extensions.Extensions; namespace YaR.Clouds.Common; @@ -52,17 +53,25 @@ public enum GetState private readonly ConcurrentDictionary _root = new(StringComparer.InvariantCultureIgnoreCase); - private readonly SemaphoreSlim _locker = new SemaphoreSlim(1); + private readonly SemaphoreSlim _rootLocker = new SemaphoreSlim(1); public delegate Task CheckOperations(); private readonly CheckOperations _activeOperationsAsync; private readonly System.Timers.Timer _checkActiveOperationsTimer; - // Проверка активных операций на сервере и внешних изменений в облаке не через сервис - // производится каждые 24 секунды, число не кратное очистке кеша, чтобы не пересекались - // алгоритмы. - private readonly TimeSpan _opCheckPeriod = TimeSpan.FromSeconds(24); + // Проверка активных операций на сервере и наличия внешних изменений в облаке мимо сервиса + private readonly TimeSpan _opCheckPeriod = TimeSpan.FromSeconds(15); + /// + /// Сохраняемая с сервера и пополняемая при операциях данного сервиса информация о состоянии Диска, + /// для выявление внешних операций на Диске, минуя данный сервис, чтобы вовремя + /// сбросить кеш и обновить информацию с сервера. + /// + private CheckUpInfo.CheckInfo _lastComparedInfo; + /// + /// Блокировщик доступа к + /// + private readonly SemaphoreSlim _lastComparedInfoLocker = new SemaphoreSlim(1); + - private CheckUpInfo.CheckInfo? _lastComparedInfo; [DebuggerDisplay("{DebuggerDisplay,nq}")] internal class CacheItem @@ -98,6 +107,25 @@ internal class CacheItem + $", Since {CreationTime:HH:mm:ss} {Entry?.FullPath}"; } + /// + /// Перед выполнение любой операции сюда в мешок помещается путь к файлу или папке. + /// После окончания выполнения операции, пусть файла или папки удаляется отсюда из мешка. + /// По наличию пути в этом мешке в методе CheckActiveOps проверяется чья операция + /// происходит на сервере - данного сервиса или сторонняя, и если стороннего, + /// то кеш сбрасывается. + /// + private readonly Dictionary _registeredOperationPath = + new(StringComparer.InvariantCultureIgnoreCase); + /// + /// Блокировщик доступа к > + /// + private readonly SemaphoreSlim _operationLocker = new SemaphoreSlim(1); + + internal class /* это должен быть class, не struct */ CounterClass + { + public int Value; + } + public EntryCache(TimeSpan expirePeriod, CheckOperations activeOperationsAsync) { _expirePeriod = expirePeriod; @@ -128,7 +156,10 @@ public EntryCache(TimeSpan expirePeriod, CheckOperations activeOperationsAsync) Enabled = true, AutoReset = true }; - _checkActiveOperationsTimer.Elapsed += CheckActiveOps; + _checkActiveOperationsTimer.Elapsed += CheckActiveOpsAsync; + + // Для инициализации коллекции счетчиков, обращение к серверу за актуальными значениями + CheckActiveOpsAsync(); } } } @@ -153,8 +184,9 @@ protected virtual void Dispose(bool disposing) _cleanTimer?.Stop(); _cleanTimer?.Dispose(); } - Clear(); - _locker?.Dispose(); + ClearNoLock(); + _rootLocker?.Dispose(); + _operationLocker?.Dispose(); Logger.Debug("EntryCache is disposed"); } @@ -200,8 +232,7 @@ private void RemoveExpired(object sender, System.Timers.ElapsedEventArgs e) int partiallyExpiredCount = 0; foreach (var entry in _root) { - _locker.Wait(); - try + _rootLocker.LockedAction(() => { if (entry.Value.CreationTime <= threshold && _root.TryRemove(entry.Key, out var cacheEntry)) @@ -225,67 +256,254 @@ private void RemoveExpired(object sender, System.Timers.ElapsedEventArgs e) } } } - } - finally - { - _locker.Release(); - } + }); } if (removedCount > 0) Logger.Debug($"Items cache clean: removed {removedCount} expired " + $"items, {partiallyExpiredCount} marked partially expired ({watch.ElapsedMilliseconds} ms)"); - - return; } - public void ResetCheck() + public void RegisterOperation(string path, CounterOperation operation) { if (!IsCacheEnabled) return; - _locker.Wait(); - try + // Регистрация пути файла или папки, над которым производится манипуляция + _operationLocker.LockedAction(() => { - _lastComparedInfo = null; - } - finally + if (_registeredOperationPath.TryGetValue(path, out var counter)) + { + counter.Value++; + } + else + { + counter = new(); + counter.Value = 1; + _registeredOperationPath.Add(path, counter); + } + }); + + // Увеличение счетчика, соответствующего операции + if (operation != CounterOperation.None && IsCacheEnabled && !_root.IsEmpty) { - _locker.Release(); + _lastComparedInfoLocker.LockedAction(() => _lastComparedInfo?.JournalCounters.Increment(operation)); } } - private async void CheckActiveOps(object sender, System.Timers.ElapsedEventArgs e) + public void UnregisterOperation(string path) { - CheckUpInfo info = await _activeOperationsAsync(); - if (info is null || _disposedValue) + if (!IsCacheEnabled) return; - CheckUpInfo.CheckInfo? currentValue; + _operationLocker.LockedAction(() => + { + if (_registeredOperationPath.TryGetValue(path, out var counter) && + --counter.Value <= 0) + { + _registeredOperationPath.Remove(path); + } + }); + } - _locker.Wait(); - try + private void CheckActiveOpsAsync(object sender, System.Timers.ElapsedEventArgs e) + { + CheckActiveOpsAsync(); + } + + private DateTime _lastCheck = DateTime.MinValue; + + private async void CheckActiveOpsAsync() + { + if (_disposedValue || !IsCacheEnabled || _activeOperationsAsync is null) + return; + + if (_root.IsEmpty) { - currentValue = _lastComparedInfo; - _lastComparedInfo = info.AccountInfo; + /* + * Когда кеш пустой, периодически все равно делаем сравнение счетчиков сохраненных + * и увеличенных на количество операций, сделанных, данным сервисом, + * со значениями с сервера для выявления ситуации, когда на сервере значение меньше, + * то есть не "догнало" увеличение сохраненного счетчика. + */ + if (DateTime.Now.Subtract(_lastCheck).Minutes < 2) + return; } - finally + _lastCheck = DateTime.Now; + + CheckUpInfo info = await _activeOperationsAsync(); + if (info is null) + return; + + /* + * Если на Диске производятся операции, которые идут мимо текущего сервиса, + * с большой вероятностью, кеш будет неактуальным. + * Наличие внешних операций отслеживается двумя методами: + * - сравнение счетчиков - если количество операций на Диске + * с учетом операций данного сервиса изменилось, + * значит кто-то что-то делает параллельно, + * минуя текущий сервис, тогда делается сброс всего кеша; + * - проверка активных операций на Диске (например длительно перемещение + * папки из одного места в другое), + * если такие длительные операции не изменяют счетчики, + * то их не отследить иначе, при обнаружении длительной операции + * делается сброс кеша с путем, затронутым операцией + * (если не менялись счетчики, если менялись - полный сброс кеша). + * + * Полученные и сохраненные счетчики увеличиваются данным сервисом при операциях с Диском. + * Т.к. на сервере обновление счетчиков требует 10-20 секунд, может получиться ситуация, + * когда от сервера пришли значения меньшие, чем сохранные (из-за увеличения при операциях данным сервисом). + * Но через какое-то время значения от сервера должны "догнать" сохраненные значения, + * то есть стать равными им. Если при отсутствии операций с Диском через данный сервис, значения с сервера + * так и не догнали сохраненные значения, значит алгоритм что-то не учитывает. + * Если значения с сервера обогнали сохраненные значения, значит на сервере что-то было сделано, + * в обход данного сервиса, в таком случае делается сброс кеша. + * Из-за необходимости "догонять" значения, сохраняются значения от сервера не напрямую, + * а берется максимум из пришедших и сохраненных значений. + */ + CheckUpInfo.CheckInfo previous = null; + _lastComparedInfoLocker.LockedAction(() => { - _locker.Release(); - } + previous = _lastComparedInfo; + _lastComparedInfo = info.AccountInfo; + if (previous is not null && _lastComparedInfo is not null) + { + var dst = _lastComparedInfo.JournalCounters; + var src = previous.JournalCounters; + _lastComparedInfo.JournalCounters.TakeMax(previous.JournalCounters); + } + }); - if (currentValue is not null) + // Сравнение счетчиков + if (previous is not null) { - if (info.AccountInfo.FilesCount != currentValue.Value.FilesCount || - info.AccountInfo.Trash != currentValue.Value.Trash || - info.AccountInfo.Free != currentValue.Value.Free) + List texts = []; + var a = previous.JournalCounters; + var b = info.AccountInfo.JournalCounters; + + if (a.RemoveCounter < b.RemoveCounter) + texts.Add($"{JournalCounters.RemoveCounterStr}: {a.RemoveCounter}->{b.RemoveCounter}"); + + if (a.RenameCounter < b.RenameCounter) + texts.Add($"{JournalCounters.RenameCounterStr}: {a.RenameCounter}->{b.RenameCounter}"); + + if (a.MoveCounter < b.MoveCounter) + texts.Add($"{JournalCounters.MoveCounterStr}: {a.MoveCounter}->{b.MoveCounter}"); + + if (a.CopyCounter < b.CopyCounter) + texts.Add($"{JournalCounters.CopyCounterStr}: {a.CopyCounter}->{b.CopyCounter}"); + + //if (a.UpdateCounter < b.UpdateCounter) + // texts.Add($"{JournalCounters.UpdateCounterStr}: {a.UpdateCounter}->{b.UpdateCounter}"); + + if (a.UploadCounter < b.UploadCounter) + texts.Add($"{JournalCounters.UploadCounterStr}: {a.UploadCounter}->{b.UploadCounter}"); + + if (a.TakeSomeonesFolderCounter < b.TakeSomeonesFolderCounter) + texts.Add($"{JournalCounters.TakeSomeonesFolderCounterStr}: {a.TakeSomeonesFolderCounter}->{b.TakeSomeonesFolderCounter}"); + + if (a.NewFolderCounter < b.NewFolderCounter) + texts.Add($"{JournalCounters.NewFolderCounterStr}: {a.NewFolderCounter}->{b.NewFolderCounter}"); + + if (a.RemoveToTrashCounter < b.RemoveToTrashCounter) + texts.Add($"{JournalCounters.RemoveToTrashCounterStr}: {a.RemoveToTrashCounter}->{b.RemoveToTrashCounter}"); + + if (a.RestoreFromTrashCounter < b.RestoreFromTrashCounter) + texts.Add($"{JournalCounters.RestoreFromTrashCounterStr}: {a.RestoreFromTrashCounter}->{b.RestoreFromTrashCounter}"); + + if (a.TrashDropItemCounter < b.TrashDropItemCounter) + texts.Add($"{JournalCounters.TrashDropItemCounterStr}: {a.TrashDropItemCounter}->{b.TrashDropItemCounter}"); + + if (a.TrashDropAllCounter < b.TrashDropAllCounter) + texts.Add($"{JournalCounters.TrashDropAllCounterStr}: {a.TrashDropAllCounter}->{b.TrashDropAllCounter}"); + + if (texts.Count > 0) { + // Обнаружено внешнее изменении на Диске + Logger.Warn($"External activity is detected ({string.Join(", ", texts)})"); // Если между проверками что-то изменились, делаем полный сброс кеша Clear(); return; } + + if (_root.IsEmpty) + { + // Реализуем механизм защиты, на случай, если счетчики серверные так и не догнали увеличение счетчиков локальных + texts.Clear(); + + if (a.RemoveCounter > b.RemoveCounter) + texts.Add($"{JournalCounters.RemoveCounterStr}: {a.RemoveCounter}->{b.RemoveCounter}"); + + if (a.RenameCounter > b.RenameCounter) + texts.Add($"{JournalCounters.RenameCounterStr}: {a.RenameCounter}->{b.RenameCounter}"); + + if (a.MoveCounter > b.MoveCounter) + texts.Add($"{JournalCounters.MoveCounterStr}: {a.MoveCounter}->{b.MoveCounter}"); + + if (a.CopyCounter > b.CopyCounter) + texts.Add($"{JournalCounters.CopyCounterStr}: {a.CopyCounter}->{b.CopyCounter}"); + + if (a.UploadCounter > b.UploadCounter) + texts.Add($"{JournalCounters.UploadCounterStr}: {a.UploadCounter}->{b.UploadCounter}"); + + if (a.TakeSomeonesFolderCounter > b.TakeSomeonesFolderCounter) + texts.Add($"{JournalCounters.TakeSomeonesFolderCounterStr}: {a.TakeSomeonesFolderCounter}->{b.TakeSomeonesFolderCounter}"); + + if (a.NewFolderCounter > b.NewFolderCounter) + texts.Add($"{JournalCounters.NewFolderCounterStr}: {a.NewFolderCounter}->{b.NewFolderCounter}"); + + //if (a.UpdateCounter > b.UpdateCounter) + // texts.Add($"{JournalCounters.UpdateCounterStr}: {a.UpdateCounter}->{b.UpdateCounter}"); + + if (a.RemoveToTrashCounter > b.RemoveToTrashCounter) + texts.Add($"{JournalCounters.RemoveToTrashCounterStr}: {a.RemoveToTrashCounter}->{b.RemoveToTrashCounter}"); + + if (a.RestoreFromTrashCounter > b.RestoreFromTrashCounter) + texts.Add($"{JournalCounters.RestoreFromTrashCounterStr}: {a.RestoreFromTrashCounter}->{b.RestoreFromTrashCounter}"); + + if (a.TrashDropItemCounter > b.TrashDropItemCounter) + texts.Add($"{JournalCounters.TrashDropItemCounterStr}: {a.TrashDropItemCounter}->{b.TrashDropItemCounter}"); + + if (a.TrashDropAllCounter > b.TrashDropAllCounter) + texts.Add($"{JournalCounters.TrashDropAllCounterStr}: {a.TrashDropAllCounter}->{b.TrashDropAllCounter}"); + + if (texts.Count > 0) + { + // Обнаружено внешнее изменении на Диске + Logger.Error($"There is a problem in external activity detection algorithm ({string.Join(", ", texts)})"); + } + + _lastComparedInfoLocker.LockedAction(() => + { + // Локальные счетчики выравниваются по серверным, без увеличения на количество операций данного сервиса + _lastComparedInfo = info.AccountInfo; + + // Выравнивание счетчиков сделано, повторно делать не нужно + _lastCheck = DateTime.MaxValue; + }); + } + + //if (info.AccountInfo.FilesCount != previous.Value.FilesCount || + // info.AccountInfo.Trash != previous.Value.Trash || + // info.AccountInfo.Free != previous.Value.Free) + //{ + // // Если между проверками что-то изменились, делаем полный сброс кеша + // Clear(); + // return; + //} } + bool isEmpty; + List ownOperation = []; + _operationLocker.LockedAction(() => + { + isEmpty = _registeredOperationPath.Count == 0; + foreach (var item in _registeredOperationPath) + { + ownOperation.Add(item.Key); + } + }); + List paths = []; foreach (var op in info.ActiveOperations) { @@ -309,21 +527,36 @@ private async void CheckActiveOps(object sender, System.Timers.ElapsedEventArgs if (paths.Count == 0) return; - await _locker.WaitAsync(); - try + _rootLocker.LockedActionAsync(() => { foreach (var cacheItem in _root) { - if (paths.Any(x => WebDavPath.IsParent(x, cacheItem.Key, selfTrue: true, oneLevelDistanceOnly: false))) + foreach (var operationPath in paths) { - _root.TryRemove(cacheItem.Key, out _); + /* + * Проверяется условие, что entry из кеша + * находится в поддереве пути операции, полученной от сервера, + * при этом исключаются каждое поддерево зарегистрированной собственной операций сервиса. + */ + if (WebDavPath.IsParent(operationPath, cacheItem.Key, selfTrue: true, oneLevelDistanceOnly: false) && + !ownOperation.Any(x => WebDavPath.IsParent(x, cacheItem.Key, selfTrue: true, oneLevelDistanceOnly: false))) + { + _root.TryRemove(cacheItem.Key, out _); + + /* + * После удаления из кеша элемента, затронутого операцией на сервере, + * которая не является собственной операцией сервиса, + * У папки элемента (родительский путь элемента) надо поставить признак, + * что загружены не все элементы. + */ + if (_root.TryGetValue(WebDavPath.Parent(cacheItem.Key), out var parentEntry) && + parentEntry.Entry is Folder fld && + parentEntry.AllDescendantsInCache) + parentEntry.AllDescendantsInCache = false; + } } } - } - finally - { - _locker.Release(); - } + }); } public (IEntry, GetState) Get(string fullPath) @@ -333,7 +566,7 @@ private async void CheckActiveOps(object sender, System.Timers.ElapsedEventArgs IEntry result = default; - _locker.Wait(); + _rootLocker.Wait(); try { if (!_root.TryGetValue(fullPath, out var cachedEntry)) @@ -408,7 +641,7 @@ parentEntry.Entry is Folder parentFolder && } finally { - _locker.Release(); + _rootLocker.Release(); } Logger.Debug($"Cache hit: {fullPath}"); @@ -438,15 +671,7 @@ private void AddInternal(IEntry entry) { if (folder.IsChildrenLoaded) { - _locker.Wait(); - try - { - AddWithChildren(folder, DateTime.Now); - } - finally - { - _locker.Release(); - } + _rootLocker.LockedAction(() => AddWithChildren(folder, DateTime.Now)); } else { @@ -557,55 +782,35 @@ private void AddWithChildren(Folder folder, DateTime creationTime) } } - public async void OnCreateAsync(string fullPath, Task newEntryTask) + public void OnCreate(DateTime operationStartTimestamp, + string createdItemFullPath, Task createdEntryTask, string ignoreItemFullPath) { if (!IsCacheEnabled) return; - IEntry newEntry = newEntryTask is null - ? null - : await newEntryTask; + IEntry createdEntry = createdEntryTask?.Result; - if (newEntry is null) + if (createdEntry is null) { - await _locker.WaitAsync(); - try - { - if (_root.TryRemove(fullPath, out var cachedItem)) - { - // Нового нет, но был, удалить из кеша - if (fullPath != WebDavPath.Root) - { - if (_root.TryGetValue(WebDavPath.Parent(fullPath), out var parentEntry) && - parentEntry.Entry is Folder) - { - parentEntry.AllDescendantsInCache = false; - } - } - } - else - { - // Нового нет, и не было - return; - } - } - finally - { - _locker.Release(); - } + // Если операция должна была создать элемент, но он не получен с сервера, + // это не нормальная ситуация. + // Сбрасываем весь кеш, чтобы все перечитать заново на всякий случай. + Clear(); + return; } else { - await _locker.WaitAsync(); - try + _rootLocker.LockedActionAsync(() => { - bool removed = _root.TryRemove(fullPath, out var cachedItem); + //string itemToCheckUnder = createdItemFullPath; + + bool removed = _root.TryRemove(createdItemFullPath, out var cachedItem); // Добавить новый - if (newEntry is File file) + if (createdEntry is File file) AddInternal(file, DateTime.Now); else - if (newEntry is Folder folder) + if (createdEntry is Folder folder) { /* Данный метод вызывается для созданных и переименованных файлов и папок. * С сервера читали entry самой папки и одного вложенного элемента. @@ -623,12 +828,13 @@ public async void OnCreateAsync(string fullPath, Task newEntryTask) } // После добавления или обновления элемента надо обновить родителя, // если у него AllDescendantsInCache=true, иначе нет смысла - string parent = WebDavPath.Parent(fullPath); - if (fullPath != WebDavPath.Root && + string parent = WebDavPath.Parent(createdItemFullPath); + if (createdItemFullPath != WebDavPath.Root && _root.TryGetValue(parent, out var parentEntry) && - parentEntry.Entry is Folder fld && - parentEntry.AllDescendantsInCache) + parentEntry.Entry is Folder fld && + parentEntry.AllDescendantsInCache) { + //itemToCheckUnder = fld.FullPath; /* * Внимание! У добавляемого в кеш folder список Descendants всегда пустой! * Он специально очищается, чтобы не было соблазна им пользоваться! @@ -648,7 +854,7 @@ parentEntry.Entry is Folder fld && fld.ServerFoldersCount--; } } - if (newEntry.IsFile) + if (createdEntry.IsFile) { if (fld.ServerFilesCount.HasValue) fld.ServerFilesCount++; @@ -659,61 +865,75 @@ parentEntry.Entry is Folder fld && fld.ServerFoldersCount++; } } - } - finally - { - _locker.Release(); - } + + //if (operationStartTimestamp != DateTime.MaxValue) + //{ + // foreach (var cacheItem in _root) + // { + // if (!WebDavPath.IsParent(createdItemFullPath, cacheItem.Key, selfTrue: true, oneLevelDistanceOnly: false) && + // (string.IsNullOrEmpty(ignoreItemFullPath) + // || !WebDavPath.IsParent(ignoreItemFullPath, cacheItem.Key, selfTrue: true, oneLevelDistanceOnly: false)) && + // WebDavPath.IsParent(itemToCheckUnder, cacheItem.Key, selfTrue: true, oneLevelDistanceOnly: false)) + // { + // if (cacheItem.Value.CreationTime > operationStartTimestamp) + // { + // /* + // * Если после начала операции, + // * в дереве элементов ниже родителя созданного элемента + // * появился новый элемент, это означает, + // * что были какие-то параллельные операции. + // * В таком случае на всякий случай лучше сделать полный сброс кеша. + // */ + // ClearNoLock(); + // return; + // } + // } + // } + //} + }); } } - public async void OnRemoveTreeAsync(string fullPath, Task newEntryTask) + public void OnRemoveTree(DateTime operationStartTimestamp, string removedItemFullPath, Task removedEntryTask) { if (!IsCacheEnabled) return; - IEntry newEntry = newEntryTask is null - ? null - : await newEntryTask; + IEntry removedEntry = removedEntryTask?.Result; - if (newEntry is not null) + if (removedEntry is not null) { // Если операция удалила элемент, но он снова получен с сервера, // это не нормальная ситуация. // Сбрасываем весь кеш, чтобы все перечитать заново на всякий случай. - await _locker.WaitAsync(); - try - { - _root.Clear(); - } - finally - { - _locker.Release(); - } + Logger.Error("Cache algorithm failed. Cache is purged."); + Clear(); return; } else { // Нового элемента на сервере нет, // очищаем кеш от элемента и всего, что под ним - await _locker.WaitAsync(); - try + _rootLocker.LockedActionAsync(() => { + //string itemToCheckUnder = removedItemFullPath; + // Если элемент был, а нового нет, надо удалить его у родителя, // чтобы не перечитывать родителя целиком с сервера, // но только, если у родителя AllDescendantsInCache=true, иначе нет смысла - if (fullPath != WebDavPath.Root && - _root.TryGetValue(WebDavPath.Parent(fullPath), out var parentEntry) && - parentEntry.Entry is Folder fld && - parentEntry.AllDescendantsInCache) + if (removedItemFullPath != WebDavPath.Root && + _root.TryGetValue(WebDavPath.Parent(removedItemFullPath), out var parentEntry) && + parentEntry.Entry is Folder fld && + parentEntry.AllDescendantsInCache) { + //itemToCheckUnder = fld.FullPath; /* * Внимание! У добавляемого в кеш folder список Descendants всегда пустой! * Он специально очищается, чтобы не было соблазна им пользоваться! * Содержимое папки берется не из этого списка, а собирается из кеша по path всех entry. * В кеше у папок Descendants всегда = ImmutableList.Empty */ - if (_root.TryRemove(fullPath, out var cachedItem)) + if (_root.TryRemove(removedItemFullPath, out var cachedItem)) { if (cachedItem.Entry.IsFile) { @@ -730,7 +950,24 @@ parentEntry.Entry is Folder fld && foreach (var cacheItem in _root) { - if (WebDavPath.IsParent(fullPath, cacheItem.Key, selfTrue: true, oneLevelDistanceOnly: false)) + //if (operationStartTimestamp != DateTime.MaxValue && + // WebDavPath.IsParent(itemToCheckUnder, cacheItem.Key, selfTrue: true, oneLevelDistanceOnly: false)) + //{ + // if (cacheItem.Value.CreationTime > operationStartTimestamp) + // { + // /* + // * Если после начала операции, + // * в дереве элементов ниже родителя удаленного элемента + // * появился новый элемент, это означает, + // * что были какие-то параллельные операции. + // * В таком случае на всякий случай лучше сделать полный сброс кеша. + // */ + // ClearNoLock(); + // return; + // } + //} + + if (WebDavPath.IsParent(removedItemFullPath, cacheItem.Key, selfTrue: true, oneLevelDistanceOnly: false)) { cacheItem.Value.CreationTime = DateTime.MinValue; } @@ -744,12 +981,8 @@ parentEntry.Entry is Folder fld && AllDescendantsInCache = true, CreationTime = DateTime.Now }; - _root.TryAdd(fullPath, deletedItem); - } - finally - { - _locker.Release(); - } + _root.TryAdd(removedItemFullPath, deletedItem); + }); } } @@ -758,8 +991,7 @@ public void RemoveOne(string fullPath) if (!IsCacheEnabled) return; - _locker.Wait(); - try + _rootLocker.LockedAction(() => { _root.TryRemove(fullPath, out _); @@ -768,11 +1000,7 @@ public void RemoveOne(string fullPath) { parentEntry.AllDescendantsInCache = false; } - } - finally - { - _locker.Release(); - } + }); } public void RemoveTree(string fullPath) @@ -780,15 +1008,7 @@ public void RemoveTree(string fullPath) if (!IsCacheEnabled) return; - _locker.Wait(); - try - { - RemoveTreeNoLock(fullPath); - } - finally - { - _locker.Release(); - } + _rootLocker.LockedAction(() => RemoveTreeNoLock(fullPath)); } private void RemoveTreeNoLock(string fullPath) @@ -814,14 +1034,12 @@ public void Clear() if (!IsCacheEnabled) return; - _locker.Wait(); - try - { - _root.Clear(); - } - finally - { - _locker.Release(); - } + _rootLocker.LockedAction(() => ClearNoLock()); + } + + public void ClearNoLock() + { + _root.Clear(); + Logger.Debug($"Cache is cleared"); } } diff --git a/MailRuCloud/MailRuCloudApi/Extensions/Extensions.cs b/MailRuCloud/MailRuCloudApi/Extensions/Extensions.cs index 9562edba..4418514d 100644 --- a/MailRuCloud/MailRuCloudApi/Extensions/Extensions.cs +++ b/MailRuCloud/MailRuCloudApi/Extensions/Extensions.cs @@ -1,314 +1,345 @@ using System; using System.Collections.Generic; -using System.Linq; +using System.Threading; using YaR.Clouds.Base; -namespace YaR.Clouds.Extensions +namespace YaR.Clouds.Extensions; + +public static class Extensions { - public static class Extensions + internal static IEnumerable ToPublicLinkInfos(this string uriString, string publicBaseUrl) { - internal static IEnumerable ToPublicLinkInfos(this string uriString, string publicBaseUrl) - { - if (!string.IsNullOrEmpty(uriString)) - yield return new PublicLinkInfo("", publicBaseUrl, uriString); - } + if (!string.IsNullOrEmpty(uriString)) + yield return new PublicLinkInfo("", publicBaseUrl, uriString); + } - internal static DateTime ToDateTime(this ulong unixTimeStamp) - { - var dtDateTime = Epoch.AddSeconds(unixTimeStamp); - return dtDateTime; - } + internal static DateTime ToDateTime(this ulong unixTimeStamp) + { + var dtDateTime = Epoch.AddSeconds(unixTimeStamp); + return dtDateTime; + } - internal static long ToUnix(this DateTime date) - { - TimeSpan diff = date.ToUniversalTime() - Epoch; + internal static long ToUnix(this DateTime date) + { + TimeSpan diff = date.ToUniversalTime() - Epoch; - long seconds = diff.Ticks / TimeSpan.TicksPerSecond; - return seconds; - } - private static readonly DateTime Epoch = new(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); + long seconds = diff.Ticks / TimeSpan.TicksPerSecond; + return seconds; + } + private static readonly DateTime Epoch = new(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); - internal static byte[] HexStringToByteArray(this string hex) - { - int len = hex.Length; - byte[] bytes = new byte[len / 2]; - for (int i = 0; i < len; i += 2) - bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16); - return bytes; - } + internal static byte[] HexStringToByteArray(this string hex) + { + int len = hex.Length; + byte[] bytes = new byte[len / 2]; + for (int i = 0; i < len; i += 2) + bytes[i / 2] = Convert.ToByte(hex.Substring(i, 2), 16); + return bytes; + } - public static string ToHexString(this byte[] ba) - { - string hex = BitConverter.ToString(ba); - //return hex; - return hex.Replace("-", ""); - } + public static string ToHexString(this byte[] ba) + { + string hex = BitConverter.ToString(ba); + //return hex; + return hex.Replace("-", ""); + } - //public static string ReadAsText(this WebResponse resp, CancellationTokenSource cancelToken) - //{ - // using (var stream = new MemoryStream()) - // { - // try - // { - // resp.ReadAsByte(cancelToken.Token, stream); - // return Encoding.UTF8.GetString(stream.ToArray()); - // } - // catch - // { - // //// Cancellation token. - // return "7035ba55-7d63-4349-9f73-c454529d4b2e"; - // } - // } - //} - - //public static void ReadAsByte(this WebResponse resp, CancellationToken token, Stream outputStream = null) - //{ - // using Stream responseStream = resp.GetResponseStream(); - // var buffer = new byte[65536]; - // int bytesRead; - - // while (responseStream != null && (bytesRead = responseStream.Read(buffer, 0, buffer.Length)) > 0) - // { - // token.ThrowIfCancellationRequested(); - // outputStream?.Write(buffer, 0, bytesRead); - // } - //} - - public static T ThrowIf(this T data, Func func, Func ex) - { - if (func(data)) throw ex(data); - return data; - } + //public static string ReadAsText(this WebResponse resp, CancellationTokenSource cancelToken) + //{ + // using (var stream = new MemoryStream()) + // { + // try + // { + // resp.ReadAsByte(cancelToken.Token, stream); + // return Encoding.UTF8.GetString(stream.ToArray()); + // } + // catch + // { + // //// Cancellation token. + // return "7035ba55-7d63-4349-9f73-c454529d4b2e"; + // } + // } + //} + + //public static void ReadAsByte(this WebResponse resp, CancellationToken token, Stream outputStream = null) + //{ + // using Stream responseStream = resp.GetResponseStream(); + // var buffer = new byte[65536]; + // int bytesRead; + + // while (responseStream != null && (bytesRead = responseStream.Read(buffer, 0, buffer.Length)) > 0) + // { + // token.ThrowIfCancellationRequested(); + // outputStream?.Write(buffer, 0, bytesRead); + // } + //} + + public static T ThrowIf(this T data, Func func, Func ex) + { + if (func(data)) throw ex(data); + return data; + } - //public static T ThrowIf(this Task data, Func func, Exception ex) - //{ - // var res = data.Result; - // if (func(res)) throw ex; - // return res; - //} - - - /// - /// Finds the first exception of the requested type. - /// - /// - /// The type of exception to return - /// - /// - /// The exception to look in. - /// - /// - /// The exception or the first inner exception that matches the - /// given type; null if not found. - /// - public static T InnerOf(this Exception ex) - where T : Exception - { - return (T)InnerOf(ex, typeof(T)); - } + //public static T ThrowIf(this Task data, Func func, Exception ex) + //{ + // var res = data.Result; + // if (func(res)) throw ex; + // return res; + //} + + + /// + /// Finds the first exception of the requested type. + /// + /// + /// The type of exception to return + /// + /// + /// The exception to look in. + /// + /// + /// The exception or the first inner exception that matches the + /// given type; null if not found. + /// + public static T InnerOf(this Exception ex) + where T : Exception + { + return (T)InnerOf(ex, typeof(T)); + } - /// - /// Finds the first exception of the requested type. - /// - /// - /// The exception to look in. - /// - /// - /// The type of exception to return - /// - /// - /// The exception or the first inner exception that matches the - /// given type; null if not found. - /// - public static Exception InnerOf(this Exception ex, Type t) + /// + /// Finds the first exception of the requested type. + /// + /// + /// The exception to look in. + /// + /// + /// The type of exception to return + /// + /// + /// The exception or the first inner exception that matches the + /// given type; null if not found. + /// + public static Exception InnerOf(this Exception ex, Type t) + { + while (true) { - while (true) - { - if (ex == null || t.IsInstanceOfType(ex)) return ex; + if (ex == null || t.IsInstanceOfType(ex)) return ex; - if (ex is AggregateException ae) + if (ex is AggregateException ae) + { + foreach (var e in ae.InnerExceptions) { - foreach (var e in ae.InnerExceptions) - { - var ret = InnerOf(e, t); - if (ret != null) return ret; - } + var ret = InnerOf(e, t); + if (ret != null) return ret; } - - ex = ex.InnerException; } + + ex = ex.InnerException; } + } - public static bool ContainsIgnoreCase(this string stringSearchIn, string stringToSearchFor, - StringComparison comparisonType = StringComparison.InvariantCultureIgnoreCase) - { + public static bool ContainsIgnoreCase(this string stringSearchIn, string stringToSearchFor, + StringComparison comparisonType = StringComparison.InvariantCultureIgnoreCase) + { #if NET48 - System.Globalization.CultureInfo cu = comparisonType == StringComparison.InvariantCultureIgnoreCase - ? System.Globalization.CultureInfo.InvariantCulture - : System.Globalization.CultureInfo.CurrentCulture; - return cu.CompareInfo.IndexOf(stringSearchIn, stringToSearchFor) >= 0; + System.Globalization.CultureInfo cu = comparisonType == StringComparison.InvariantCultureIgnoreCase + ? System.Globalization.CultureInfo.InvariantCulture + : System.Globalization.CultureInfo.CurrentCulture; + return cu.CompareInfo.IndexOf(stringSearchIn, stringToSearchFor) >= 0; #else - return stringSearchIn.Contains(stringToSearchFor, comparisonType); + return stringSearchIn.Contains(stringToSearchFor, comparisonType); #endif - } + } - /// Finds all exception of the requested type. - /// The type of exception to look for. - /// The exception to look in. - /// - /// - /// The enumeration of exceptions matching the specified type or null if not found. - /// - /// - /// - public static IEnumerable OfType(this Exception exception) where T : Exception - { - return exception.OfType(false); - } + /// Finds all exception of the requested type. + /// The type of exception to look for. + /// The exception to look in. + /// + /// + /// The enumeration of exceptions matching the specified type or null if not found. + /// + /// + /// + public static IEnumerable OfType(this Exception exception) where T : Exception + { + return exception.OfType(false); + } - /// Finds all exception of the requested type. - /// The type of exception to look for. - /// The exception to look in. - /// - /// - /// If throwIfNotNotFound=false, - /// the enumeration of exceptions matching the specified type or null if not found. - /// - /// - /// If throwIfNotNotFound=true, - /// the enumeration of exceptions matching the specified type or throws the flatten AggregateException if not found. - /// - /// - public static IEnumerable OfType(this Exception exception, bool throwIfNotNotFound = false) where T : Exception + /// Finds all exception of the requested type. + /// The type of exception to look for. + /// The exception to look in. + /// + /// + /// If throwIfNotNotFound=false, + /// the enumeration of exceptions matching the specified type or null if not found. + /// + /// + /// If throwIfNotNotFound=true, + /// the enumeration of exceptions matching the specified type or throws the flatten AggregateException if not found. + /// + /// + public static IEnumerable OfType(this Exception exception, bool throwIfNotNotFound = false) where T : Exception + { + bool found = false; + if (exception is not null) { - bool found = false; - if (exception is not null) + if (exception is T ex1) { - if (exception is T ex1) - { - found = true; - yield return ex1; - } + found = true; + yield return ex1; + } - if (exception is AggregateException ae) + if (exception is AggregateException ae) + { + for (int index = 0; index < ae.InnerExceptions.Count; index++) { - for (int index = 0; index < ae.InnerExceptions.Count; index++) + Exception innerEx = ae.InnerExceptions[index]; + if (innerEx is T ex2) + { + found = true; + yield return ex2; + } + if (innerEx.InnerException is not null) { - Exception innerEx = ae.InnerExceptions[index]; - if (innerEx is T ex2) + foreach (var item in innerEx.OfType(throwIfNotNotFound)) { found = true; - yield return ex2; - } - if (innerEx.InnerException is not null) - { - foreach (var item in innerEx.OfType(throwIfNotNotFound)) - { - found = true; - yield return item; - }; - } + yield return item; + }; } } - else - if (exception.InnerException.InnerException is not null) + } + else + if (exception.InnerException.InnerException is not null) + { + foreach (var item in exception.InnerException.OfType(throwIfNotNotFound)) { - foreach (var item in exception.InnerException.OfType(throwIfNotNotFound)) - { - found = true; - yield return item; - }; - } + found = true; + yield return item; + }; } - - if (!found && throwIfNotNotFound) - throw exception; } - /// Finds first occurrence of exception of the requested type. - /// The type of exception to look for. - /// The exception to look in. - /// - /// - /// The exception matching the specified type or null if not found. - /// - /// - public static T FirstOfType(this Exception exception) where T : Exception - { - return exception.FirstOfType(false); - } + if (!found && throwIfNotNotFound) + throw exception; + } - /// Finds first occurrence of exception of the requested type. - /// The type of exception to look for. - /// The exception to look in. - /// - /// - /// If throwIfNotNotFound=false, - /// the exception matching the specified type or null if not found. - /// - /// - /// If throwIfNotNotFound=true, - /// the exceptions matching the specified type or throws the flatten AggregateException if not found. - /// - /// - public static T FirstOfType(this Exception exception, bool throwIfNotNotFound = false) where T : Exception - { - if (exception is null) - return null; + /// Finds first occurrence of exception of the requested type. + /// The type of exception to look for. + /// The exception to look in. + /// + /// + /// The exception matching the specified type or null if not found. + /// + /// + public static T FirstOfType(this Exception exception) where T : Exception + { + return exception.FirstOfType(false); + } - if (exception is T ex1) - return ex1; + /// Finds first occurrence of exception of the requested type. + /// The type of exception to look for. + /// The exception to look in. + /// + /// + /// If throwIfNotNotFound=false, + /// the exception matching the specified type or null if not found. + /// + /// + /// If throwIfNotNotFound=true, + /// the exceptions matching the specified type or throws the flatten AggregateException if not found. + /// + /// + public static T FirstOfType(this Exception exception, bool throwIfNotNotFound = false) where T : Exception + { + if (exception is null) + return null; - if (exception is AggregateException ae) + if (exception is T ex1) + return ex1; + + if (exception is AggregateException ae) + { + for (int index = 0; index < ae.InnerExceptions.Count; index++) { - for (int index = 0; index < ae.InnerExceptions.Count; index++) - { - Exception innerEx = ae.InnerExceptions[index]; - if (innerEx.FirstOfType() is T ex2) - return ex2; - } + Exception innerEx = ae.InnerExceptions[index]; + if (innerEx.FirstOfType() is T ex2) + return ex2; } - else - if (exception.InnerException.FirstOfType() is T ex3) - return ex3; + } + else + if (exception.InnerException.FirstOfType() is T ex3) + return ex3; - if (throwIfNotNotFound) - throw exception; + if (throwIfNotNotFound) + throw exception; - return null; - } + return null; + } - /// Searches through all inner exceptions for exception of the requested type and returns true if found. - /// The type of exception to look for. - /// The exception to look in. - /// - /// - /// True if the exception of the requested type is found, otherwise False. - /// - /// - public static bool Contains(this Exception exception) where T : Exception - { - if(exception is null ) - return false; + /// Searches through all inner exceptions for exception of the requested type and returns true if found. + /// The type of exception to look for. + /// The exception to look in. + /// + /// + /// True if the exception of the requested type is found, otherwise False. + /// + /// + public static bool Contains(this Exception exception) where T : Exception + { + if (exception is null) + return false; - if (exception is T) - return true; + if (exception is T) + return true; - if (exception is AggregateException ae) + if (exception is AggregateException ae) + { + for (int index = 0; index < ae.InnerExceptions.Count; index++) { - for (int index = 0; index < ae.InnerExceptions.Count; index++) - { - Exception innerEx = ae.InnerExceptions[index]; - if (innerEx.Contains()) - return true; - } + Exception innerEx = ae.InnerExceptions[index]; + if (innerEx.Contains()) + return true; } - else - if (exception.InnerException.Contains()) - return true; + } + else + if (exception.InnerException.Contains()) + return true; - return false; + return false; + } + + public static void LockedAction(this SemaphoreSlim semaphore, Action action) + { + if (action is null) + return; + + semaphore.Wait(); + try + { + action(); + } + finally + { + semaphore.Release(); + } + } + + public static async void LockedActionAsync(this SemaphoreSlim semaphore, Action action) + { + if (action is null) + return; + + await semaphore.WaitAsync(); + try + { + action(); + } + finally + { + semaphore.Release(); } } } diff --git a/MailRuCloud/MailRuCloudApi/Links/LinkManager.cs b/MailRuCloud/MailRuCloudApi/Links/LinkManager.cs index ca2af920..e35da1d9 100644 --- a/MailRuCloud/MailRuCloudApi/Links/LinkManager.cs +++ b/MailRuCloud/MailRuCloudApi/Links/LinkManager.cs @@ -169,7 +169,7 @@ public bool RemoveLink(string path, bool doSave = true) return false; _itemList.Items.Remove(z); - _linkCache.OnRemoveTreeAsync(path, null); + _linkCache.OnRemoveTree(DateTime.MaxValue, path, null); if (doSave) Save(); diff --git a/NWebDav/NWebDav.Server/NWebDav.Server.csproj b/NWebDav/NWebDav.Server/NWebDav.Server.csproj index 091408f0..b31de73e 100644 --- a/NWebDav/NWebDav.Server/NWebDav.Server.csproj +++ b/NWebDav/NWebDav.Server/NWebDav.Server.csproj @@ -29,6 +29,70 @@ false + + $(WarningsAsErrors);NU1605;CAA1510; + + + + $(WarningsAsErrors);NU1605;CAA1510; + + + + $(WarningsAsErrors);NU1605;CAA1510; + + + + $(WarningsAsErrors);NU1605;CAA1510; + + + + $(WarningsAsErrors);NU1605;CAA1510; + + + + $(WarningsAsErrors);NU1605;CAA1510; + + + + $(WarningsAsErrors);NU1605;CAA1510; + + + + $(WarningsAsErrors);NU1605;CAA1510; + + + + $(WarningsAsErrors);NU1605;CAA1510; + + + + $(WarningsAsErrors);NU1605;CAA1510; + + + + $(WarningsAsErrors);NU1605;CAA1510; + + + + $(WarningsAsErrors);NU1605;CAA1510; + + + + $(WarningsAsErrors);NU1605;CAA1510; + + + + $(WarningsAsErrors);NU1605;CAA1510; + + + + $(WarningsAsErrors);NU1605;CAA1510; + + + + $(WarningsAsErrors);NU1605;CAA1510; + + diff --git a/WDMRC.Console/WDMRC.Console.csproj b/WDMRC.Console/WDMRC.Console.csproj index 4f96fe2f..fa3a0d11 100644 --- a/WDMRC.Console/WDMRC.Console.csproj +++ b/WDMRC.Console/WDMRC.Console.csproj @@ -4,7 +4,7 @@ $(NoWarn);NU1605 - + Exe $(CommonTargetFrameworks) false @@ -13,7 +13,7 @@ YaR WebDAVCloudMailRu - MIT License, Copyright (c) 2023 YaR229 and Contributors + MIT License, Copyright (c) 2024 YaR229 and Contributors WebDAV emulator for RU-clouds: Cloud.Mail.Ru & Disk.Yandex.Ru WebDAVCloudMailRu $(ReleaseVersion) @@ -42,7 +42,7 @@ - + diff --git a/WebDAV.Uploader/UploadStub.cs b/WebDAV.Uploader/UploadStub.cs index 21fd1868..34f35bf4 100644 --- a/WebDAV.Uploader/UploadStub.cs +++ b/WebDAV.Uploader/UploadStub.cs @@ -63,15 +63,13 @@ public static int Upload(UploadOptions cmdOptions) source.Seek(0, SeekOrigin.Begin); var buffer = new byte[64000]; long wrote = 0; - using (var target = cloud.GetFileUploadStream(WebDavPath.Combine(targetfile, fileInfo.Name), fileInfo.Length, null, null).Result) + using var target = cloud.GetFileUploadStream(WebDavPath.Combine(targetfile, fileInfo.Name), fileInfo.Length, null, null).Result; + int read; + while ((read = source.Read(buffer, 0, buffer.Length)) > 0) { - int read; - while ((read = source.Read(buffer, 0, buffer.Length)) > 0) - { - target.Write(buffer, 0, read); - wrote += read; - System.Console.Write($"\r{wrote / fileInfo.Length * 100}%"); - } + target.Write(buffer, 0, read); + wrote += read; + System.Console.Write($"\r{wrote / fileInfo.Length * 100}%"); } } diff --git a/WebDavMailRuCloudStore/Extensions.cs b/WebDavMailRuCloudStore/Extensions.cs index 8fb17458..a4f4838d 100644 --- a/WebDavMailRuCloudStore/Extensions.cs +++ b/WebDavMailRuCloudStore/Extensions.cs @@ -4,64 +4,63 @@ using NWebDav.Server.Stores; using YaR.Clouds.WebDavStore.StoreBase; -namespace YaR.Clouds.WebDavStore +namespace YaR.Clouds.WebDavStore; + +internal static class Extensions { - internal static class Extensions + public static async Task Remove(this Cloud cloud, IStoreItem item) { - public static async Task Remove(this Cloud cloud, IStoreItem item) - { - return item switch - { - null => await Task.FromResult(false), - LocalStoreItem storeItem => await cloud.Remove(storeItem.FileInfo), - LocalStoreCollection storeCollection => await cloud.Remove(storeCollection.FolderWithDescendants), - _ => throw new ArgumentException(string.Empty, nameof(item)) - }; - } - - public static Task Rename(this Cloud cloud, IStoreItem item, string destinationName) + return item switch { - if (item == null) - throw new ArgumentNullException(nameof(item)); - if (string.IsNullOrEmpty(destinationName)) - throw new ArgumentNullException(nameof(destinationName)); - if (item is not ILocalStoreItem storeItem) - throw new ArgumentException($"{nameof(ILocalStoreItem)} required.", nameof(item)); + null => await Task.FromResult(false), + LocalStoreItem storeItem => await cloud.Remove(storeItem.FileInfo), + LocalStoreCollection storeCollection => await cloud.Remove(storeCollection.FolderWithDescendants), + _ => throw new ArgumentException(string.Empty, nameof(item)) + }; + } - return cloud.Rename(storeItem.EntryInfo, destinationName); - } + public static Task Rename(this Cloud cloud, IStoreItem item, string destinationName) + { + if (item == null) + throw new ArgumentNullException(nameof(item)); + if (string.IsNullOrEmpty(destinationName)) + throw new ArgumentNullException(nameof(destinationName)); + if (item is not ILocalStoreItem storeItem) + throw new ArgumentException($"{nameof(ILocalStoreItem)} required.", nameof(item)); - public static Task Move(this Cloud cloud, IStoreItem item, string destinationPath) - { - if (item == null) - throw new ArgumentNullException(nameof(item)); - if (string.IsNullOrEmpty(destinationPath)) - throw new ArgumentNullException(nameof(destinationPath)); - if (item is not ILocalStoreItem storeItem) - throw new ArgumentException($"{nameof(ILocalStoreItem)} required.", nameof(item)); + return cloud.Rename(storeItem.EntryInfo, destinationName); + } - return cloud.MoveAsync(storeItem.EntryInfo, destinationPath); - } - public static string GetFullPath(this IStoreItem item) - { - if (item == null) - throw new ArgumentNullException(nameof(item)); - if (item is not ILocalStoreItem storeItem) - throw new ArgumentException($"{nameof(ILocalStoreItem)} required.", nameof(item)); + public static Task Move(this Cloud cloud, IStoreItem item, string destinationPath) + { + if (item == null) + throw new ArgumentNullException(nameof(item)); + if (string.IsNullOrEmpty(destinationPath)) + throw new ArgumentNullException(nameof(destinationPath)); + if (item is not ILocalStoreItem storeItem) + throw new ArgumentException($"{nameof(ILocalStoreItem)} required.", nameof(item)); - return storeItem.FullPath; - } + return cloud.MoveAsync(storeItem.EntryInfo, destinationPath); + } + public static string GetFullPath(this IStoreItem item) + { + if (item == null) + throw new ArgumentNullException(nameof(item)); + if (item is not ILocalStoreItem storeItem) + throw new ArgumentException($"{nameof(ILocalStoreItem)} required.", nameof(item)); + return storeItem.FullPath; + } - public static long ContentLength(this IHttpRequest request) - { - long.TryParse(request.GetHeaderValue("Content-Length"), out var res); - return res; - } - //public static long BytesCount(this string value) - //{ - // return Encoding.UTF8.GetByteCount(value); - //} + public static long ContentLength(this IHttpRequest request) + { + long.TryParse(request.GetHeaderValue("Content-Length"), out var res); + return res; } + + //public static long BytesCount(this string value) + //{ + // return Encoding.UTF8.GetByteCount(value); + //} } diff --git a/WebDavMailRuCloudStore/StoreBase/LocalStoreItem.cs b/WebDavMailRuCloudStore/StoreBase/LocalStoreItem.cs index b59deee0..1f3bb5f9 100644 --- a/WebDavMailRuCloudStore/StoreBase/LocalStoreItem.cs +++ b/WebDavMailRuCloudStore/StoreBase/LocalStoreItem.cs @@ -88,13 +88,13 @@ public async Task UploadFromStreamAsync(IHttpContext httpContext, FileInfo.OriginalSize = new FileSize(memStream.Length); - using (var outputStream = IsWritable - ? await CloudManager.Instance(httpContext.Session.Principal.Identity).GetFileUploadStream(FileInfo.FullPath, FileInfo.Size, null, null).ConfigureAwait(false) - : null) - { - memStream.Seek(0, SeekOrigin.Begin); - await memStream.CopyToAsync(outputStream).ConfigureAwait(false); - } + using var outputStream = IsWritable + ? await CloudManager + .Instance(httpContext.Session.Principal.Identity) + .GetFileUploadStream(FileInfo.FullPath, FileInfo.Size, null, null).ConfigureAwait(false) + : null; + memStream.Seek(0, SeekOrigin.Begin); + await memStream.CopyToAsync(outputStream).ConfigureAwait(false); return DavStatusCode.Ok; } From 646bf7d9cf1bb46e59058b1f743c9e083079cb10 Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Sat, 28 Sep 2024 20:18:30 +0300 Subject: [PATCH 63/77] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB?= =?UTF-8?q?=D0=B5=D0=BD=D0=B8=D0=B5=20=D1=81=D0=B1=D0=BE=D1=80=D0=BE=D0=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BrowserAuthenticator/BrowserAuthenticator.csproj | 2 +- MailRuCloud/MailRuCloudApi/YaR.Clouds.csproj | 4 ++-- WebDavMailRuCloudStore/WebDavStore.csproj | 2 +- WinServiceInstaller/WinServiceInstaller.csproj | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/BrowserAuthenticator/BrowserAuthenticator.csproj b/BrowserAuthenticator/BrowserAuthenticator.csproj index f1c731e2..06b35203 100644 --- a/BrowserAuthenticator/BrowserAuthenticator.csproj +++ b/BrowserAuthenticator/BrowserAuthenticator.csproj @@ -30,7 +30,7 @@ - + diff --git a/MailRuCloud/MailRuCloudApi/YaR.Clouds.csproj b/MailRuCloud/MailRuCloudApi/YaR.Clouds.csproj index c69b93b1..9262fd06 100644 --- a/MailRuCloud/MailRuCloudApi/YaR.Clouds.csproj +++ b/MailRuCloud/MailRuCloudApi/YaR.Clouds.csproj @@ -30,8 +30,8 @@ - - + + diff --git a/WebDavMailRuCloudStore/WebDavStore.csproj b/WebDavMailRuCloudStore/WebDavStore.csproj index 05a8376c..5c0277ae 100644 --- a/WebDavMailRuCloudStore/WebDavStore.csproj +++ b/WebDavMailRuCloudStore/WebDavStore.csproj @@ -34,7 +34,7 @@ - + diff --git a/WinServiceInstaller/WinServiceInstaller.csproj b/WinServiceInstaller/WinServiceInstaller.csproj index d4bbbe13..8a03d8a1 100644 --- a/WinServiceInstaller/WinServiceInstaller.csproj +++ b/WinServiceInstaller/WinServiceInstaller.csproj @@ -55,7 +55,7 @@ - + From d97caef398621a94cb5f08dbd37144a1524ef097 Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Sat, 28 Sep 2024 21:03:45 +0300 Subject: [PATCH 64/77] =?UTF-8?q?=D0=92=D0=B5=D1=80=D1=81=D0=B8=D1=8F=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=B4=D0=BD=D1=8F=D1=82=D0=B0=20=D0=B4=D0=BE=201.1?= =?UTF-8?q?5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Common.targets | 2 +- .../MailRuCloudApi/Base/Credentials.cs | 6 ++- readme.md | 47 +++++++++---------- 3 files changed, 26 insertions(+), 29 deletions(-) diff --git a/Common.targets b/Common.targets index 203662c6..2c2b3c27 100644 --- a/Common.targets +++ b/Common.targets @@ -2,6 +2,6 @@ net8.0-windows;net7.0-windows;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0 latest - 1.14.2.1 + 1.15.0.0 \ No newline at end of file diff --git a/MailRuCloud/MailRuCloudApi/Base/Credentials.cs b/MailRuCloud/MailRuCloudApi/Base/Credentials.cs index a4f2f893..5aa85b1f 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Credentials.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Credentials.cs @@ -559,8 +559,10 @@ private async Task MakeLogin() else { string text = string.IsNullOrEmpty(response?.ErrorMessage) - ? "Authentication using BrowserAuthenticator application is failed!" - : string.Concat("Authentication using BrowserAuthenticator application is failed! ", response.ErrorMessage); + ? "Authentication using BrowserAuthenticator application is failed! " + + "Check password for the BrowserAuthenticator!" + : string.Concat("Authentication using BrowserAuthenticator application is failed! " + + "Check password for the BrowserAuthenticator! ", response.ErrorMessage); Logger.Error(text); throw new AuthenticationException(text); diff --git a/readme.md b/readme.md index a2b2a8e3..464d448d 100644 --- a/readme.md +++ b/readme.md @@ -18,33 +18,28 @@ The fork project Date: Sat, 28 Sep 2024 21:03:45 +0300 Subject: [PATCH 65/77] =?UTF-8?q?=D0=92=D0=B5=D1=80=D1=81=D0=B8=D1=8F=20?= =?UTF-8?q?=D0=BF=D0=BE=D0=B4=D0=BD=D1=8F=D1=82=D0=B0=20=D0=B4=D0=BE=201.1?= =?UTF-8?q?5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 7z release.cmd | 2 +- Common.targets | 2 +- .../MailRuCloudApi/Base/Credentials.cs | 6 ++- readme.md | 47 +++++++++---------- 4 files changed, 27 insertions(+), 30 deletions(-) diff --git a/7z release.cmd b/7z release.cmd index c43ee567..0c0d2fc6 100644 --- a/7z release.cmd +++ b/7z release.cmd @@ -1,4 +1,4 @@ -set ver=1.14.2.1 +set ver=1.15 set options=-tzip -mx9 -r -sse -x!*.pdb -x!*dev* "C:\Program Files\7-Zip\7z.exe" a %options% "BrowserAuthenticator\bin\Release\BrowserAuthenticator-%ver%-net7.0-windows.zip" ".\BrowserAuthenticator\bin\Release\net7.0-windows\*" diff --git a/Common.targets b/Common.targets index 203662c6..2c2b3c27 100644 --- a/Common.targets +++ b/Common.targets @@ -2,6 +2,6 @@ net8.0-windows;net7.0-windows;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0 latest - 1.14.2.1 + 1.15.0.0 \ No newline at end of file diff --git a/MailRuCloud/MailRuCloudApi/Base/Credentials.cs b/MailRuCloud/MailRuCloudApi/Base/Credentials.cs index a4f2f893..5aa85b1f 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Credentials.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Credentials.cs @@ -559,8 +559,10 @@ private async Task MakeLogin() else { string text = string.IsNullOrEmpty(response?.ErrorMessage) - ? "Authentication using BrowserAuthenticator application is failed!" - : string.Concat("Authentication using BrowserAuthenticator application is failed! ", response.ErrorMessage); + ? "Authentication using BrowserAuthenticator application is failed! " + + "Check password for the BrowserAuthenticator!" + : string.Concat("Authentication using BrowserAuthenticator application is failed! " + + "Check password for the BrowserAuthenticator! ", response.ErrorMessage); Logger.Error(text); throw new AuthenticationException(text); diff --git a/readme.md b/readme.md index a2b2a8e3..464d448d 100644 --- a/readme.md +++ b/readme.md @@ -18,33 +18,28 @@ The fork project Date: Thu, 31 Oct 2024 18:59:01 +0300 Subject: [PATCH 66/77] =?UTF-8?q?'=3FJohn@yandex.ru'=20=D0=B8=20'John@yand?= =?UTF-8?q?ex.ru'=20=D1=82=D0=B5=D0=BF=D0=B5=D1=80=D1=8C=20=D0=BD=D0=B5=20?= =?UTF-8?q?=D1=81=D0=BE=D0=B7=D0=B4=D0=B0=D1=8E=D1=82=20=D0=B4=D0=B2=D0=B0?= =?UTF-8?q?=20=D1=80=D0=B0=D0=B7=D0=BD=D1=8B=D1=85=20=D0=BE=D0=B1=D1=80?= =?UTF-8?q?=D0=B0=D0=B1=D0=BE=D1=82=D1=87=D0=B8=D0=BA=D0=B0,=20=D0=BA?= =?UTF-8?q?=D0=BE=D1=82=D0=BE=D1=80=D1=8B=D0=B5=20=D0=B2=D0=B7=D0=B0=D0=B8?= =?UTF-8?q?=D0=BC=D0=BD=D0=BE=20=D1=80=D0=B0=D1=81=D1=81=D0=BC=D0=B0=D1=82?= =?UTF-8?q?=D1=80=D0=B8=D0=B2=D0=B0=D1=8E=D1=82=D1=81=D1=8F=20=D0=BA=D0=B0?= =?UTF-8?q?=D0=BA=20=D0=B2=D0=BD=D0=B5=D1=88=D0=BD=D1=8F=D1=8F=20=D0=B0?= =?UTF-8?q?=D0=BA=D1=82=D0=B8=D0=B2=D0=BD=D0=BE=D1=81=D1=82=D1=8C=20=D0=BD?= =?UTF-8?q?=D0=B0=20=D0=94=D0=B8=D1=81=D0=BA=D0=B5,=20=D0=BF=D1=80=D0=B8?= =?UTF-8?q?=D0=B2=D0=BE=D0=B4=D1=8F=D1=89=D0=B0=D1=8F=20=D0=BA=20=D1=81?= =?UTF-8?q?=D0=B1=D1=80=D0=BE=D1=81=D1=83=20=D0=BA=D1=8D=D1=88=D0=B0.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- WebDavMailRuCloudStore/CloudManager.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/WebDavMailRuCloudStore/CloudManager.cs b/WebDavMailRuCloudStore/CloudManager.cs index 43b3c5eb..02644e9f 100644 --- a/WebDavMailRuCloudStore/CloudManager.cs +++ b/WebDavMailRuCloudStore/CloudManager.cs @@ -41,7 +41,8 @@ static CloudManager() public static Cloud Instance(IIdentity identity) { var basicIdentity = (HttpListenerBasicIdentity)identity; - string key = basicIdentity.Name + basicIdentity.Password; + // Ключ не должен зависеть от специальных сиволов, указывающих на способ аутентификации + string key = basicIdentity.Name.TrimStart(['?', '!']) + basicIdentity.Password; _dictionaryLocker.Wait(); try From 6913ec9a3c463d9fcd9aeaf4a3a7b591e4841dd8 Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Thu, 31 Oct 2024 19:09:51 +0300 Subject: [PATCH 67/77] Yandex has API version 383.0.0. Folder listing and folder creation if fixed. Support of netcoreapp3.1 is stopped --- 7z release.cmd | 2 +- Common.targets | 4 +- .../YandexDisk/YadWeb/DtoImportYadWeb.cs | 10 +++ .../Repos/YandexDisk/YadWeb/Models/Base.cs | 34 +++++++ .../YadWeb/Models/CreateFolderV2.cs | 28 ++++++ .../YandexDisk/YadWeb/Models/FolderInfo.cs | 2 +- .../YandexDisk/YadWeb/Models/ItemInfo.cs | 2 +- .../YadWeb/Models/KnownYadModelConverter.cs | 11 ++- .../YadWeb/Models/ResourceInfoV2.cs | 31 +++++++ .../Models/YadResourceStatsPostModel.cs | 2 +- .../YadWeb/Requests/YaDCommonRequestV2.cs | 5 +- .../YandexDisk/YadWeb/YadWebRequestRepo.cs | 10 ++- .../YandexDisk/YadWeb/YadWebRequestRepo2.cs | 89 ++++++++++++------- .../Base/Requests/BaseRequest.cs | 11 ++- .../Base/Requests/Types/CheckUpInfo.cs | 63 ++++++++----- MailRuCloud/MailRuCloudApi/Base/WebDavPath.cs | 4 +- MailRuCloud/MailRuCloudApi/Cloud.cs | 3 - .../MailRuCloudApi/Common/EntryCache.cs | 14 +-- MailRuCloud/MailRuCloudApi/PublishInfo.cs | 2 +- readme.md | 1 + 20 files changed, 248 insertions(+), 80 deletions(-) create mode 100644 MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/CreateFolderV2.cs create mode 100644 MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/ResourceInfoV2.cs diff --git a/7z release.cmd b/7z release.cmd index 0c0d2fc6..9f1609c1 100644 --- a/7z release.cmd +++ b/7z release.cmd @@ -4,7 +4,7 @@ set options=-tzip -mx9 -r -sse -x!*.pdb -x!*dev* "C:\Program Files\7-Zip\7z.exe" a %options% "BrowserAuthenticator\bin\Release\BrowserAuthenticator-%ver%-net7.0-windows.zip" ".\BrowserAuthenticator\bin\Release\net7.0-windows\*" "C:\Program Files\7-Zip\7z.exe" a %options% "BrowserAuthenticator\bin\Release\BrowserAuthenticator-%ver%-net8.0-windows.zip" ".\BrowserAuthenticator\bin\Release\net8.0-windows\*" -"C:\Program Files\7-Zip\7z.exe" a %options% "WDMRC.Console\bin\Release\WebDAVCloudMailRu-%ver%-dotNetCore3.1.zip" ".\WDMRC.Console\bin\Release\netcoreapp3.1\*" +REM "C:\Program Files\7-Zip\7z.exe" a %options% "WDMRC.Console\bin\Release\WebDAVCloudMailRu-%ver%-dotNetCore3.1.zip" ".\WDMRC.Console\bin\Release\netcoreapp3.1\*" "C:\Program Files\7-Zip\7z.exe" a %options% "WDMRC.Console\bin\Release\WebDAVCloudMailRu-%ver%-dotNet48.zip" ".\WDMRC.Console\bin\Release\net48\*" "C:\Program Files\7-Zip\7z.exe" a %options% "WDMRC.Console\bin\Release\WebDAVCloudMailRu-%ver%-dotNet5.zip" ".\WDMRC.Console\bin\Release\net5.0\*" "C:\Program Files\7-Zip\7z.exe" a %options% "WDMRC.Console\bin\Release\WebDAVCloudMailRu-%ver%-dotNet6.zip" ".\WDMRC.Console\bin\Release\net6.0\*" diff --git a/Common.targets b/Common.targets index 2c2b3c27..65a59d2b 100644 --- a/Common.targets +++ b/Common.targets @@ -1,7 +1,7 @@ - net8.0-windows;net7.0-windows;net48;netcoreapp3.1;net5.0;net6.0;net7.0;net8.0 + net48;net5.0;net6.0;net7.0;net7.0-windows;net8.0;net8.0-windows latest - 1.15.0.0 + 1.15.0.1 \ No newline at end of file diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/DtoImportYadWeb.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/DtoImportYadWeb.cs index c06e705c..ae6c13cb 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/DtoImportYadWeb.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/DtoImportYadWeb.cs @@ -212,6 +212,16 @@ public static CreateFolderResult ToCreateFolderResult(this YadCreateFolderReques }; return res; } + + public static CreateFolderResult ToCreateFolderResult(this YadResourceInfoPostModelV2 data) + { + var res = new CreateFolderResult + { + IsSuccess = true, + Path = data.Result.Path.Remove(0, "/disk".Length) + }; + return res; + } public static CopyResult ToCopyResult(this YadResponseModel data) { diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/Base.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/Base.cs index ec5432d7..2cef4e2c 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/Base.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/Base.cs @@ -97,6 +97,18 @@ public class YadModelDataBase public YadModelDataError Error { get; set; } } + public class YadModelDataBaseErrorStruct + { + [JsonProperty("error")] + public YadModelDataError2 Error { get; set; } + } + + public class YadModelDataBaseErrorString + { + [JsonProperty("error")] + public string ErrorToken { get; set; } + } + public class YadModelDataError { [JsonProperty("id")] @@ -117,4 +129,26 @@ public class YadModelDataErrorBody [JsonProperty("title")] public string Title { get; set; } } + + public class YadModelDataError2 + { + [JsonProperty("id")] + public string Id { get; set; } + + //[JsonProperty("body")] + //public YadModelDataErrorBody Body { get; set; } + + [JsonProperty("type")] + public string Type { get; set; } + + [JsonProperty("statusCode")] + public int StatusCode { get; set; } + + [JsonProperty("code")] + public long Code { get; set; } + + [JsonProperty("title")] + public string Title { get; set; } + } + } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/CreateFolderV2.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/CreateFolderV2.cs new file mode 100644 index 00000000..d98329fe --- /dev/null +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/CreateFolderV2.cs @@ -0,0 +1,28 @@ +using System.Collections.Generic; +using Newtonsoft.Json; +using YaR.Clouds.Base.Repos.YandexDisk.YadWeb.Models; + +namespace YaR.Clouds.Base.Repos.YandexDisk.YadWeb.Models +{ + class YadCreateFolderPostModelV2 : YadModelV2 + { + public YadCreateFolderPostModelV2(string path) + { + APIMethod = "mpfs/mkdir"; + ResultType = typeof(void); + RequestParameter = new YadRequestV2CreateFolder() + { + Path = WebDavPath.Combine("/disk", path) + }; + } + + /// Result is not supported. Use bulk-resource-info + public string Result => null; + } +} + +public class YadRequestV2CreateFolder : YadRequestV2Parameter +{ + [JsonProperty("path")] + public string Path { get; set; } +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/FolderInfo.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/FolderInfo.cs index 41fa18b4..24311928 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/FolderInfo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/FolderInfo.cs @@ -57,7 +57,7 @@ public override IEnumerable> ToKvp(int index) } } - internal class YadFolderInfoRequestData : YadModelDataBase + internal class YadFolderInfoRequestData : YadModelDataBaseErrorString { [JsonProperty("resources")] public List Resources { get; set; } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/ItemInfo.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/ItemInfo.cs index 28bf336e..c51dc4d0 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/ItemInfo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/ItemInfo.cs @@ -34,7 +34,7 @@ public override IEnumerable> ToKvp(int index) } } - class YadItemInfoRequestData : YadModelDataBase + class YadItemInfoRequestData : YadModelDataBaseErrorStruct { [JsonProperty("ctime")] public long Ctime { get; set; } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/KnownYadModelConverter.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/KnownYadModelConverter.cs index 5fd599da..2f9cf070 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/KnownYadModelConverter.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/KnownYadModelConverter.cs @@ -8,6 +8,8 @@ namespace YaR.Clouds.Base.Repos.YandexDisk.YadWeb.Models { class KnownYadModelConverter : JsonConverter> { + private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(KnownYadModelConverter)); + private readonly List _createdModels; public KnownYadModelConverter(List createdModels) @@ -25,7 +27,14 @@ public override List ReadJson(JsonReader reader, Type objectTy { var chToken = children[i]; var resItem = _createdModels[i]; - serializer.Populate(chToken.CreateReader(), resItem); + try + { + serializer.Populate(chToken.CreateReader(), resItem); + } + catch(Exception ex) + { + Logger.Warn($"Error unpacking JSON: {ex.Message}"); + } } return null; diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/ResourceInfoV2.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/ResourceInfoV2.cs new file mode 100644 index 00000000..c143ccf1 --- /dev/null +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/ResourceInfoV2.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using System.Linq; +using Newtonsoft.Json; +using YaR.Clouds.Base.Repos.YandexDisk.YadWeb.Models; + +namespace YaR.Clouds.Base.Repos.YandexDisk.YadWeb.Models +{ + class YadResourceInfoPostModelV2 : YadModelV2 + { + public YadResourceInfoPostModelV2(string path) + { + APIMethod = "mpfs/bulk-resource-info"; + ResultType = typeof(List); + var pathes = new YadRequestV2ResourceInfo(); + RequestParameter = new YadRequestV2ResourceInfo() + { + Pathes = [WebDavPath.Combine("/disk", path)] + }; + } + + /// Result is not supported. Use bulk-resource-info + public FolderInfoDataResource Result + => ((List)ResultObject)?.FirstOrDefault(); + } +} + +public class YadRequestV2ResourceInfo : YadRequestV2Parameter +{ + [JsonProperty("ids")] + public List Pathes { get; set; } +} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/YadResourceStatsPostModel.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/YadResourceStatsPostModel.cs index e2cf011d..2f82c7d1 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/YadResourceStatsPostModel.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/YadResourceStatsPostModel.cs @@ -26,7 +26,7 @@ public override IEnumerable> ToKvp(int index) } - class YadResourceStatsRequestData : YadModelDataBase + class YadResourceStatsRequestData : YadModelDataBaseErrorStruct { /// /// Здесь количество файлов (не директорий), на всех уровнях вложенности. diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YaDCommonRequestV2.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YaDCommonRequestV2.cs index 7426ecf6..5fcbc2e3 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YaDCommonRequestV2.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Requests/YaDCommonRequestV2.cs @@ -112,8 +112,11 @@ protected override RequestResponse DeserializeMessage( //} }); - Model.Deserialize(text); Model.SourceJsonForDebug = text; + if (Model.ResultType == typeof(void)) + Model.ResultObject = null; + else + Model.Deserialize(text); if (Model.Errors is not null && Model.Errors.Count == 0) Model.Errors = null; diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebRequestRepo.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebRequestRepo.cs index ae97d534..489599fb 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebRequestRepo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebRequestRepo.cs @@ -375,12 +375,20 @@ public async Task CreateFolder(string path) //var req = await new YadCreateFolderRequest(HttpSettings, (YadWebAuth)Authenticator, path) // .MakeRequestAsync(_connectionLimiter); + /* API changed in october 2024 await new YaDCommonRequest(HttpSettings, (YadWebAuth)Auth) .With(new YadCreateFolderPostModel(path), out YadResponseModel itemInfo) .MakeRequestAsync(_connectionLimiter); + */ + await new YaDCommonRequestV2(HttpSettings, (YadWebAuth)Auth) + .With(new YadCreateFolderPostModelV2(path), out YadCreateFolderPostModelV2 resultUnsed) + .MakeRequestAsync(_connectionLimiter); + await new YaDCommonRequestV2(HttpSettings, (YadWebAuth)Auth) + .With(new YadResourceInfoPostModelV2(path), out YadResourceInfoPostModelV2 itemInfo) + .MakeRequestAsync(_connectionLimiter); - var res = itemInfo.Params.ToCreateFolderResult(); + var res = itemInfo.ToCreateFolderResult(); return res; } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebRequestRepo2.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebRequestRepo2.cs index 0428a3be..bc5af63c 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebRequestRepo2.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebRequestRepo2.cs @@ -16,6 +16,8 @@ using Stream = System.IO.Stream; // Yandex has API version 378.1.0 +// Yandex has API version 382.2.0 +// Yandex has API version 383.0.0 namespace YaR.Clouds.Base.Repos.YandexDisk.YadWeb { @@ -131,12 +133,6 @@ public override async Task FolderInfo(RemotePath path, int offset = 0, i if (path.Path.StartsWith(YadMediaPath)) return await MediaFolderInfo(path.Path); - // YaD perform async deletion - YadResponseModel itemInfo = null; - YadResponseModel folderInfo = null; - YadResponseModel resourceStats = null; - YadResponseModel, YadActiveOperationsParams> activeOps = null; - /* * Не менее 1 параллельного потока, * не более доступного по ограничителю за вычетом одного для соседних запросов, @@ -148,10 +144,41 @@ public override async Task FolderInfo(RemotePath path, int offset = 0, i Logger.Debug($"Listing path {path.Path}"); + YadResponseModel itemInfo = null; Retry.Do( () => TimeSpan.Zero, () => new YaDCommonRequest(HttpSettings, (YadWebAuth)Auth) .With(new YadItemInfoPostModel(path.Path), out itemInfo) + .MakeRequestAsync(_connectionLimiter) + .Result, + _ => false, + TimeSpan.FromMilliseconds(OperationStatusCheckIntervalMs), OperationStatusCheckRetryTimeout); + + + if (itemInfo is null) + throw new IOException("Error reading file or directory information from server"); + if( itemInfo.Error is not null) + throw new IOException($"Error reading file or directory information from server {itemInfo.Error.Message}"); + if (itemInfo.Data is null) + throw new IOException($"Error reading file or directory information from server"); + if ((itemInfo?.Data?.Error?.Id ?? "HTTP_404") != "HTTP_404") + throw new IOException($"Error reading file or directory information from server {itemInfo.Data.Error.Title}"); + + // directory entiry is missing + if (itemInfo.Data.Type is null) + return null; + + // it's a file + if (itemInfo.Data.Type == "file") + return itemInfo.Data.ToFile(PublicBaseUrlDefault); + + // it's a folder + YadResponseModel folderInfo = null; + YadResponseModel resourceStats = null; + YadResponseModel, YadActiveOperationsParams> activeOps = null; + Retry.Do( + () => TimeSpan.Zero, + () => new YaDCommonRequest(HttpSettings, (YadWebAuth)Auth) .With(new YadFolderInfoPostModel(path.Path) { WithParent = true, Amount = firstReadLimit }, out folderInfo) .With(new YadResourceStatsPostModel(path.Path), out resourceStats) .With(new YadActiveOperationsPostModel(), out activeOps) @@ -161,30 +188,26 @@ public override async Task FolderInfo(RemotePath path, int offset = 0, i TimeSpan.FromMilliseconds(OperationStatusCheckIntervalMs), OperationStatusCheckRetryTimeout); - if (itemInfo?.Error != null || - (itemInfo?.Data?.Error?.Id ?? "HTTP_404") != "HTTP_404" || - resourceStats?.Error != null || - (resourceStats?.Data?.Error?.Id ?? "HTTP_404") != "HTTP_404" || - folderInfo?.Error != null || - (folderInfo?.Data?.Error?.Id ?? "HTTP_404") != "HTTP_404") - { - throw new IOException(string.Concat("Error reading file or directory information from server ", - itemInfo?.Error?.Message, - " ", - itemInfo?.Data?.Error?.Message, - " ", - resourceStats?.Error?.Message, - " ", - resourceStats?.Data?.Error?.Message)); - } + if (folderInfo is null) + throw new IOException("Error reading directory information from server"); + if (folderInfo.Error is not null) + throw new IOException($"Error reading directory information from server {folderInfo.Error.Message}"); + if (folderInfo.Data is null) + throw new IOException($"Error reading directory information from server"); + if ((folderInfo?.Data?.ErrorToken ?? "HTTP_404") != "HTTP_404") + throw new IOException($"Error reading directory information from server {folderInfo.Data.ErrorToken}"); + + if (resourceStats is null) + throw new IOException("Error reading directory information from server"); + if (resourceStats.Error is not null) + throw new IOException($"Error reading directory information from server {resourceStats.Error.Message}"); + if (resourceStats.Data is null) + throw new IOException($"Error reading directory information from server"); + if ((resourceStats?.Data?.Error?.Id ?? "HTTP_404") != "HTTP_404") + throw new IOException($"Error reading directory information from server {resourceStats.Data.Error.Title}"); - var entryData = itemInfo?.Data; - if (entryData?.Type is null) - return null; - if (entryData.Type == "file") - return entryData.ToFile(PublicBaseUrlDefault); - Folder folder = folderInfo.Data.ToFolder(entryData, resourceStats.Data, path.Path, PublicBaseUrlDefault, activeOps?.Data); + Folder folder = folderInfo.Data.ToFolder(itemInfo.Data, resourceStats.Data, path.Path, PublicBaseUrlDefault, activeOps?.Data); folder.IsChildrenLoaded = limit == int.MaxValue; int alreadyCount = folder.Descendants.Count; @@ -237,7 +260,7 @@ public override async Task FolderInfo(RemotePath path, int offset = 0, i () => TimeSpan.Zero, () => { - string diskPath = WebDavPath.Combine("/disk", entryData.Path); + string diskPath = WebDavPath.Combine("/disk", itemInfo.Data.Path); Parallel.For(0, info.Length, (int index) => { @@ -252,11 +275,11 @@ public override async Task FolderInfo(RemotePath path, int offset = 0, i .Result; if (folderPartInfo?.Error != null || - folderPartInfo?.Data?.Error != null) + folderPartInfo?.Data?.ErrorToken != null) throw new IOException(string.Concat("Error reading file or directory information from server ", folderPartInfo?.Error?.Message, " ", - folderPartInfo?.Data?.Error?.Message)); + folderPartInfo?.Data?.ErrorToken)); if (folderPartInfo?.Data is not null && folderPartInfo.Error is null) info[index].Result = folderPartInfo.Data; @@ -406,7 +429,7 @@ protected void WaitForOperation2(string operationOpId) TimeSpan.FromMilliseconds(OperationStatusCheckIntervalMs), OperationStatusCheckRetryTimeout); } - public override async Task DetectOutsideChanges() + public override Task DetectOutsideChanges() { YadResponseModel, YadActiveOperationsParams> itemInfo = null; @@ -448,7 +471,7 @@ public override async Task DetectOutsideChanges() ActiveOperations = list, }; - return info; + return Task.FromResult(info); } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs index fbdc4378..cecf5d9e 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs @@ -55,8 +55,15 @@ protected virtual HttpWebRequest CreateRequest(string baseDomain) request.ContinueTimeout = _settings.CloudSettings.Wait100ContinueTimeoutSec * 1000; request.Timeout = _settings.CloudSettings.WaitResponseTimeoutSec * 1000; request.ReadWriteTimeout = _settings.CloudSettings.ReadWriteTimeoutSec * 1000; - request.AllowWriteStreamBuffering = false; - request.AllowReadStreamBuffering = true; + /* + * NET 4.8: When performing a write operation with AllowWriteStreamBuffering set to false, + * you must either set ContentLength to a non-negative number or set SendChunked to true. + */ + //request.AllowWriteStreamBuffering = false; + /* + * NET 4.8: This operation is not supported. + */ + //request.AllowReadStreamBuffering = true; request.SendChunked = false; request.ServicePoint.Expect100Continue = false; request.KeepAlive = true; diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/CheckUpInfo.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/CheckUpInfo.cs index e2da6d44..78ccddb7 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/CheckUpInfo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/CheckUpInfo.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Threading; using YaR.Clouds.Base.Repos.YandexDisk.YadWeb.Models; namespace YaR.Clouds.Base.Requests.Types; @@ -42,6 +43,8 @@ public enum CounterOperation public class JournalCounters { + private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(JournalCounters)); + /// Удаление навсегда public long RemoveCounter = 0; public const string RemoveCounterStr = "fs-rm"; @@ -125,57 +128,71 @@ public void Increment(CounterOperation operation) case CounterOperation.None: break; case CounterOperation.Remove: - RemoveCounter++; + Interlocked.Increment(ref RemoveCounter); break; case CounterOperation.Rename: - RenameCounter++; + Interlocked.Increment(ref RenameCounter); break; case CounterOperation.Move: - MoveCounter++; + Interlocked.Increment(ref MoveCounter); break; case CounterOperation.Copy: - CopyCounter++; + Interlocked.Increment(ref CopyCounter); break; case CounterOperation.Upload: - UploadCounter++; + Interlocked.Increment(ref UploadCounter); + //Logger.Warn($"UploadCounter->{UploadCounter}"); break; case CounterOperation.TakeSomeonesFolder: - TakeSomeonesFolderCounter++; + Interlocked.Increment(ref TakeSomeonesFolderCounter); break; case CounterOperation.NewFolder: - NewFolderCounter++; + Interlocked.Increment(ref NewFolderCounter); break; //case CounterOperation.Update: // UpdateCounter++; // break; case CounterOperation.RemoveToTrash: - RemoveToTrashCounter++; + Interlocked.Increment(ref RemoveToTrashCounter); + //Logger.Warn($"RemoveToTrashCounter->{RemoveToTrashCounter}"); break; case CounterOperation.RestoreFromTrash: - RestoreFromTrashCounter++; + Interlocked.Increment(ref RestoreFromTrashCounter); break; case CounterOperation.TrashDropItem: - TrashDropItemCounter++; + Interlocked.Increment(ref TrashDropItemCounter); break; case CounterOperation.TrashDropAll: - TrashDropAllCounter++; + Interlocked.Increment(ref TrashDropAllCounter); break; } } public void TakeMax(JournalCounters src) { - RemoveCounter = Math.Max(RemoveCounter, src.RemoveCounter); - RenameCounter = Math.Max(RenameCounter, src.RenameCounter); - MoveCounter = Math.Max(MoveCounter, src.MoveCounter); - CopyCounter = Math.Max(CopyCounter, src.CopyCounter); - UploadCounter = Math.Max(UploadCounter, src.UploadCounter); - TakeSomeonesFolderCounter = Math.Max(TakeSomeonesFolderCounter, src.TakeSomeonesFolderCounter); - NewFolderCounter = Math.Max(NewFolderCounter, src.NewFolderCounter); - //UpdateCounter = Math.Max(UpdateCounter, src.UpdateCounter); - RemoveToTrashCounter = Math.Max(RemoveToTrashCounter, src.RemoveToTrashCounter); - RestoreFromTrashCounter = Math.Max(RestoreFromTrashCounter, src.RestoreFromTrashCounter); - TrashDropItemCounter = Math.Max(TrashDropItemCounter, src.TrashDropItemCounter); - TrashDropAllCounter = Math.Max(TrashDropAllCounter, src.TrashDropAllCounter); + if(RemoveCounter { - previous = _lastComparedInfo; - _lastComparedInfo = info.AccountInfo; - if (previous is not null && _lastComparedInfo is not null) + if (info.AccountInfo is not null && _lastComparedInfo is not null) { - var dst = _lastComparedInfo.JournalCounters; - var src = previous.JournalCounters; - _lastComparedInfo.JournalCounters.TakeMax(previous.JournalCounters); + info.AccountInfo.JournalCounters.TakeMax(_lastComparedInfo.JournalCounters); } + previous = Interlocked.Exchange(ref _lastComparedInfo, info.AccountInfo); }); // Сравнение счетчиков @@ -420,7 +418,9 @@ private async void CheckActiveOpsAsync() if (texts.Count > 0) { // Обнаружено внешнее изменении на Диске - Logger.Warn($"External activity is detected ({string.Join(", ", texts)})"); + Logger.Warn($"External activity is detected ({string.Join(", ", texts)}). " + + $"If you want to keep cache you have to use the same password for your account in all your programs, " + + $"so please check it."); // Если между проверками что-то изменились, делаем полный сброс кеша Clear(); return; diff --git a/MailRuCloud/MailRuCloudApi/PublishInfo.cs b/MailRuCloud/MailRuCloudApi/PublishInfo.cs index 34dc66d4..35776d8b 100644 --- a/MailRuCloud/MailRuCloudApi/PublishInfo.cs +++ b/MailRuCloud/MailRuCloudApi/PublishInfo.cs @@ -8,7 +8,7 @@ public class PublishInfo public const string SharedFilePostfix = ".share.wdmrc"; public const string PlayListFilePostfix = ".m3u8"; - public List Items { get; } = new(); + public List Items { get; } = []; public DateTime DateTime { get; set; } = DateTime.Now; } diff --git a/readme.md b/readme.md index 464d448d..bbdc1a85 100644 --- a/readme.md +++ b/readme.md @@ -26,6 +26,7 @@ The fork project Date: Thu, 31 Oct 2024 19:13:21 +0300 Subject: [PATCH 68/77] NuGet packages versions updated --- BrowserAuthenticator/BrowserAuthenticator.csproj | 2 +- MailRuCloud/MailRuCloudApi/YaR.Clouds.csproj | 2 +- WebDavMailRuCloudStore/WebDavStore.csproj | 2 +- WinServiceInstaller/WinServiceInstaller.csproj | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/BrowserAuthenticator/BrowserAuthenticator.csproj b/BrowserAuthenticator/BrowserAuthenticator.csproj index 06b35203..bdf6a838 100644 --- a/BrowserAuthenticator/BrowserAuthenticator.csproj +++ b/BrowserAuthenticator/BrowserAuthenticator.csproj @@ -30,7 +30,7 @@ - + diff --git a/MailRuCloud/MailRuCloudApi/YaR.Clouds.csproj b/MailRuCloud/MailRuCloudApi/YaR.Clouds.csproj index 9262fd06..fb41208f 100644 --- a/MailRuCloud/MailRuCloudApi/YaR.Clouds.csproj +++ b/MailRuCloud/MailRuCloudApi/YaR.Clouds.csproj @@ -31,7 +31,7 @@ - + diff --git a/WebDavMailRuCloudStore/WebDavStore.csproj b/WebDavMailRuCloudStore/WebDavStore.csproj index 5c0277ae..bdd756fe 100644 --- a/WebDavMailRuCloudStore/WebDavStore.csproj +++ b/WebDavMailRuCloudStore/WebDavStore.csproj @@ -34,7 +34,7 @@ - + diff --git a/WinServiceInstaller/WinServiceInstaller.csproj b/WinServiceInstaller/WinServiceInstaller.csproj index 8a03d8a1..e1bf728e 100644 --- a/WinServiceInstaller/WinServiceInstaller.csproj +++ b/WinServiceInstaller/WinServiceInstaller.csproj @@ -55,7 +55,7 @@ - + From 2e13afc04c8a133cdccfecc5c4e13b2f1064a947 Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Thu, 31 Oct 2024 19:20:12 +0300 Subject: [PATCH 69/77] version bump to 1.24.10.31 --- 7z release.cmd | 2 +- Common.targets | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/7z release.cmd b/7z release.cmd index 9f1609c1..06269276 100644 --- a/7z release.cmd +++ b/7z release.cmd @@ -1,4 +1,4 @@ -set ver=1.15 +set ver=1.24.10.31 set options=-tzip -mx9 -r -sse -x!*.pdb -x!*dev* "C:\Program Files\7-Zip\7z.exe" a %options% "BrowserAuthenticator\bin\Release\BrowserAuthenticator-%ver%-net7.0-windows.zip" ".\BrowserAuthenticator\bin\Release\net7.0-windows\*" diff --git a/Common.targets b/Common.targets index 65a59d2b..0f0bd04c 100644 --- a/Common.targets +++ b/Common.targets @@ -2,6 +2,6 @@ net48;net5.0;net6.0;net7.0;net7.0-windows;net8.0;net8.0-windows latest - 1.15.0.1 + 1.24.10.31 \ No newline at end of file From d8739f43b962cca40f98dd7f5f3d8f9eed364d10 Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Thu, 31 Oct 2024 19:48:22 +0300 Subject: [PATCH 70/77] Install as service fixed a little --- WDMRC.Console/Program.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WDMRC.Console/Program.cs b/WDMRC.Console/Program.cs index c1660f27..52c71b1d 100644 --- a/WDMRC.Console/Program.cs +++ b/WDMRC.Console/Program.cs @@ -15,7 +15,7 @@ private static void Main(string[] args) var exitCode = result .MapResult( - options => + (CommandLineOptions options) => { _c = new ServiceConfigurator { From 8660e7ffb314686068a98bc01e445626c7199c6a Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Mon, 4 Nov 2024 16:04:48 +0300 Subject: [PATCH 71/77] =?UTF-8?q?=D0=97=D0=B0=D0=BC=D0=B5=D0=BD=D0=B0=20?= =?UTF-8?q?=D1=80=D0=B0=D0=B7=D1=80=D0=B5=D1=88=D0=B5=D0=BD=D0=BD=D1=8B?= =?UTF-8?q?=D1=85=20=D0=B2=20Ext4,=20=D0=BD=D0=BE=20=D0=B7=D0=B0=D0=BF?= =?UTF-8?q?=D1=80=D0=B5=D1=89=D0=B5=D0=BD=D0=BD=D1=8B=D1=85=20=D0=B2=20?= =?UTF-8?q?=D0=BE=D0=B1=D0=BB=D0=B0=D0=BA=D0=B5=20=D0=B2=D0=B8=D0=BC=D0=B2?= =?UTF-8?q?=D0=BE=D0=BB=D0=BE=D0=B2:=20*=20->=20=E2=80=A2=20(\u2022),=20:?= =?UTF-8?q?=20->=20=E2=81=9E=20(\u205e),=20<=20->=20=C2=AB=20(\u00ab),=20>?= =?UTF-8?q?=20->=20=C2=BB=20(\u00bb),=20=3F->=20=E2=80=BD=20(\u203d),=20|?= =?UTF-8?q?=20->=20=E2=94=82=20(\u2502),=20/=20->=20~,=20\=20->=20~.=20?= =?UTF-8?q?=D0=A2=D0=B0=D0=BA=20=D0=B6=D0=B5=20=D0=B8=D1=81=D0=BF=D1=80?= =?UTF-8?q?=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=B0=20=D0=BD=D0=B5=D0=BF=D1=80?= =?UTF-8?q?=D0=B0=D0=B2=D0=B8=D0=BB=D1=8C=D0=BD=D0=B0=D1=8F=20=D0=BF=D0=BE?= =?UTF-8?q?=D1=81=D0=BB=D0=B5=D0=B4=D0=BE=D0=B2=D0=B0=D1=82=D0=B5=D0=BB?= =?UTF-8?q?=D1=8C=D0=BD=D0=BE=D1=81=D1=82=D1=8C=20=D0=B8=D0=BD=D0=B8=D1=86?= =?UTF-8?q?=D0=B8=D0=B0=D0=BB=D0=B8=D0=B7=D0=B0=D1=86=D0=B8=D0=B8,=20?= =?UTF-8?q?=D0=BF=D1=80=D0=B8=D0=B2=D0=BE=D0=B4=D1=8F=D1=89=D0=B0=D1=8F=20?= =?UTF-8?q?=D0=BA=20=D0=BE=D1=88=D0=B8=D0=B1=D0=BA=D0=B5=20=D1=81=20TLS.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MailRuCloud/Mobile/MobileRequestRepo.cs | 10 ++-- .../MailRuCloud/WebBin/WebBinRequestRepo.cs | 48 +++++++++++++++---- .../MailRuCloud/WebM1/WebM1RequestRepo.cs | 10 ++-- .../MailRuCloud/WebV2/WebV2RequestRepo.cs | 14 +++--- 4 files changed, 56 insertions(+), 26 deletions(-) diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/MobileRequestRepo.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/MobileRequestRepo.cs index 8d7b06fa..7193a30d 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/MobileRequestRepo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/MobileRequestRepo.cs @@ -32,6 +32,11 @@ class MobileRequestRepo : MailRuBaseRepo, IRequestRepo public MobileRequestRepo(CloudSettings settings, IWebProxy proxy, IAuth auth, int listDepth) : base(new Credentials(settings, auth.Login, auth.Password)) { + ServicePointManager.DefaultConnectionLimit = int.MaxValue; + + // required for Windows 7 breaking connection + ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13 | SecurityProtocolType.Tls12; + _connectionLimiter = new SemaphoreSlim(settings.MaxConnectionCount); _listDepth = listDepth; @@ -48,11 +53,6 @@ public MobileRequestRepo(CloudSettings settings, IWebProxy proxy, IAuth auth, in }, _ => TimeSpan.FromSeconds(MetaServerExpiresSec)); - ServicePointManager.DefaultConnectionLimit = int.MaxValue; - - // required for Windows 7 breaking connection - ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13 | SecurityProtocolType.Tls12; - //_downloadServer = new Cached(old => // { // Logger.Debug("DownloadServer expired, refreshing."); diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebBin/WebBinRequestRepo.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebBin/WebBinRequestRepo.cs index d981ff80..f207237f 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebBin/WebBinRequestRepo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebBin/WebBinRequestRepo.cs @@ -21,8 +21,6 @@ using CreateFolderRequest = YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests.CreateFolderRequest; using MoveRequest = YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests.MoveRequest; using static YaR.Clouds.Cloud; -using System.Timers; -using System.Xml.Linq; namespace YaR.Clouds.Base.Repos.MailRuCloud.WebBin { @@ -53,6 +51,11 @@ class WebBinRequestRepo : MailRuBaseRepo, IRequestRepo public WebBinRequestRepo(CloudSettings settings, IBasicCredentials credentials, AuthCodeRequiredDelegate onAuthCodeRequired) : base(credentials) { + ServicePointManager.DefaultConnectionLimit = int.MaxValue; + + // required for Windows 7 breaking connection + ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13 | SecurityProtocolType.Tls12; + _connectionLimiter = new SemaphoreSlim(settings.MaxConnectionCount); HttpSettings.CloudSettings = settings; @@ -63,11 +66,6 @@ public WebBinRequestRepo(CloudSettings settings, IBasicCredentials credentials, Auth = new OAuth(_connectionLimiter, HttpSettings, credentials, onAuthCodeRequired); ShardManager = new ShardManager(_connectionLimiter, this); - - ServicePointManager.DefaultConnectionLimit = int.MaxValue; - - // required for Windows 7 breaking connection - ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13 | SecurityProtocolType.Tls12; } @@ -293,7 +291,7 @@ public async Task Publish(string fullPath) if (res.IsSuccess) { - CachedSharedList.Value[fullPath] = new[] { new PublicLinkInfo(PublicBaseUrlDefault + res.Url) }; + CachedSharedList.Value[fullPath] = [new PublicLinkInfo(PublicBaseUrlDefault + res.Url)]; } return res; @@ -387,6 +385,12 @@ public void CleanTrash() public async Task CreateFolder(string path) { + /* + * В названии папок нельзя использовать символы «" * / : < > ? \ |». + * Также название не может состоять только из точки «.» или из двух точек «..» + */ + path = StripBadSymbols(path); + //return (await new CreateFolderRequest(HttpSettings, Authenticator, path).MakeRequestAsync()) // .ToCreateFolderResult(); @@ -396,6 +400,12 @@ public async Task CreateFolder(string path) public async Task AddFile(string fileFullPath, IFileHash fileHash, FileSize fileSize, DateTime dateTime, ConflictResolver? conflictResolver) { + /* + * В названии папок нельзя использовать символы «" * / : < > ? \ |». + * Также название не может состоять только из точки «.» или из двух точек «..» + */ + fileFullPath = StripBadSymbols(fileFullPath); + //var res = await new CreateFileRequest(Proxy, Authenticator, fileFullPath, fileHash, fileSize, conflictResolver) // .MakeRequestAsync(_connectionLimiter); //return res.ToAddFileResult(); @@ -410,6 +420,28 @@ public async Task AddFile(string fileFullPath, IFileHash fileHash return res; } + public string StripBadSymbols(string fullPath) + { + /* + * В названии папок нельзя использовать символы «" * / : < > ? \ |». + * Также название не может состоять только из точки «.» или из двух точек «..» + */ + string name = WebDavPath.Name(fullPath); + string newName = name + .Replace("*", "\u2022") // • + .Replace(":", "\u205e") // ⁞ + .Replace("<", "\u00ab") // « + .Replace(">", "\u00bb") // » + .Replace("?", "\u203d") // ‽ + .Replace("|", "\u2502") // │ + .Replace("/", "~") + .Replace("\\", "~") + ; + return name != newName + ? WebDavPath.Combine(WebDavPath.Parent(fullPath), newName) + : name; + } + public async Task DetectOutsideChanges() => await Task.FromResult(null); } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/WebM1RequestRepo.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/WebM1RequestRepo.cs index a36e6972..521cbcac 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/WebM1RequestRepo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/WebM1RequestRepo.cs @@ -44,17 +44,17 @@ protected WebM1RequestRepo(CloudSettings settings, IWebProxy proxy, IBasicCredentials credentials, AuthCodeRequiredDelegate onAuthCodeRequired) : base(credentials) { + ServicePointManager.DefaultConnectionLimit = int.MaxValue; + + // required for Windows 7 breaking connection + ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13 | SecurityProtocolType.Tls12; + _connectionLimiter = new SemaphoreSlim(settings.MaxConnectionCount); ShardManager = new ShardManager(_connectionLimiter, this); HttpSettings.Proxy = proxy; HttpSettings.CloudSettings = settings; _onAuthCodeRequired = onAuthCodeRequired; - ServicePointManager.DefaultConnectionLimit = int.MaxValue; - - // required for Windows 7 breaking connection - ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13 | SecurityProtocolType.Tls12; - Auth = new OAuth(_connectionLimiter, HttpSettings, credentials, onAuthCodeRequired); CachedSharedList = new Cached>>(_ => diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/WebV2RequestRepo.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/WebV2RequestRepo.cs index db12934c..955fbc8a 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/WebV2RequestRepo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/WebV2RequestRepo.cs @@ -29,6 +29,11 @@ class WebV2RequestRepo: MailRuBaseRepo, IRequestRepo public WebV2RequestRepo(CloudSettings settings, IBasicCredentials credentials, AuthCodeRequiredDelegate onAuthCodeRequired) : base(credentials) { + ServicePointManager.DefaultConnectionLimit = int.MaxValue; + + // required for Windows 7 breaking connection + ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13 | SecurityProtocolType.Tls12; + _connectionLimiter = new SemaphoreSlim(settings.MaxConnectionCount); HttpSettings.UserAgent = settings.UserAgent; HttpSettings.CloudSettings = settings; @@ -36,18 +41,11 @@ public WebV2RequestRepo(CloudSettings settings, IBasicCredentials credentials, A Auth = new WebAuth(_connectionLimiter, HttpSettings, credentials, onAuthCodeRequired); - _bannedShards = new Cached>(_ => new List(), - _ => TimeSpan.FromMinutes(2)); + _bannedShards = new Cached>(_ => [], _ => TimeSpan.FromMinutes(2)); _cachedShards = new Cached>( _ => new ShardInfoRequest(HttpSettings, Auth).MakeRequestAsync(_connectionLimiter).Result.ToShardInfo(), _ => TimeSpan.FromSeconds(ShardsExpiresInSec)); - - ServicePointManager.DefaultConnectionLimit = int.MaxValue; - - // required for Windows 7 breaking connection - ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13 | SecurityProtocolType.Tls12; - } From 68d98c5c4faeb6c3526f7d9b03cf94cba3694928 Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Mon, 4 Nov 2024 16:23:47 +0300 Subject: [PATCH 72/77] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D1=84=D0=BE?= =?UTF-8?q?=D1=80=D0=BC=D0=B0=D1=82=D0=B8=D1=80=D0=BE=D0=B2=D0=B0=D0=BD?= =?UTF-8?q?=D0=B8=D0=B5=20=D0=BA=D0=BE=D0=B4=D0=B0,=20=D0=B2=20=D0=BE?= =?UTF-8?q?=D1=81=D0=BD=D0=BE=D0=B2=D0=BD=D0=BE=D0=BC=20=D0=B2=20=D1=87?= =?UTF-8?q?=D0=B0=D1=81=D1=82=D0=B8=20namespace?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MailRuCloud/MailRuCloudApi/Base/Common.cs | 13 +- MailRuCloud/MailRuCloudApi/Base/CryptInfo.cs | 23 +- MailRuCloud/MailRuCloudApi/Base/CryptoUtil.cs | 95 +- MailRuCloud/MailRuCloudApi/Base/File.cs | 393 ++- MailRuCloud/MailRuCloudApi/Base/FileHash.cs | 41 +- MailRuCloud/MailRuCloudApi/Base/FileSize.cs | 101 +- .../MailRuCloudApi/Base/FileSplitInfo.cs | 13 +- .../Base/FilenameServiceInfo.cs | 115 +- MailRuCloud/MailRuCloudApi/Base/Folder.cs | 187 +- .../MailRuCloudApi/Base/HashMatchException.cs | 23 +- .../MailRuCloudApi/Base/HeaderFileContent.cs | 15 +- .../MailRuCloudApi/Base/IBasicCredentials.cs | 13 +- MailRuCloud/MailRuCloudApi/Base/IEntry.cs | 21 +- .../MailRuCloudApi/Base/PublicLinkInfo.cs | 79 +- .../Base/Repos/DtoImportExtensions.cs | 257 +- .../MailRuCloudApi/Base/Repos/IAuth.cs | 21 +- .../MailRuCloudApi/Base/Repos/ICloudHasher.cs | 17 +- .../MailRuCloudApi/Base/Repos/IRequestRepo.cs | 61 +- .../Base/Repos/MailRuCloud/DtoImport.cs | 523 ++-- .../Base/Repos/MailRuCloud/FileHashMrc.cs | 39 +- .../Base/Repos/MailRuCloud/MailRuBaseRepo.cs | 159 +- .../Base/Repos/MailRuCloud/MailRuSha1Hash.cs | 135 +- .../MailRuCloud/Mobile/MobileRequestRepo.cs | 391 ++- .../Mobile/Requests/AccountInfoRequest.cs | 13 +- .../Mobile/Requests/BaseRequestMobile.cs | 25 +- .../Mobile/Requests/CreateFolderRequest.cs | 91 +- .../Mobile/Requests/GetServerRequest.cs | 13 +- .../Mobile/Requests/GetUploadServerRequest.cs | 13 +- .../Mobile/Requests/ListRequest.cs | 401 ++- .../Mobile/Requests/MobAddFileRequest.cs | 159 +- .../Mobile/Requests/MobMetaServerRequest.cs | 13 +- .../Mobile/Requests/MoveRequest.cs | 103 +- .../Mobile/Requests/OAuthRefreshRequest.cs | 85 +- .../Mobile/Requests/OAuthSecondStepRequest.cs | 37 +- .../Mobile/Requests/ServerRequest.cs | 35 +- .../Requests/SharedFoldersListRequest.cs | 103 +- .../Requests/WeblinkGetServerRequest.cs | 31 +- .../Base/Repos/MailRuCloud/OAuth.cs | 159 +- .../Base/Repos/MailRuCloud/ShardManager.cs | 115 +- .../WebBin/Requests/DownloadRequest.cs | 101 +- .../MailRuCloud/WebBin/WebBinRequestRepo.cs | 661 +++-- .../WebM1/Requests/AccountInfoRequest.cs | 13 +- .../WebM1/Requests/CloneItemRequest.cs | 25 +- .../MailRuCloud/WebM1/Requests/CopyRequest.cs | 53 +- .../WebM1/Requests/CreateFileRequest.cs | 41 +- .../WebM1/Requests/CreateFolderRequest.cs | 29 +- .../WebM1/Requests/FolderInfoRequest.cs | 213 +- .../WebM1/Requests/ItemInfoRequest.cs | 57 +- .../MailRuCloud/WebM1/Requests/MoveRequest.cs | 33 +- .../WebM1/Requests/PublishRequest.cs | 29 +- .../WebM1/Requests/RemoveRequest.cs | 33 +- .../WebM1/Requests/RenameRequest.cs | 33 +- .../WebM1/Requests/ShardInfoRequest.cs | 25 +- .../WebM1/Requests/SharedListRequest.cs | 25 +- .../WebM1/Requests/UnpublishRequest.cs | 35 +- .../MailRuCloud/WebM1/WebM1RequestRepo.cs | 589 ++-- .../WebV2/Requests/AccountInfoRequest.cs | 13 +- .../WebV2/Requests/AuthTokenRequest.cs | 21 +- .../WebV2/Requests/CloneItemRequest.cs | 25 +- .../WebV2/Requests/CommonSettings.cs | 13 +- .../MailRuCloud/WebV2/Requests/CopyRequest.cs | 59 +- .../WebV2/Requests/CreateFileRequest.cs | 43 +- .../WebV2/Requests/CreateFolderRequest.cs | 27 +- .../WebV2/Requests/DownloadRequest.cs | 73 +- .../WebV2/Requests/DownloadTokenRequest.cs | 87 +- .../WebV2/Requests/EnsureSdcCookieRequest.cs | 27 +- .../WebV2/Requests/FolderInfoRequest.cs | 59 +- .../WebV2/Requests/ItemInfoRequest.cs | 57 +- .../WebV2/Requests/LoginRequest.cs | 63 +- .../MailRuCloud/WebV2/Requests/MoveRequest.cs | 35 +- .../WebV2/Requests/PublishRequest.cs | 29 +- .../WebV2/Requests/RemoveRequest.cs | 28 +- .../WebV2/Requests/RenameRequest.cs | 32 +- .../WebV2/Requests/SecondStepAuthRequest.cs | 31 +- .../WebV2/Requests/ShardInfoRequest.cs | 33 +- .../WebV2/Requests/UnpublishRequest.cs | 29 +- .../WebV2/Requests/UploadRequest.cs | 55 +- .../Base/Repos/MailRuCloud/WebV2/WebAuth.cs | 141 +- .../MailRuCloud/WebV2/WebV2RequestRepo.cs | 469 ++-- .../MailRuCloudApi/Base/Repos/RemotePath.cs | 36 +- .../MailRuCloudApi/Base/Repos/RepoFabric.cs | 57 +- .../Base/Requests/BaseRequest.cs | 449 ++- .../Base/Requests/BaseRequestJson.cs | 57 +- .../Base/Requests/BaseRequestString.cs | 27 +- .../Base/Requests/BaseRequestStringT.cs | 19 +- .../Base/Requests/HttpCommonSettings.cs | 17 +- .../Base/Requests/RequestException.cs | 61 +- .../Base/Requests/RequestResponse.cs | 15 +- .../Base/Requests/Types/AccountInfo.cs | 21 +- .../Types/AccountInfoRequestResult.cs | 117 +- .../Base/Requests/Types/AddFileResult.cs | 11 +- .../Requests/Types/AuthTokenRequestResult.cs | 13 +- .../Base/Requests/Types/AuthTokenResult.cs | 19 +- .../Base/Requests/Types/CheckUpInfo.cs | 7 +- .../Base/Requests/Types/CloneItemResult.cs | 11 +- .../Requests/Types/CommonOperationResult.cs | 23 +- .../Base/Requests/Types/CopyResult.cs | 15 +- .../Base/Requests/Types/CreateFolderResult.cs | 11 +- .../Base/Requests/Types/DiskUsage.cs | 35 +- .../Requests/Types/DownloadTokenResult.cs | 13 +- .../Base/Requests/Types/FolderInfoResult.cs | 137 +- .../Base/Requests/Types/ItemOperation.cs | 11 +- .../Base/Requests/Types/LoginResult.cs | 9 +- .../Base/Requests/Types/PublishResult.cs | 11 +- .../Base/Requests/Types/RemoveResult.cs | 13 +- .../Base/Requests/Types/RenameResult.cs | 13 +- .../Requests/Types/ServerRequestResult.cs | 13 +- .../Base/Requests/Types/ShardInfo.cs | 39 +- .../Requests/Types/ShardInfoRequestResult.cs | 65 +- .../Base/Requests/Types/ShardType.cs | 119 +- .../Base/Requests/Types/UnpublishResult.cs | 9 +- .../Base/Requests/Types/UploadFileResult.cs | 17 +- .../Base/ResolveFileConflictMethod.cs | 81 +- .../MailRuCloudApi/Base/SplittedFile.cs | 87 +- .../Base/Streams/Cache/CacheStream.cs | 81 +- .../Base/Streams/Cache/CacheType.cs | 11 +- .../Base/Streams/Cache/DataCache.cs | 15 +- .../Base/Streams/Cache/DeduplicateRule.cs | 15 +- .../Base/Streams/Cache/DiskDataCache.cs | 43 +- .../Base/Streams/Cache/MemoryDataCache.cs | 29 +- .../Base/Streams/DownloadStream.cs | 217 +- .../Base/Streams/PushStreamContent.cs | 311 ++- .../Base/Streams/RingBufferedStream.cs | 977 ++++--- .../Base/Streams/UploadStream.cs | 9 +- .../Base/Streams/UploadStreamHttpClient.cs | 329 ++- MailRuCloud/MailRuCloudApi/Base/WebDavPath.cs | 371 ++- MailRuCloud/MailRuCloudApi/Cloud.cs | 2435 ++++++++--------- MailRuCloud/MailRuCloudApi/CloudSettings.cs | 69 +- MailRuCloud/MailRuCloudApi/Common/Cached.cs | 101 +- .../MailRuCloudApi/Common/CustomDisposable.cs | 23 +- .../MailRuCloudApi/Common/EntryCache.cs | 1 - MailRuCloud/MailRuCloudApi/Common/Pending.cs | 105 +- MailRuCloud/MailRuCloudApi/Common/Retry.cs | 135 +- .../Common/SharedVideoResolution.cs | 31 +- MailRuCloud/MailRuCloudApi/CryptFileInfo.cs | 17 +- .../Extensions/EnumExtensions.cs | 59 +- .../MailRuCloudApi/FileUploadedDelegate.cs | 7 +- .../MailRuCloudApi/Links/Dto/ItemLink.cs | 19 +- .../MailRuCloudApi/Links/Dto/ItemList.cs | 9 +- MailRuCloud/MailRuCloudApi/Links/Link.cs | 113 +- .../MailRuCloudApi/Links/LinkManager.cs | 779 +++--- MailRuCloud/MailRuCloudApi/PublishInfo.cs | 27 +- .../MailRuCloudApi/SmtpAsyncAppender.cs | 31 +- .../Commands/CleanTrashCommand.cs | 28 +- .../SpecialCommands/Commands/CopyCommand.cs | 25 +- .../Commands/CryptInitCommand.cs | 51 +- .../Commands/CryptPasswdCommand.cs | 34 +- .../SpecialCommands/Commands/DeleteCommand.cs | 45 +- .../SpecialCommands/Commands/FishCommand.cs | 113 +- .../SpecialCommands/Commands/JoinCommand.cs | 103 +- .../SpecialCommands/Commands/ListCommand.cs | 91 +- .../Commands/LocalToServerCopyCommand.cs | 47 +- .../SpecialCommands/Commands/MoveCommand.cs | 33 +- .../Commands/RemoveBadLinksCommand.cs | 22 +- .../SpecialCommands/Commands/ShareCommand.cs | 83 +- .../Commands/SharedFolderLinkCommand.cs | 67 +- .../SpecialCommands/Commands/TestCommand.cs | 41 +- .../SpecialCommands/SpecialCommand.cs | 77 +- .../SpecialCommands/SpecialCommandFabric.cs | 310 ++- .../SpecialCommands/SpecialCommandResult.cs | 30 +- .../Streams/DownloadStreamFabric.cs | 81 +- .../Streams/SplittedUploadStream.cs | 312 ++- .../Streams/UploadStreamFabric.cs | 135 +- MailRuCloud/MailRuCloudApi/TwoFAHandler.cs | 9 +- .../XTSSharp/RandomAccessSectorStream.cs | 527 ++-- .../MailRuCloudApi/XTSSharp/SectorStream.cs | 343 ++- .../XTSSharp/XTSReadOnlyStream.cs | 181 +- .../XTSSharp/XTSWriteOnlyStream.cs | 185 +- MailRuCloud/MailRuCloudApi/XTSSharp/Xts.cs | 185 +- .../MailRuCloudApi/XTSSharp/XtsAes128.cs | 89 +- .../MailRuCloudApi/XTSSharp/XtsAes256.cs | 89 +- .../XTSSharp/XtsCryptoTransform.cs | 387 ++- .../XTSSharp/XtsSectorStream.cs | 221 +- .../MailRuCloudApi/XTSSharp/XtsStream.cs | 61 +- 174 files changed, 9715 insertions(+), 9892 deletions(-) diff --git a/MailRuCloud/MailRuCloudApi/Base/Common.cs b/MailRuCloud/MailRuCloudApi/Base/Common.cs index 3362088d..04c0336e 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Common.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Common.cs @@ -1,13 +1,12 @@ using System; -namespace YaR.Clouds.Base +namespace YaR.Clouds.Base; + +internal static class Common { - internal static class Common + public static string Base64Encode(string plainText) { - public static string Base64Encode(string plainText) - { - var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText); - return Convert.ToBase64String(plainTextBytes); - } + var plainTextBytes = System.Text.Encoding.UTF8.GetBytes(plainText); + return Convert.ToBase64String(plainTextBytes); } } diff --git a/MailRuCloud/MailRuCloudApi/Base/CryptInfo.cs b/MailRuCloud/MailRuCloudApi/Base/CryptInfo.cs index 32e68cc9..c74afafd 100644 --- a/MailRuCloud/MailRuCloudApi/Base/CryptInfo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/CryptInfo.cs @@ -1,16 +1,15 @@ -namespace YaR.Clouds.Base +namespace YaR.Clouds.Base; + +public class CryptInfo { - public class CryptInfo - { - //public const uint HeaderSize = 1024; - public uint AlignBytes { get; set; } - public CryptoKeyInfo PublicKey { get; set; } - } + //public const uint HeaderSize = 1024; + public uint AlignBytes { get; set; } + public CryptoKeyInfo PublicKey { get; set; } +} - public class CryptoKeyInfo - { - public byte[] IV { get; set; } - public byte[] Salt { get; set; } +public class CryptoKeyInfo +{ + public byte[] IV { get; set; } + public byte[] Salt { get; set; } - } } diff --git a/MailRuCloud/MailRuCloudApi/Base/CryptoUtil.cs b/MailRuCloud/MailRuCloudApi/Base/CryptoUtil.cs index 91623aab..bf1eec91 100644 --- a/MailRuCloud/MailRuCloudApi/Base/CryptoUtil.cs +++ b/MailRuCloud/MailRuCloudApi/Base/CryptoUtil.cs @@ -1,63 +1,62 @@ using System; using System.Security.Cryptography; -namespace YaR.Clouds.Base +namespace YaR.Clouds.Base; + +public static class CryptoUtil { - public static class CryptoUtil + public static byte[] GetCryptoKey(string password, byte[] salt) { - public static byte[] GetCryptoKey(string password, byte[] salt) - { #if NET7_0_OR_GREATER - using var keygen = new Rfc2898DeriveBytes(password, salt, 4002, HashAlgorithmName.SHA512); + using var keygen = new Rfc2898DeriveBytes(password, salt, 4002, HashAlgorithmName.SHA512); #else - using var keygen = new Rfc2898DeriveBytes(password, salt, 4002); + using var keygen = new Rfc2898DeriveBytes(password, salt, 4002); #endif - var key = keygen.GetBytes(32); - return key; - } + var key = keygen.GetBytes(32); + return key; + } - public static KeyAndSalt GetCryptoKeyAndSalt(string password, int saltSize = SaltSizeInBytes) - { + public static KeyAndSalt GetCryptoKeyAndSalt(string password, int saltSize = SaltSizeInBytes) + { #if NET7_0_OR_GREATER - using var keygen = new Rfc2898DeriveBytes(password, saltSize, 4002, HashAlgorithmName.SHA512); + using var keygen = new Rfc2898DeriveBytes(password, saltSize, 4002, HashAlgorithmName.SHA512); #else - using var keygen = new Rfc2898DeriveBytes(password, saltSize, 4002); + using var keygen = new Rfc2898DeriveBytes(password, saltSize, 4002); #endif - var res = new KeyAndSalt - { - Salt = keygen.Salt, - Key = keygen.GetBytes(32), - IV = keygen.GetBytes(32) - }; - return res; - } - - public static CryptoKeyInfo GetCryptoPublicInfo(Cloud cloud, File file) - { - var iv = file.EnsurePublicKey(cloud); - if (null == iv) - throw new Exception("Cannot get crypto public key"); - return iv; - } - - //public static byte[] CreateSalt(int saltSizeInBytes = SaltSizeInBytes) - //{ - // byte[] bytesSalt = new byte[saltSizeInBytes]; - // using (RNGCryptoServiceProvider crypto = new RNGCryptoServiceProvider()) - // { - // crypto.GetBytes(bytesSalt); - // return bytesSalt; - // } - //} - - - public const int SaltSizeInBytes = 8; - - public class KeyAndSalt + var res = new KeyAndSalt { - public byte[] Key { get; set; } - public byte[] Salt { get; set; } - public byte[] IV { get; set; } - } + Salt = keygen.Salt, + Key = keygen.GetBytes(32), + IV = keygen.GetBytes(32) + }; + return res; + } + + public static CryptoKeyInfo GetCryptoPublicInfo(Cloud cloud, File file) + { + var iv = file.EnsurePublicKey(cloud); + if (null == iv) + throw new Exception("Cannot get crypto public key"); + return iv; + } + + //public static byte[] CreateSalt(int saltSizeInBytes = SaltSizeInBytes) + //{ + // byte[] bytesSalt = new byte[saltSizeInBytes]; + // using (RNGCryptoServiceProvider crypto = new RNGCryptoServiceProvider()) + // { + // crypto.GetBytes(bytesSalt); + // return bytesSalt; + // } + //} + + + public const int SaltSizeInBytes = 8; + + public class KeyAndSalt + { + public byte[] Key { get; set; } + public byte[] Salt { get; set; } + public byte[] IV { get; set; } } } diff --git a/MailRuCloud/MailRuCloudApi/Base/File.cs b/MailRuCloud/MailRuCloudApi/Base/File.cs index 4574f779..902e1a98 100644 --- a/MailRuCloud/MailRuCloudApi/Base/File.cs +++ b/MailRuCloud/MailRuCloudApi/Base/File.cs @@ -7,243 +7,242 @@ using System.Linq; using YaR.Clouds.Common; -namespace YaR.Clouds.Base +namespace YaR.Clouds.Base; + +/// +/// Server file info. +/// +[DebuggerDisplay("{" + nameof(FullPath) + "}")] +public class File : IEntry { + protected File() + { + } + + public File(string fullPath, long size, IFileHash hash = null) + { + FullPath = fullPath; + ServiceInfo = FilenameServiceInfo.Parse(WebDavPath.Name(fullPath)); + + _originalSize = size; + _hash = hash; + } + + public File(string fullPath, long size, params PublicLinkInfo[] links) + : this(fullPath, size) + { + foreach (var link in links) + PublicLinks.AddOrUpdate(link.Uri.AbsolutePath, link, (_, _) => link); + } + + private IFileHash _hash; + + /// + /// Кеш URL на скачивание файла с сервера. + /// Заполняется операцией GetDownloadStream, + /// чтобы при повторном обращении на чтение файла не тратить время на получения URL'а. + /// + public string DownloadUrlCache { get; set; } = null; + /// + /// Время, с которого кешем пользоваться нельзя + /// и нужно получить новый URL. + /// + public DateTime DownloadUrlCacheExpirationTime = DateTime.MinValue; + + /// - /// Server file info. + /// makes copy of this file with new path /// - [DebuggerDisplay("{" + nameof(FullPath) + "}")] - public class File : IEntry + /// + /// + public virtual File New(string newFullPath) { - protected File() + var file = new File(newFullPath, Size, Hash) { - } + CreationTimeUtc = CreationTimeUtc, + LastAccessTimeUtc = LastAccessTimeUtc, + LastWriteTimeUtc = LastWriteTimeUtc + }; + foreach (var linkPair in PublicLinks) + file.PublicLinks.AddOrUpdate(linkPair.Key, linkPair.Value, (_, _) => linkPair.Value); + + return file; + } - public File(string fullPath, long size, IFileHash hash = null) + /// + /// Gets file name. + /// + /// File name. + public string Name + { + get => _name; + private set { - FullPath = fullPath; - ServiceInfo = FilenameServiceInfo.Parse(WebDavPath.Name(fullPath)); + _name = value; - _originalSize = size; - _hash = hash; + Extension = System.IO.Path.GetExtension(_name)?.TrimStart('.') ?? string.Empty; } + } //WebDavPath.Name(FullPath) + private string _name; - public File(string fullPath, long size, params PublicLinkInfo[] links) - : this(fullPath, size) - { - foreach (var link in links) - PublicLinks.AddOrUpdate(link.Uri.AbsolutePath, link, (_, _) => link); - } + /// + /// Gets file extension (without ".") + /// + public string Extension { get; private set; } - private IFileHash _hash; - - /// - /// Кеш URL на скачивание файла с сервера. - /// Заполняется операцией GetDownloadStream, - /// чтобы при повторном обращении на чтение файла не тратить время на получения URL'а. - /// - public string DownloadUrlCache { get; set; } = null; - /// - /// Время, с которого кешем пользоваться нельзя - /// и нужно получить новый URL. - /// - public DateTime DownloadUrlCacheExpirationTime = DateTime.MinValue; - - - /// - /// makes copy of this file with new path - /// - /// - /// - public virtual File New(string newFullPath) - { - var file = new File(newFullPath, Size, Hash) - { - CreationTimeUtc = CreationTimeUtc, - LastAccessTimeUtc = LastAccessTimeUtc, - LastWriteTimeUtc = LastWriteTimeUtc - }; - foreach (var linkPair in PublicLinks) - file.PublicLinks.AddOrUpdate(linkPair.Key, linkPair.Value, (_, _) => linkPair.Value); - - return file; - } + /// + /// Gets file hash value. + /// + /// File hash. + public virtual IFileHash Hash + { + get => _hash; + internal set => _hash = value; + } - /// - /// Gets file name. - /// - /// File name. - public string Name - { - get => _name; - private set - { - _name = value; - - Extension = System.IO.Path.GetExtension(_name)?.TrimStart('.') ?? string.Empty; - } - } //WebDavPath.Name(FullPath) - private string _name; - - /// - /// Gets file extension (without ".") - /// - public string Extension { get; private set; } - - /// - /// Gets file hash value. - /// - /// File hash. - public virtual IFileHash Hash - { - get => _hash; - internal set => _hash = value; - } + /// + /// Gets file size. + /// + /// File size. + public virtual FileSize Size => OriginalSize - (ServiceInfo.CryptInfo?.AlignBytes ?? 0); - /// - /// Gets file size. - /// - /// File size. - public virtual FileSize Size => OriginalSize - (ServiceInfo.CryptInfo?.AlignBytes ?? 0); + public virtual FileSize OriginalSize + { + get => _originalSize; + set => _originalSize = value; + } + private FileSize _originalSize; + + protected virtual File FileHeader => null; - public virtual FileSize OriginalSize + /// + /// Gets full file path with name on server. + /// + /// Full file path. + public string FullPath + { + get => _fullPath; + protected set { - get => _originalSize; - set => _originalSize = value; + _fullPath = WebDavPath.Clean(value); + Name = WebDavPath.Name(_fullPath); + Path = WebDavPath.Parent(_fullPath); } - private FileSize _originalSize; + } - protected virtual File FileHeader => null; + private string _fullPath; - /// - /// Gets full file path with name on server. - /// - /// Full file path. - public string FullPath - { - get => _fullPath; - protected set - { - _fullPath = WebDavPath.Clean(value); - Name = WebDavPath.Name(_fullPath); - Path = WebDavPath.Parent(_fullPath); - } - } + /// + /// Path to file (without filename) + /// + public string Path { get; private set; } - private string _fullPath; + /// + /// Gets public file link. + /// + /// Public link. + public ConcurrentDictionary PublicLinks + => _publicLinks ??= new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); - /// - /// Path to file (without filename) - /// - public string Path { get; private set; } + private ConcurrentDictionary _publicLinks; - /// - /// Gets public file link. - /// - /// Public link. - public ConcurrentDictionary PublicLinks - => _publicLinks ??= new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); + public IEnumerable GetPublicLinks(Cloud cloud) + { + return PublicLinks.IsEmpty + ? cloud.GetSharedLinks(FullPath) + : PublicLinks.Values; + } - private ConcurrentDictionary _publicLinks; + public ImmutableList Descendants => ImmutableList.Empty; - public IEnumerable GetPublicLinks(Cloud cloud) - { - return PublicLinks.IsEmpty - ? cloud.GetSharedLinks(FullPath) - : PublicLinks.Values; - } + /// + /// List of physical files contains data + /// + public virtual List Parts => new() { this }; + public virtual IList Files => new List { this }; - public ImmutableList Descendants => ImmutableList.Empty; + private static readonly DateTime MinFileDate = new(1900, 1, 1); + public virtual DateTime CreationTimeUtc { get; set; } = MinFileDate; + public virtual DateTime LastWriteTimeUtc { get; set; } = MinFileDate; + public virtual DateTime LastAccessTimeUtc { get; set; } = MinFileDate; - /// - /// List of physical files contains data - /// - public virtual List Parts => new() {this}; - public virtual IList Files => new List { this }; + public FileAttributes Attributes { get; set; } = FileAttributes.Normal; - private static readonly DateTime MinFileDate = new(1900, 1, 1); - public virtual DateTime CreationTimeUtc { get; set; } = MinFileDate; - public virtual DateTime LastWriteTimeUtc { get; set; } = MinFileDate; - public virtual DateTime LastAccessTimeUtc { get; set; } = MinFileDate; + public bool IsFile => true; + public FilenameServiceInfo ServiceInfo { get; protected set; } - public FileAttributes Attributes { get; set; } = FileAttributes.Normal; + //TODO : refact, bad design + public void SetName(string destinationName) + { + FullPath = WebDavPath.Combine(Path, destinationName); + if (ServiceInfo != null) ServiceInfo.CleanName = Name; - public bool IsFile => true; - public FilenameServiceInfo ServiceInfo { get; protected set; } + if (Files.Count <= 1) + return; - //TODO : refact, bad design - public void SetName(string destinationName) - { - FullPath = WebDavPath.Combine(Path, destinationName); - if (ServiceInfo != null) ServiceInfo.CleanName = Name; + string path = Path; + foreach (var fiFile in Parts) + fiFile.FullPath = WebDavPath.Combine( + path, + string.Concat(destinationName, fiFile.ServiceInfo.ToString(false))); //TODO: refact + } - if (Files.Count <= 1) - return; + //TODO : refact, bad design + public void SetPath(string fullPath) + { + FullPath = WebDavPath.Combine(fullPath, Name); + if (Parts.Count <= 1) + return; - string path = Path; - foreach (var fiFile in Parts) - fiFile.FullPath = WebDavPath.Combine( - path, - string.Concat(destinationName, fiFile.ServiceInfo.ToString(false))); //TODO: refact - } + foreach (var fiFile in Parts) + fiFile.FullPath = WebDavPath.Combine(fullPath, fiFile.Name); //TODO: refact + } - //TODO : refact, bad design - public void SetPath(string fullPath) - { - FullPath = WebDavPath.Combine(fullPath, Name); - if (Parts.Count <= 1) - return; - foreach (var fiFile in Parts) - fiFile.FullPath = WebDavPath.Combine(fullPath, fiFile.Name); //TODO: refact - } + //TODO : refact, bad design + public CryptoKeyInfo EnsurePublicKey(Cloud cloud) + { + if (!ServiceInfo.IsCrypted || null != ServiceInfo.CryptInfo.PublicKey) + return ServiceInfo.CryptInfo.PublicKey; + var info = cloud.DownloadFileAsJson(FileHeader ?? this); + ServiceInfo.CryptInfo.PublicKey = info.PublicKey; + return ServiceInfo.CryptInfo.PublicKey; + } - //TODO : refact, bad design - public CryptoKeyInfo EnsurePublicKey(Cloud cloud) - { - if (!ServiceInfo.IsCrypted || null != ServiceInfo.CryptInfo.PublicKey) - return ServiceInfo.CryptInfo.PublicKey; + public PublishInfo ToPublishInfo(Cloud cloud, bool generateDirectVideoLink, SharedVideoResolution videoResolution) + { + var info = new PublishInfo(); - var info = cloud.DownloadFileAsJson(FileHeader ?? this); - ServiceInfo.CryptInfo.PublicKey = info.PublicKey; - return ServiceInfo.CryptInfo.PublicKey; - } + bool isSplitted = Files.Count > 1; - public PublishInfo ToPublishInfo(Cloud cloud, bool generateDirectVideoLink, SharedVideoResolution videoResolution) + int cnt = 0; + foreach (var innerFile in Files) { - var info = new PublishInfo(); - - bool isSplitted = Files.Count > 1; - - int cnt = 0; - foreach (var innerFile in Files) - { - if (!innerFile.PublicLinks.IsEmpty) - info.Items.Add(new PublishInfoItem - { - Path = innerFile.FullPath, - Urls = innerFile.PublicLinks.Select(pli => pli.Value.Uri).ToList(), - PlayListUrl = !isSplitted || cnt > 0 - ? generateDirectVideoLink - ? File.ConvertToVideoLink(cloud, innerFile.PublicLinks.Values.FirstOrDefault()?.Uri, videoResolution) - : null - : null - }); - cnt++; - } - - return info; + if (!innerFile.PublicLinks.IsEmpty) + info.Items.Add(new PublishInfoItem + { + Path = innerFile.FullPath, + Urls = innerFile.PublicLinks.Select(pli => pli.Value.Uri).ToList(), + PlayListUrl = !isSplitted || cnt > 0 + ? generateDirectVideoLink + ? File.ConvertToVideoLink(cloud, innerFile.PublicLinks.Values.FirstOrDefault()?.Uri, videoResolution) + : null + : null + }); + cnt++; } - private static string ConvertToVideoLink(Cloud cloud, Uri publicLink, SharedVideoResolution videoResolution) - { - return cloud.RequestRepo.ConvertToVideoLink(publicLink, videoResolution); + return info; + } - // GetShardInfo(ShardType.WeblinkVideo).Result.Url + - //videoResolution.ToEnumMemberValue() + "/" + //"0p/" + - //Base64Encode(publicLink.TrimStart('/')) + - //".m3u8?double_encode=1"; - } + private static string ConvertToVideoLink(Cloud cloud, Uri publicLink, SharedVideoResolution videoResolution) + { + return cloud.RequestRepo.ConvertToVideoLink(publicLink, videoResolution); + + // GetShardInfo(ShardType.WeblinkVideo).Result.Url + + //videoResolution.ToEnumMemberValue() + "/" + //"0p/" + + //Base64Encode(publicLink.TrimStart('/')) + + //".m3u8?double_encode=1"; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/FileHash.cs b/MailRuCloud/MailRuCloudApi/Base/FileHash.cs index fa308c25..c0a2ac5f 100644 --- a/MailRuCloud/MailRuCloudApi/Base/FileHash.cs +++ b/MailRuCloud/MailRuCloudApi/Base/FileHash.cs @@ -1,28 +1,27 @@ -namespace YaR.Clouds.Base +namespace YaR.Clouds.Base; + +public enum FileHashType { - public enum FileHashType - { - Mrc, + Mrc, - YadSha256, - YadMd5 - } + YadSha256, + YadMd5 +} - public readonly struct Hash +public readonly struct Hash +{ + public Hash(FileHashType htype, string hash) { - public Hash(FileHashType htype, string hash) - { - HashType = htype; - Value = hash; - } - - public FileHashType HashType { get; } - public string Value { get; } + HashType = htype; + Value = hash; } - public interface IFileHash - { - Hash Get(FileHashType htype); - Hash Hash { get; } - } + public FileHashType HashType { get; } + public string Value { get; } +} + +public interface IFileHash +{ + Hash Get(FileHashType htype); + Hash Hash { get; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/FileSize.cs b/MailRuCloud/MailRuCloudApi/Base/FileSize.cs index 2eee9500..35cb37ac 100644 --- a/MailRuCloud/MailRuCloudApi/Base/FileSize.cs +++ b/MailRuCloud/MailRuCloudApi/Base/FileSize.cs @@ -1,69 +1,68 @@ using System; using System.Diagnostics; -namespace YaR.Clouds.Base +namespace YaR.Clouds.Base; + +/// +/// File size definition. +/// +[DebuggerDisplay("{" + nameof(DefaultValue) + "}")] +public readonly struct FileSize : IEquatable { - /// - /// File size definition. - /// - [DebuggerDisplay("{" + nameof(DefaultValue) + "}")] - public readonly struct FileSize : IEquatable + public FileSize(long defaultValue) : this() { - public FileSize(long defaultValue) : this() - { - DefaultValue = defaultValue; - } + DefaultValue = defaultValue; + } - /// - /// Gets default size in bytes. - /// - /// File size. - public long DefaultValue { get; } //TODO: make it ulong + /// + /// Gets default size in bytes. + /// + /// File size. + public long DefaultValue { get; } //TODO: make it ulong - #region == Equality =================================================================================================================== - public static implicit operator FileSize(long defaultValue) - { - return new FileSize(defaultValue); - } + #region == Equality =================================================================================================================== + public static implicit operator FileSize(long defaultValue) + { + return new FileSize(defaultValue); + } - public static implicit operator long(FileSize fsize) - { - return fsize.DefaultValue; - } + public static implicit operator long(FileSize fsize) + { + return fsize.DefaultValue; + } - public static implicit operator ulong(FileSize fsize) - { - return (ulong)fsize.DefaultValue; - } + public static implicit operator ulong(FileSize fsize) + { + return (ulong)fsize.DefaultValue; + } - public static FileSize operator +(FileSize first, FileSize second) - { - return new FileSize(first.DefaultValue + second.DefaultValue); - } + public static FileSize operator +(FileSize first, FileSize second) + { + return new FileSize(first.DefaultValue + second.DefaultValue); + } - public static FileSize operator -(FileSize first, FileSize second) - { - return new FileSize(first.DefaultValue - second.DefaultValue); - } + public static FileSize operator -(FileSize first, FileSize second) + { + return new FileSize(first.DefaultValue - second.DefaultValue); + } - public bool Equals(FileSize other) - { - return DefaultValue == other.DefaultValue; - } + public bool Equals(FileSize other) + { + return DefaultValue == other.DefaultValue; + } - public override bool Equals(object obj) - { - if (ReferenceEquals(null, obj)) - return false; + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + return false; - return obj.GetType() == GetType() && Equals((FileSize)obj); - } + return obj.GetType() == GetType() && Equals((FileSize)obj); + } - public override int GetHashCode() - { - return DefaultValue.GetHashCode(); - } - #endregion == Equality =================================================================================================================== + public override int GetHashCode() + { + return DefaultValue.GetHashCode(); } + #endregion == Equality =================================================================================================================== } diff --git a/MailRuCloud/MailRuCloudApi/Base/FileSplitInfo.cs b/MailRuCloud/MailRuCloudApi/Base/FileSplitInfo.cs index 963d0887..2395b639 100644 --- a/MailRuCloud/MailRuCloudApi/Base/FileSplitInfo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/FileSplitInfo.cs @@ -1,9 +1,8 @@ -namespace YaR.Clouds.Base +namespace YaR.Clouds.Base; + +public class FileSplitInfo { - public class FileSplitInfo - { - public bool IsHeader { get; set; } - public bool IsPart => PartNumber > 0; - public int PartNumber { get; set; } - } + public bool IsHeader { get; set; } + public bool IsPart => PartNumber > 0; + public int PartNumber { get; set; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/FilenameServiceInfo.cs b/MailRuCloud/MailRuCloudApi/Base/FilenameServiceInfo.cs index 5a66ae5f..8c96d76c 100644 --- a/MailRuCloud/MailRuCloudApi/Base/FilenameServiceInfo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/FilenameServiceInfo.cs @@ -1,85 +1,84 @@ using System; -namespace YaR.Clouds.Base +namespace YaR.Clouds.Base; + +public class FilenameServiceInfo { - public class FilenameServiceInfo - { - public string CleanName { get; set; } + public string CleanName { get; set; } - public bool IsCrypted => CryptInfo != null; - public CryptInfo CryptInfo { get; set; } + public bool IsCrypted => CryptInfo != null; + public CryptInfo CryptInfo { get; set; } - //public bool IsSplitted => SplitInfo != null; - public FileSplitInfo SplitInfo { get; set; } + //public bool IsSplitted => SplitInfo != null; + public FileSplitInfo SplitInfo { get; set; } - public override string ToString() - { - return WdmrcDots + (SplitInfo?.PartNumber.ToString("D3") ?? "000") + (CryptInfo?.AlignBytes.ToString("x") ?? string.Empty); - } + public override string ToString() + { + return WdmrcDots + (SplitInfo?.PartNumber.ToString("D3") ?? "000") + (CryptInfo?.AlignBytes.ToString("x") ?? string.Empty); + } - public string ToString(bool withName) - { - return withName - ? CleanName + ToString() - : ToString(); - } + public string ToString(bool withName) + { + return withName + ? CleanName + ToString() + : ToString(); + } - public static FilenameServiceInfo Parse(string filename) - { + public static FilenameServiceInfo Parse(string filename) + { - static int HexToInt(char h) + static int HexToInt(char h) + { + return h switch { - return h switch - { - >= '0' and <= '9' => h - '0', - >= 'a' and <= 'f' => h - 'a' + 10, - >= 'A' and <= 'F' => h - 'A' + 10, - _ => -1 - }; - } - - static bool IsDigit(char c) => c is >= '0' and <= '9'; + >= '0' and <= '9' => h - '0', + >= 'a' and <= 'f' => h - 'a' + 10, + >= 'A' and <= 'F' => h - 'A' + 10, + _ => -1 + }; + } + static bool IsDigit(char c) => c is >= '0' and <= '9'; - var res = new FilenameServiceInfo { CleanName = filename, SplitInfo = new FileSplitInfo { IsHeader = true } }; - if (filename.Length < 11) - return res; + var res = new FilenameServiceInfo { CleanName = filename, SplitInfo = new FileSplitInfo { IsHeader = true } }; - var fns = filename.AsSpan(); + if (filename.Length < 11) + return res; - int pos = fns.LastIndexOf(WdmrcDots.AsSpan()); - if (pos < 0) - return res; + var fns = filename.AsSpan(); - int startpos = pos; + int pos = fns.LastIndexOf(WdmrcDots.AsSpan()); + if (pos < 0) + return res; - pos += WdmrcDots.Length; - int parselen = fns.Length - pos; + int startpos = pos; - int align = parselen == 4 ? HexToInt(fns[pos + 3]) : -1; - bool hasDigits = (parselen == 3 || (parselen == 4 && align > -1)) - && IsDigit(fns[pos]) && IsDigit(fns[pos + 1]) && IsDigit(fns[pos + 2]); + pos += WdmrcDots.Length; + int parselen = fns.Length - pos; - if (!hasDigits) - return res; + int align = parselen == 4 ? HexToInt(fns[pos + 3]) : -1; + bool hasDigits = (parselen == 3 || (parselen == 4 && align > -1)) + && IsDigit(fns[pos]) && IsDigit(fns[pos + 1]) && IsDigit(fns[pos + 2]); - res.CleanName = fns[..startpos].ToString(); + if (!hasDigits) + return res; - res.SplitInfo.IsHeader = false; - #if NET48 - res.SplitInfo.PartNumber = int.Parse(fns.Slice(pos, 3).ToString()); - #else - res.SplitInfo.PartNumber = int.Parse(fns.Slice(pos, 3)); - #endif + res.CleanName = fns[..startpos].ToString(); - if (align > -1) - res.CryptInfo = new CryptInfo { AlignBytes = (uint)align }; + res.SplitInfo.IsHeader = false; +#if NET48 + res.SplitInfo.PartNumber = int.Parse(fns.Slice(pos, 3).ToString()); +#else + res.SplitInfo.PartNumber = int.Parse(fns.Slice(pos, 3)); +#endif - return res; - } + if (align > -1) + res.CryptInfo = new CryptInfo { AlignBytes = (uint)align }; - private const string WdmrcDots = ".wdmrc."; + return res; } + + private const string WdmrcDots = ".wdmrc."; } diff --git a/MailRuCloud/MailRuCloudApi/Base/Folder.cs b/MailRuCloud/MailRuCloudApi/Base/Folder.cs index 94f94963..295e5b7b 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Folder.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Folder.cs @@ -6,128 +6,127 @@ using System.IO; using System.Linq; -namespace YaR.Clouds.Base +namespace YaR.Clouds.Base; + +/// +/// Server file info. +/// +[DebuggerDisplay("{" + nameof(FullPath) + "}")] +public class Folder : IEntry { /// - /// Server file info. + /// Initializes a new instance of the class. /// - [DebuggerDisplay("{" + nameof(FullPath) + "}")] - public class Folder : IEntry + public Folder(string fullPath) { - /// - /// Initializes a new instance of the class. - /// - public Folder(string fullPath) - { - FullPath = WebDavPath.Clean(fullPath); - Name = WebDavPath.Name(FullPath); - } + FullPath = WebDavPath.Clean(fullPath); + Name = WebDavPath.Name(FullPath); + } - /// - /// Initializes a new instance of the class. - /// - /// Folder size. - /// Full folder path. - /// Public folder link. - public Folder(FileSize size, string fullPath, IEnumerable publicLinks = null) - : this(fullPath) + /// + /// Initializes a new instance of the class. + /// + /// Folder size. + /// Full folder path. + /// Public folder link. + public Folder(FileSize size, string fullPath, IEnumerable publicLinks = null) + : this(fullPath) + { + Size = size; + if (publicLinks != null) { - Size = size; - if (publicLinks != null) - { - foreach (var link in publicLinks) - PublicLinks.TryAdd(link.Uri.AbsolutePath, link); - } + foreach (var link in publicLinks) + PublicLinks.TryAdd(link.Uri.AbsolutePath, link); } + } - /// - /// Makes copy of this file with new path - /// - /// - /// - public virtual Folder New(string newFullPath, IEnumerable children = null) + /// + /// Makes copy of this file with new path + /// + /// + /// + public virtual Folder New(string newFullPath, IEnumerable children = null) + { + var folder = new Folder(Size, newFullPath, null) { - var folder = new Folder(Size, newFullPath, null) - { - CreationTimeUtc = CreationTimeUtc, - LastAccessTimeUtc = LastAccessTimeUtc, - LastWriteTimeUtc = LastWriteTimeUtc, - Attributes = Attributes, - ServerFoldersCount = ServerFoldersCount, - ServerFilesCount = ServerFilesCount, - Descendants = children != null - ? ImmutableList.Create(children.ToArray()) - : ImmutableList.Empty, - IsChildrenLoaded = children != null - }; - foreach (var linkPair in PublicLinks) - folder.PublicLinks.AddOrUpdate(linkPair.Key, linkPair.Value, (_, _) => linkPair.Value); - - return folder; - } + CreationTimeUtc = CreationTimeUtc, + LastAccessTimeUtc = LastAccessTimeUtc, + LastWriteTimeUtc = LastWriteTimeUtc, + Attributes = Attributes, + ServerFoldersCount = ServerFoldersCount, + ServerFilesCount = ServerFilesCount, + Descendants = children != null + ? ImmutableList.Create(children.ToArray()) + : ImmutableList.Empty, + IsChildrenLoaded = children != null + }; + foreach (var linkPair in PublicLinks) + folder.PublicLinks.AddOrUpdate(linkPair.Key, linkPair.Value, (_, _) => linkPair.Value); + + return folder; + } - /// - /// Gets folder name. - /// - /// Folder name. - public string Name { get; } + /// + /// Gets folder name. + /// + /// Folder name. + public string Name { get; } - /// - /// Gets folder size. - /// - /// Folder size. - public FileSize Size { get; } + /// + /// Gets folder size. + /// + /// Folder size. + public FileSize Size { get; } - /// - /// Gets full folder path on the server. - /// - /// Full folder path. - public string FullPath { get; } + /// + /// Gets full folder path on the server. + /// + /// Full folder path. + public string FullPath { get; } - /// - /// Gets public file link. - /// - /// Public link. - public ConcurrentDictionary PublicLinks - => _publicLinks ??= new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); + /// + /// Gets public file link. + /// + /// Public link. + public ConcurrentDictionary PublicLinks + => _publicLinks ??= new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); - private ConcurrentDictionary _publicLinks; + private ConcurrentDictionary _publicLinks; - public IEnumerable GetPublicLinks(Cloud cloud) - { - return PublicLinks.IsEmpty - ? cloud.GetSharedLinks(FullPath) - : PublicLinks.Values; - } + public IEnumerable GetPublicLinks(Cloud cloud) + { + return PublicLinks.IsEmpty + ? cloud.GetSharedLinks(FullPath) + : PublicLinks.Values; + } - public DateTime CreationTimeUtc { get; set; } = DateTime.Now.AddDays(-1); + public DateTime CreationTimeUtc { get; set; } = DateTime.Now.AddDays(-1); - public DateTime LastWriteTimeUtc { get; set; } = DateTime.Now.AddDays(-1); + public DateTime LastWriteTimeUtc { get; set; } = DateTime.Now.AddDays(-1); - public DateTime LastAccessTimeUtc { get; set; } = DateTime.Now.AddDays(-1); + public DateTime LastAccessTimeUtc { get; set; } = DateTime.Now.AddDays(-1); - public FileAttributes Attributes { get; set; } = FileAttributes.Directory; + public FileAttributes Attributes { get; set; } = FileAttributes.Directory; - public bool IsFile => false; + public bool IsFile => false; - public bool IsChildrenLoaded { get; internal set; } = false; + public bool IsChildrenLoaded { get; internal set; } = false; - public ImmutableList Descendants { get; set; } = ImmutableList.Empty; + public ImmutableList Descendants { get; set; } = ImmutableList.Empty; - public int? ServerFoldersCount { get; set; } - public int? ServerFilesCount { get; set; } + public int? ServerFoldersCount { get; set; } + public int? ServerFilesCount { get; set; } - public PublishInfo ToPublishInfo() - { - var info = new PublishInfo(); - if (!PublicLinks.IsEmpty) - info.Items.Add(new PublishInfoItem { Path = FullPath, Urls = PublicLinks.Select(pli => pli.Value.Uri).ToList() }); - return info; - } + public PublishInfo ToPublishInfo() + { + var info = new PublishInfo(); + if (!PublicLinks.IsEmpty) + info.Items.Add(new PublishInfoItem { Path = FullPath, Urls = PublicLinks.Select(pli => pli.Value.Uri).ToList() }); + return info; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/HashMatchException.cs b/MailRuCloud/MailRuCloudApi/Base/HashMatchException.cs index 2bfc034d..3125b59d 100644 --- a/MailRuCloud/MailRuCloudApi/Base/HashMatchException.cs +++ b/MailRuCloud/MailRuCloudApi/Base/HashMatchException.cs @@ -1,18 +1,17 @@ using System; -namespace YaR.Clouds.Base -{ - public class HashMatchException : Exception - { - public string LocalHash { get; } - public string RemoteHash { get; } +namespace YaR.Clouds.Base; - public HashMatchException(string localHash, string remoteHash) - { - LocalHash = localHash; - RemoteHash = remoteHash; - } +public class HashMatchException : Exception +{ + public string LocalHash { get; } + public string RemoteHash { get; } - public override string Message => $"Local and remote hashes does not match, local = {LocalHash}, remote = {RemoteHash}"; + public HashMatchException(string localHash, string remoteHash) + { + LocalHash = localHash; + RemoteHash = remoteHash; } + + public override string Message => $"Local and remote hashes does not match, local = {LocalHash}, remote = {RemoteHash}"; } diff --git a/MailRuCloud/MailRuCloudApi/Base/HeaderFileContent.cs b/MailRuCloud/MailRuCloudApi/Base/HeaderFileContent.cs index de1d724a..9beb4c01 100644 --- a/MailRuCloud/MailRuCloudApi/Base/HeaderFileContent.cs +++ b/MailRuCloud/MailRuCloudApi/Base/HeaderFileContent.cs @@ -1,12 +1,11 @@ using System; -namespace YaR.Clouds.Base +namespace YaR.Clouds.Base; + +public class HeaderFileContent { - public class HeaderFileContent - { - public string Name { get; set; } - public long Size { get; set; } - public CryptoKeyInfo PublicKey { get; set; } - public DateTime CreationDate { get; set; } - } + public string Name { get; set; } + public long Size { get; set; } + public CryptoKeyInfo PublicKey { get; set; } + public DateTime CreationDate { get; set; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/IBasicCredentials.cs b/MailRuCloud/MailRuCloudApi/Base/IBasicCredentials.cs index 2fd017e4..ab25121d 100644 --- a/MailRuCloud/MailRuCloudApi/Base/IBasicCredentials.cs +++ b/MailRuCloud/MailRuCloudApi/Base/IBasicCredentials.cs @@ -1,10 +1,9 @@ -namespace YaR.Clouds.Base +namespace YaR.Clouds.Base; + +internal interface IBasicCredentials { - internal interface IBasicCredentials - { - string Login { get; } - string Password { get; } + string Login { get; } + string Password { get; } - bool IsAnonymous { get; } - } + bool IsAnonymous { get; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/IEntry.cs b/MailRuCloud/MailRuCloudApi/Base/IEntry.cs index 53d71f20..34787267 100644 --- a/MailRuCloud/MailRuCloudApi/Base/IEntry.cs +++ b/MailRuCloud/MailRuCloudApi/Base/IEntry.cs @@ -2,16 +2,15 @@ using System.Collections.Concurrent; using System.Collections.Immutable; -namespace YaR.Clouds.Base +namespace YaR.Clouds.Base; + +public interface IEntry { - public interface IEntry - { - bool IsFile { get; } - FileSize Size { get; } - string Name { get; } - string FullPath { get; } - DateTime CreationTimeUtc { get; } - ConcurrentDictionary PublicLinks { get; } - ImmutableList Descendants { get; } - } + bool IsFile { get; } + FileSize Size { get; } + string Name { get; } + string FullPath { get; } + DateTime CreationTimeUtc { get; } + ConcurrentDictionary PublicLinks { get; } + ImmutableList Descendants { get; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/PublicLinkInfo.cs b/MailRuCloud/MailRuCloudApi/Base/PublicLinkInfo.cs index 235dd355..159c8b93 100644 --- a/MailRuCloud/MailRuCloudApi/Base/PublicLinkInfo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/PublicLinkInfo.cs @@ -1,60 +1,59 @@ using System; -namespace YaR.Clouds.Base +namespace YaR.Clouds.Base; + +public class PublicLinkInfo { - public class PublicLinkInfo + public PublicLinkInfo(string type, string baseurl, string urlstring)//:this(type, new Uri(baseurl) + new Uri(urlstring))) { - public PublicLinkInfo(string type, string baseurl, string urlstring)//:this(type, new Uri(baseurl) + new Uri(urlstring))) - { - Init(type, baseurl, urlstring); - } + Init(type, baseurl, urlstring); + } - public PublicLinkInfo(string type, string urlstring):this(type, new Uri(urlstring)) - { - } + public PublicLinkInfo(string type, string urlstring) : this(type, new Uri(urlstring)) + { + } - public PublicLinkInfo(string urlstring):this("", new Uri(urlstring)) - { - } + public PublicLinkInfo(string urlstring) : this("", new Uri(urlstring)) + { + } - public PublicLinkInfo(string type, Uri url) - { - Init(type,url); - } + public PublicLinkInfo(string type, Uri url) + { + Init(type, url); + } - public PublicLinkInfo(Uri url):this("", url) - { - } + public PublicLinkInfo(Uri url) : this("", url) + { + } - private void Init(string type, string baseurl, string urlstring) - { - var u = UrlPathCombine(baseurl, urlstring); - Init(type, u); + private void Init(string type, string baseurl, string urlstring) + { + var u = UrlPathCombine(baseurl, urlstring); + Init(type, u); - } + } - private void Init(string type, Uri url) - { - if (!url.IsAbsoluteUri) - throw new ArgumentException("Absolute uri required"); + private void Init(string type, Uri url) + { + if (!url.IsAbsoluteUri) + throw new ArgumentException("Absolute uri required"); - Type = type; - Uri = url; - } + Type = type; + Uri = url; + } - public string Type { get; set; } + public string Type { get; set; } - public Uri Uri { get; private set; } + public Uri Uri { get; private set; } - public string Key { get; set; } + public string Key { get; set; } - private static Uri UrlPathCombine(string path1, string path2) - { - path1 = path1.TrimEnd('/') + "/"; - path2 = path2.TrimStart('/'); + private static Uri UrlPathCombine(string path1, string path2) + { + path1 = path1.TrimEnd('/') + "/"; + path2 = path2.TrimStart('/'); - return new Uri(path1 + path2, UriKind.Absolute); + return new Uri(path1 + path2, UriKind.Absolute); - } } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/DtoImportExtensions.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/DtoImportExtensions.cs index e7d8e9c5..0db137c5 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/DtoImportExtensions.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/DtoImportExtensions.cs @@ -8,158 +8,157 @@ using YaR.Clouds.Base.Requests.Types; using YaR.Clouds.Extensions; -namespace YaR.Clouds.Base.Repos +namespace YaR.Clouds.Base.Repos; + +internal static class DtoImportExtensions { - internal static class DtoImportExtensions + internal static IEntry ToEntry(this ListRequest.Result data) + { + Cloud.ItemType itemType = data.Item is FsFile ? Cloud.ItemType.File : Cloud.ItemType.Folder; + + var entry = itemType == Cloud.ItemType.File + ? (IEntry)data.ToFile() + : (IEntry)data.ToFolder(); + + return entry; + } + + internal static File ToFile(this ListRequest.Result data) + { + var source = data.Item as FsFile; + var res = source.ToFile();//new File(data.FullPath, (long)source.Size, source.Sha1.ToHexString()); + return res; + } + + internal static File ToFile(this FsFile data) { - internal static IEntry ToEntry(this ListRequest.Result data) - { - Cloud.ItemType itemType = data.Item is FsFile ? Cloud.ItemType.File : Cloud.ItemType.Folder; - - var entry = itemType == Cloud.ItemType.File - ? (IEntry)data.ToFile() - : (IEntry)data.ToFolder(); - - return entry; - } - - internal static File ToFile(this ListRequest.Result data) - { - var source = data.Item as FsFile; - var res = source.ToFile();//new File(data.FullPath, (long)source.Size, source.Sha1.ToHexString()); - return res; - } - - internal static File ToFile(this FsFile data) - { - var res = new File(data.FullPath, (long) data.Size, new FileHashMrc(data.Sha1.ToHexString())) - { - CreationTimeUtc = data.ModifDate, - LastAccessTimeUtc = data.ModifDate, - LastWriteTimeUtc = data.ModifDate //TODO: this is the main time ( - }; - return res; - } - - internal static IEnumerable ToGroupedFiles(this IEnumerable list) - { - var groupedFiles = list - .GroupBy(f => f.ServiceInfo.CleanName, - file => file) - //TODO: DIRTY: if group contains header file, than make SplittedFile, else do not group - .SelectMany(group => group.Count() == 1 - ? group.Take(1) - : group.Any(f => f.Name == f.ServiceInfo.CleanName) - ? Enumerable.Repeat(new SplittedFile(group.ToList()), 1) - : group.Select(file => file)); - - return groupedFiles; - } - - internal static Folder ToFolder(this ListRequest.Result data) - { - var res = (data.Item as FsFolder)?.ToFolder(); - return res; - } - - internal static Folder ToFolder(this FsFolder data) + var res = new File(data.FullPath, (long)data.Size, new FileHashMrc(data.Sha1.ToHexString())) { - var folder = new Folder((long)data.Size, data.FullPath) { IsChildrenLoaded = data.IsChildrenLoaded }; + CreationTimeUtc = data.ModifDate, + LastAccessTimeUtc = data.ModifDate, + LastWriteTimeUtc = data.ModifDate //TODO: this is the main time ( + }; + return res; + } + + internal static IEnumerable ToGroupedFiles(this IEnumerable list) + { + var groupedFiles = list + .GroupBy(f => f.ServiceInfo.CleanName, + file => file) + //TODO: DIRTY: if group contains header file, than make SplittedFile, else do not group + .SelectMany(group => group.Count() == 1 + ? group.Take(1) + : group.Any(f => f.Name == f.ServiceInfo.CleanName) + ? Enumerable.Repeat(new SplittedFile(group.ToList()), 1) + : group.Select(file => file)); + + return groupedFiles; + } + + internal static Folder ToFolder(this ListRequest.Result data) + { + var res = (data.Item as FsFolder)?.ToFolder(); + return res; + } + + internal static Folder ToFolder(this FsFolder data) + { + var folder = new Folder((long)data.Size, data.FullPath) { IsChildrenLoaded = data.IsChildrenLoaded }; - var children = new List(); - children.AddRange( - data.Items - .OfType() - .Select(f => f.ToFile()) - .ToGroupedFiles()); + var children = new List(); + children.AddRange( + data.Items + .OfType() + .Select(f => f.ToFile()) + .ToGroupedFiles()); - children.AddRange( - data.Items - .OfType() - .Select(f => f.ToFolder())); + children.AddRange( + data.Items + .OfType() + .Select(f => f.ToFolder())); - folder.Descendants = folder.Descendants.AddRange(children); + folder.Descendants = folder.Descendants.AddRange(children); - return folder; - } + return folder; + } - internal static CopyResult ToCopyResult(this MoveRequest.Result data, string newName) + internal static CopyResult ToCopyResult(this MoveRequest.Result data, string newName) + { + var res = new CopyResult { - var res = new CopyResult - { - IsSuccess = true, - NewName = newName - }; - return res; - } - - internal static RenameResult ToRenameResult(this MoveRequest.Result data) + IsSuccess = true, + NewName = newName + }; + return res; + } + + internal static RenameResult ToRenameResult(this MoveRequest.Result data) + { + var res = new RenameResult { - var res = new RenameResult - { - IsSuccess = true - }; - return res; - } - - internal static AddFileResult ToAddFileResult(this MobAddFileRequest.Result data) + IsSuccess = true + }; + return res; + } + + internal static AddFileResult ToAddFileResult(this MobAddFileRequest.Result data) + { + var res = new AddFileResult { - var res = new AddFileResult - { - Success = data.IsSuccess, - Path = data.Path - }; - return res; - } + Success = data.IsSuccess, + Path = data.Path + }; + return res; + } + + internal static AuthTokenResult ToAuthTokenResult(this OAuthRefreshRequest.Result data, string refreshToken) + { + if (data.ErrorCode > 0) + throw new Exception($"OAuth: Error Code={data.ErrorCode}, Value={data.Error}, Description={data.ErrorDescription}"); - internal static AuthTokenResult ToAuthTokenResult(this OAuthRefreshRequest.Result data, string refreshToken) + var res = new AuthTokenResult { - if (data.ErrorCode > 0) - throw new Exception($"OAuth: Error Code={data.ErrorCode}, Value={data.Error}, Description={data.ErrorDescription}"); + IsSuccess = true, + Token = data.AccessToken, + ExpiresIn = TimeSpan.FromSeconds(data.ExpiresIn), + RefreshToken = refreshToken + }; - var res = new AuthTokenResult - { - IsSuccess = true, - Token = data.AccessToken, - ExpiresIn = TimeSpan.FromSeconds(data.ExpiresIn), - RefreshToken = refreshToken - }; + return res; + } - return res; - } + internal static AuthTokenResult ToAuthTokenResult(this OAuthRequest.Result data) + { + if (data.ErrorCode > 0 && data.ErrorCode != 15) + throw new AuthenticationException($"OAuth: Error Code={data.ErrorCode}, Value={data.Error}, Description={data.ErrorDescription}"); - internal static AuthTokenResult ToAuthTokenResult(this OAuthRequest.Result data) + var res = new AuthTokenResult { - if (data.ErrorCode > 0 && data.ErrorCode != 15) - throw new AuthenticationException($"OAuth: Error Code={data.ErrorCode}, Value={data.Error}, Description={data.ErrorDescription}"); - - var res = new AuthTokenResult - { - IsSuccess = true, - Token = data.AccessToken, - ExpiresIn = TimeSpan.FromSeconds(data.ExpiresIn), - RefreshToken = data.RefreshToken, - IsSecondStepRequired = data.ErrorCode == 15, - TsaToken = data.TsaToken - }; - - return res; - } - - internal static CreateFolderResult ToCreateFolderResult(this CreateFolderRequest.Result data) + IsSuccess = true, + Token = data.AccessToken, + ExpiresIn = TimeSpan.FromSeconds(data.ExpiresIn), + RefreshToken = data.RefreshToken, + IsSecondStepRequired = data.ErrorCode == 15, + TsaToken = data.TsaToken + }; + + return res; + } + + internal static CreateFolderResult ToCreateFolderResult(this CreateFolderRequest.Result data) + { + var res = new CreateFolderResult { - var res = new CreateFolderResult - { - IsSuccess = data.OperationResult == OperationResult.Ok, - Path = data.Path - }; - return res; - } + IsSuccess = data.OperationResult == OperationResult.Ok, + Path = data.Path + }; + return res; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/IAuth.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/IAuth.cs index 5a945542..5590cd51 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/IAuth.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/IAuth.cs @@ -1,18 +1,17 @@ using System.Net; -namespace YaR.Clouds.Base.Repos +namespace YaR.Clouds.Base.Repos; + +public interface IAuth { - public interface IAuth - { - bool IsAnonymous { get; } + bool IsAnonymous { get; } - string Login { get; } - string Password { get; } - string AccessToken { get; } - string DownloadToken { get; } + string Login { get; } + string Password { get; } + string AccessToken { get; } + string DownloadToken { get; } - CookieContainer Cookies { get; } + CookieContainer Cookies { get; } - void ExpireDownloadToken(); - } + void ExpireDownloadToken(); } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/ICloudHasher.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/ICloudHasher.cs index 9a489a38..2029644b 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/ICloudHasher.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/ICloudHasher.cs @@ -1,16 +1,15 @@ using System; using System.IO; -namespace YaR.Clouds.Base.Repos +namespace YaR.Clouds.Base.Repos; + +public interface ICloudHasher : IDisposable { - public interface ICloudHasher : IDisposable - { - string Name { get; } + string Name { get; } - void Append(byte[] buffer, int offset, int count); - void Append(Stream stream); + void Append(byte[] buffer, int offset, int count); + void Append(Stream stream); - IFileHash Hash { get; } - long Length { get; } - } + IFileHash Hash { get; } + long Length { get; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/IRequestRepo.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/IRequestRepo.cs index 5ae6631a..fb0bf832 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/IRequestRepo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/IRequestRepo.cs @@ -8,59 +8,58 @@ using YaR.Clouds.Base.Streams; using YaR.Clouds.Common; -namespace YaR.Clouds.Base.Repos +namespace YaR.Clouds.Base.Repos; + +public interface IRequestRepo { - public interface IRequestRepo - { - IAuth Auth { get; } - HttpCommonSettings HttpSettings { get; } + IAuth Auth { get; } + HttpCommonSettings HttpSettings { get; } - Task DetectOutsideChanges(); + Task DetectOutsideChanges(); - Stream GetDownloadStream(File file, long? start = null, long? end = null); + Stream GetDownloadStream(File file, long? start = null, long? end = null); - Task DoUpload(HttpClient client, PushStreamContent content, File file); + Task DoUpload(HttpClient client, PushStreamContent content, File file); - Task FolderInfo(RemotePath path, int offset = 0, int limit = int.MaxValue, int depth = 1); + Task FolderInfo(RemotePath path, int offset = 0, int limit = int.MaxValue, int depth = 1); - Task ItemInfo(RemotePath path, int offset = 0, int limit = int.MaxValue); + Task ItemInfo(RemotePath path, int offset = 0, int limit = int.MaxValue); - Task AccountInfo(); + Task AccountInfo(); - Task CreateFolder(string path); + Task CreateFolder(string path); - Task AddFile(string fileFullPath, IFileHash fileHash, FileSize fileSize, DateTime dateTime, ConflictResolver? conflictResolver); + Task AddFile(string fileFullPath, IFileHash fileHash, FileSize fileSize, DateTime dateTime, ConflictResolver? conflictResolver); - Task CloneItem(string fromUrl, string toPath); + Task CloneItem(string fromUrl, string toPath); - Task Copy(string sourceFullPath, string destinationPath, ConflictResolver? conflictResolver = null); + Task Copy(string sourceFullPath, string destinationPath, ConflictResolver? conflictResolver = null); - Task Move(string sourceFullPath, string destinationPath, ConflictResolver? conflictResolver = null); + Task Move(string sourceFullPath, string destinationPath, ConflictResolver? conflictResolver = null); - Task Publish(string fullPath); + Task Publish(string fullPath); - Task Unpublish(Uri publicLink, string fullPath); + Task Unpublish(Uri publicLink, string fullPath); - Task Remove(string fullPath); + Task Remove(string fullPath); - Task Rename(string fullPath, string newName); + Task Rename(string fullPath, string newName); - //TODO: move to inner repo functionality - Dictionary GetShardInfo1(); + //TODO: move to inner repo functionality + Dictionary GetShardInfo1(); - IEnumerable GetShareLinks(string path); + IEnumerable GetShareLinks(string path); - void CleanTrash(); + void CleanTrash(); - //TODO: bad quick patch - string ConvertToVideoLink(Uri publicLink, SharedVideoResolution videoResolution); + //TODO: bad quick patch + string ConvertToVideoLink(Uri publicLink, SharedVideoResolution videoResolution); - ICloudHasher GetHasher(); + ICloudHasher GetHasher(); - bool SupportsAddSmallFileByHash { get; } - bool SupportsDeduplicate { get; } + bool SupportsAddSmallFileByHash { get; } + bool SupportsDeduplicate { get; } - string PublicBaseUrlDefault { get; } - } + string PublicBaseUrlDefault { get; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/DtoImport.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/DtoImport.cs index 8f8dec3e..1d1e4218 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/DtoImport.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/DtoImport.cs @@ -7,342 +7,341 @@ using YaR.Clouds.Links; using System.Collections.Immutable; -namespace YaR.Clouds.Base.Repos.MailRuCloud +namespace YaR.Clouds.Base.Repos.MailRuCloud; + +internal static class DtoImportMailRu { - internal static class DtoImportMailRu - { - private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(DtoImportMailRu)); + private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(DtoImportMailRu)); - public static UnpublishResult ToUnpublishResult(this CommonOperationResult data) + public static UnpublishResult ToUnpublishResult(this CommonOperationResult data) + { + var res = new UnpublishResult { - var res = new UnpublishResult - { - IsSuccess = data.Status == 200 - }; - return res; - } + IsSuccess = data.Status == 200 + }; + return res; + } - public static RenameResult ToRenameResult(this CommonOperationResult data) + public static RenameResult ToRenameResult(this CommonOperationResult data) + { + var res = new RenameResult { - var res = new RenameResult - { - IsSuccess = data.Status == 200 - }; - return res; - } + IsSuccess = data.Status == 200 + }; + return res; + } - public static RemoveResult ToRemoveResult(this CommonOperationResult data) + public static RemoveResult ToRemoveResult(this CommonOperationResult data) + { + var res = new RemoveResult { - var res = new RemoveResult - { - IsSuccess = data.Status == 200 - }; - return res; - } + IsSuccess = data.Status == 200 + }; + return res; + } - public static PublishResult ToPublishResult(this CommonOperationResult data) + public static PublishResult ToPublishResult(this CommonOperationResult data) + { + var res = new PublishResult { - var res = new PublishResult - { - IsSuccess = data.Status == 200, - Url = data.Body - }; - return res; - } + IsSuccess = data.Status == 200, + Url = data.Body + }; + return res; + } - public static CopyResult ToCopyResult(this CommonOperationResult data) + public static CopyResult ToCopyResult(this CommonOperationResult data) + { + var res = new CopyResult { - var res = new CopyResult - { - IsSuccess = data.Status == 200, - NewName = data.Body - }; - return res; - } + IsSuccess = data.Status == 200, + NewName = data.Body + }; + return res; + } - public static CloneItemResult ToCloneItemResult(this CommonOperationResult data) + public static CloneItemResult ToCloneItemResult(this CommonOperationResult data) + { + var res = new CloneItemResult { - var res = new CloneItemResult - { - IsSuccess = data.Status == 200, - Path = data.Body - }; - return res; - } + IsSuccess = data.Status == 200, + Path = data.Body + }; + return res; + } - public static AddFileResult ToAddFileResult(this CommonOperationResult data) + public static AddFileResult ToAddFileResult(this CommonOperationResult data) + { + var res = new AddFileResult { - var res = new AddFileResult - { - Success = data.Status == 200, - Path = data.Body - }; - return res; - } + Success = data.Status == 200, + Path = data.Body + }; + return res; + } - //TODO: move to repo - public static UploadFileResult ToUploadPathResult(this HttpResponseMessage response) - { - var res = new UploadFileResult { HttpStatusCode = response.StatusCode, HasReturnedData = false }; + //TODO: move to repo + public static UploadFileResult ToUploadPathResult(this HttpResponseMessage response) + { + var res = new UploadFileResult { HttpStatusCode = response.StatusCode, HasReturnedData = false }; - if (!response.IsSuccessStatusCode) - return res; + if (!response.IsSuccessStatusCode) + return res; - var strres = response.Content.ReadAsStringAsync().Result; + var strres = response.Content.ReadAsStringAsync().Result; - if (string.IsNullOrEmpty(strres)) - return res; + if (string.IsNullOrEmpty(strres)) + return res; - res.HasReturnedData = true; + res.HasReturnedData = true; - var resp = strres.Split(';'); + var resp = strres.Split(';'); - res.Hash = new FileHashMrc(resp[0]); - res.Size = resp.Length > 1 - ? long.Parse(resp[1].Trim('\r', '\n', ' ')) - : 0; + res.Hash = new FileHashMrc(resp[0]); + res.Size = resp.Length > 1 + ? long.Parse(resp[1].Trim('\r', '\n', ' ')) + : 0; - return res; - } + return res; + } - public static CreateFolderResult ToCreateFolderResult(this CommonOperationResult data) + public static CreateFolderResult ToCreateFolderResult(this CommonOperationResult data) + { + var res = new CreateFolderResult { - var res = new CreateFolderResult - { - IsSuccess = data.Status == 200, - Path = data.Body - }; - return res; - } + IsSuccess = data.Status == 200, + Path = data.Body + }; + return res; + } - public static AccountInfoResult ToAccountInfo(this AccountInfoRequestResult data) + public static AccountInfoResult ToAccountInfo(this AccountInfoRequestResult data) + { + var res = new AccountInfoResult { - var res = new AccountInfoResult - { - FileSizeLimit = data.Body.Cloud.FileSizeLimit, + FileSizeLimit = data.Body.Cloud.FileSizeLimit, - DiskUsage = new DiskUsage - { - Total = data.Body.Cloud.Space.BytesTotal, //total * 1024 * 1024, - Used = data.Body.Cloud.Space.BytesUsed, //used * 1024 * 1024, - OverQuota = data.Body.Cloud.Space.IsOverquota - } - }; + DiskUsage = new DiskUsage + { + Total = data.Body.Cloud.Space.BytesTotal, //total * 1024 * 1024, + Used = data.Body.Cloud.Space.BytesUsed, //used * 1024 * 1024, + OverQuota = data.Body.Cloud.Space.IsOverquota + } + }; - return res; - } + return res; + } - public static AuthTokenResult ToAuthTokenResult(this AuthTokenRequestResult data) + public static AuthTokenResult ToAuthTokenResult(this AuthTokenRequestResult data) + { + var res = new AuthTokenResult { - var res = new AuthTokenResult - { - IsSuccess = true, - Token = data.Body.Token, - ExpiresIn = TimeSpan.FromMinutes(58), - RefreshToken = string.Empty - }; + IsSuccess = true, + Token = data.Body.Token, + ExpiresIn = TimeSpan.FromMinutes(58), + RefreshToken = string.Empty + }; - return res; - } + return res; + } - public static string ToToken(this DownloadTokenResult data) - { - var res = data.Body.Token; - return res; - } + public static string ToToken(this DownloadTokenResult data) + { + var res = data.Body.Token; + return res; + } - public static Dictionary ToShardInfo(this ShardInfoRequestResult webdata) + public static Dictionary ToShardInfo(this ShardInfoRequestResult webdata) + { + var dict = new Dictionary { - var dict = new Dictionary - { - {ShardType.Video, new ShardInfo{Type = ShardType.Video, Url = webdata.Body.Video[0].Url} }, - {ShardType.ViewDirect, new ShardInfo{Type = ShardType.ViewDirect, Url = webdata.Body.ViewDirect[0].Url} }, - {ShardType.WeblinkView, new ShardInfo{Type = ShardType.WeblinkView, Url = webdata.Body.WeblinkView[0].Url} }, - {ShardType.WeblinkVideo, new ShardInfo{Type = ShardType.WeblinkVideo, Url = webdata.Body.WeblinkVideo[0].Url} }, - {ShardType.WeblinkGet, new ShardInfo{Type = ShardType.WeblinkGet, Url = webdata.Body.WeblinkGet[0].Url} }, - {ShardType.WeblinkThumbnails, new ShardInfo{Type = ShardType.WeblinkThumbnails, Url = webdata.Body.WeblinkThumbnails[0].Url} }, - {ShardType.Auth, new ShardInfo{Type = ShardType.Auth, Url = webdata.Body.Auth[0].Url} }, - {ShardType.View, new ShardInfo{Type = ShardType.View, Url = webdata.Body.View[0].Url} }, - {ShardType.Get, new ShardInfo{Type = ShardType.Get, Url = webdata.Body.Get[0].Url} }, - {ShardType.Upload, new ShardInfo{Type = ShardType.Upload, Url = webdata.Body.Upload[0].Url} }, - {ShardType.Thumbnails, new ShardInfo{Type = ShardType.Thumbnails, Url = webdata.Body.Thumbnails[0].Url} } - }; - - return dict; - } + {ShardType.Video, new ShardInfo{Type = ShardType.Video, Url = webdata.Body.Video[0].Url} }, + {ShardType.ViewDirect, new ShardInfo{Type = ShardType.ViewDirect, Url = webdata.Body.ViewDirect[0].Url} }, + {ShardType.WeblinkView, new ShardInfo{Type = ShardType.WeblinkView, Url = webdata.Body.WeblinkView[0].Url} }, + {ShardType.WeblinkVideo, new ShardInfo{Type = ShardType.WeblinkVideo, Url = webdata.Body.WeblinkVideo[0].Url} }, + {ShardType.WeblinkGet, new ShardInfo{Type = ShardType.WeblinkGet, Url = webdata.Body.WeblinkGet[0].Url} }, + {ShardType.WeblinkThumbnails, new ShardInfo{Type = ShardType.WeblinkThumbnails, Url = webdata.Body.WeblinkThumbnails[0].Url} }, + {ShardType.Auth, new ShardInfo{Type = ShardType.Auth, Url = webdata.Body.Auth[0].Url} }, + {ShardType.View, new ShardInfo{Type = ShardType.View, Url = webdata.Body.View[0].Url} }, + {ShardType.Get, new ShardInfo{Type = ShardType.Get, Url = webdata.Body.Get[0].Url} }, + {ShardType.Upload, new ShardInfo{Type = ShardType.Upload, Url = webdata.Body.Upload[0].Url} }, + {ShardType.Thumbnails, new ShardInfo{Type = ShardType.Thumbnails, Url = webdata.Body.Thumbnails[0].Url} } + }; + + return dict; + } - private static Folder ToFolder(this FolderInfoResult.FolderInfoBody.FolderInfoProps item, string publicBaseUrl) + private static Folder ToFolder(this FolderInfoResult.FolderInfoBody.FolderInfoProps item, string publicBaseUrl) + { + var folder = new Folder(item.Size, item.Home ?? item.Name, item.Weblink.ToPublicLinkInfos(publicBaseUrl)) { - var folder = new Folder(item.Size, item.Home ?? item.Name, item.Weblink.ToPublicLinkInfos(publicBaseUrl)) - { - ServerFoldersCount = item.Count?.Folders, - ServerFilesCount = item.Count?.Files - }; - return folder; - } + ServerFoldersCount = item.Count?.Folders, + ServerFilesCount = item.Count?.Files + }; + return folder; + } - private static readonly string[] FolderKinds = { "folder", "camera-upload", "mounted", "shared" }; + private static readonly string[] FolderKinds = { "folder", "camera-upload", "mounted", "shared" }; - public static IEntry ToEntry(this FolderInfoResult data, string publicBaseUrl) - { - if (data.Body.Kind == "file") - return data.ToFile(publicBaseUrl); - - var folder = new Folder(data.Body.Size, WebDavPath.Combine(data.Body.Home ?? WebDavPath.Root, data.Body.Name)); - - var children = new List(); - children.AddRange( - data.Body.List - .Where(it => it.Kind == "file") - .Select(item => item.ToFile(publicBaseUrl, "")) - .ToGroupedFiles() - .Select(f => f)); - children.AddRange( - data.Body.List - .Where(it => FolderKinds.Contains(it.Kind)) - .Select(item => item.ToFolder(publicBaseUrl)) - .Select(f => f)); - - folder.Descendants = folder.Descendants.AddRange(children); - return folder; - } + public static IEntry ToEntry(this FolderInfoResult data, string publicBaseUrl) + { + if (data.Body.Kind == "file") + return data.ToFile(publicBaseUrl); - public static Folder ToFolder(this FolderInfoResult data, string publicBaseUrl, string home = null, Link link = null) - { - PatchEntryPath(data, home, link); + var folder = new Folder(data.Body.Size, WebDavPath.Combine(data.Body.Home ?? WebDavPath.Root, data.Body.Name)); - var body = data.Body; + var children = new List(); + children.AddRange( + data.Body.List + .Where(it => it.Kind == "file") + .Select(item => item.ToFile(publicBaseUrl, "")) + .ToGroupedFiles() + .Select(f => f)); + children.AddRange( + data.Body.List + .Where(it => FolderKinds.Contains(it.Kind)) + .Select(item => item.ToFolder(publicBaseUrl)) + .Select(f => f)); + + folder.Descendants = folder.Descendants.AddRange(children); + return folder; + } - var folder = new Folder(body.Size, body.Home ?? body.Name, body.Weblink.ToPublicLinkInfos(publicBaseUrl)) - { - ServerFoldersCount = body.Count?.Folders, - ServerFilesCount = body.Count?.Files, - }; - - var children = new List(); - children.AddRange( - body.List - .Where(it => it.Kind == "file") - .Select(item => item.ToFile(publicBaseUrl, "")) - .ToGroupedFiles() - .Select(f => f)); - children.AddRange( - body.List - .Where(it => FolderKinds.Contains(it.Kind)) - .Select(item => item.ToFolder(publicBaseUrl)) - .Select(f => f)); - - folder.Descendants = folder.Descendants.AddRange(children); - return folder; - } + public static Folder ToFolder(this FolderInfoResult data, string publicBaseUrl, string home = null, Link link = null) + { + PatchEntryPath(data, home, link); - /// - /// When it's a linked item, need to shift paths - /// - /// - /// - /// - private static void PatchEntryPath(FolderInfoResult data, string home, Link link) + var body = data.Body; + + var folder = new Folder(body.Size, body.Home ?? body.Name, body.Weblink.ToPublicLinkInfos(publicBaseUrl)) { - if (string.IsNullOrEmpty(home) || null == link) - return; + ServerFoldersCount = body.Count?.Folders, + ServerFilesCount = body.Count?.Files, + }; - foreach (var propse in data.Body.List) - { - string name = link.OriginalName == propse.Name ? link.Name : propse.Name; - propse.Home = WebDavPath.Combine(home, name); - propse.Name = name; - } - data.Body.Home = home; + var children = new List(); + children.AddRange( + body.List + .Where(it => it.Kind == "file") + .Select(item => item.ToFile(publicBaseUrl, "")) + .ToGroupedFiles() + .Select(f => f)); + children.AddRange( + body.List + .Where(it => FolderKinds.Contains(it.Kind)) + .Select(item => item.ToFolder(publicBaseUrl)) + .Select(f => f)); + + folder.Descendants = folder.Descendants.AddRange(children); + return folder; + } + + /// + /// When it's a linked item, need to shift paths + /// + /// + /// + /// + private static void PatchEntryPath(FolderInfoResult data, string home, Link link) + { + if (string.IsNullOrEmpty(home) || null == link) + return; + + foreach (var propse in data.Body.List) + { + string name = link.OriginalName == propse.Name ? link.Name : propse.Name; + propse.Home = WebDavPath.Combine(home, name); + propse.Name = name; } + data.Body.Home = home; + } - //TODO: subject to heavily refact - public static File ToFile(this FolderInfoResult data, string publicBaseUrl, - string home = null, Link ulink = null, string fileName = null, string nameReplacement = null) + //TODO: subject to heavily refact + public static File ToFile(this FolderInfoResult data, string publicBaseUrl, + string home = null, Link ulink = null, string fileName = null, string nameReplacement = null) + { + if ((ulink == null || ulink.IsLinkedToFileSystem) && + string.IsNullOrEmpty(fileName)) { - if ((ulink == null || ulink.IsLinkedToFileSystem) && - string.IsNullOrEmpty(fileName)) - { - return new File(WebDavPath.Combine(data.Body.Home ?? "", data.Body.Name), data.Body.Size); - } + return new File(WebDavPath.Combine(data.Body.Home ?? "", data.Body.Name), data.Body.Size); + } - PatchEntryPath(data, home, ulink); + PatchEntryPath(data, home, ulink); - var z = data.Body.List? - .Where(it => it.Kind == "file") - .Select(it => fileName != null && it.Name == fileName - ? it.ToFile(publicBaseUrl, nameReplacement) - : it.ToFile(publicBaseUrl, "")) - .ToList(); + var z = data.Body.List? + .Where(it => it.Kind == "file") + .Select(it => fileName != null && it.Name == fileName + ? it.ToFile(publicBaseUrl, nameReplacement) + : it.ToFile(publicBaseUrl, "")) + .ToList(); - var cmpName = string.IsNullOrEmpty(nameReplacement) - ? fileName - : nameReplacement; + var cmpName = string.IsNullOrEmpty(nameReplacement) + ? fileName + : nameReplacement; - if (string.IsNullOrEmpty(cmpName) && data.Body.Weblink != "/" && ulink is { IsLinkedToFileSystem: false }) - { - cmpName = WebDavPath.Name(ulink.PublicLinks.Values.FirstOrDefault()?.Uri.OriginalString ?? string.Empty); - } + if (string.IsNullOrEmpty(cmpName) && data.Body.Weblink != "/" && ulink is { IsLinkedToFileSystem: false }) + { + cmpName = WebDavPath.Name(ulink.PublicLinks.Values.FirstOrDefault()?.Uri.OriginalString ?? string.Empty); + } - var groupedFile = z?.ToGroupedFiles(); + var groupedFile = z?.ToGroupedFiles(); - var res = groupedFile?.FirstOrDefault(it => string.IsNullOrEmpty(cmpName) || it.Name == cmpName); + var res = groupedFile?.FirstOrDefault(it => string.IsNullOrEmpty(cmpName) || it.Name == cmpName); - return res; - } + return res; + } - private static File ToFile(this FolderInfoResult.FolderInfoBody.FolderInfoProps item, - string publicBaseUrl, string nameReplacement) + private static File ToFile(this FolderInfoResult.FolderInfoBody.FolderInfoProps item, + string publicBaseUrl, string nameReplacement) + { + try { - try - { - var path = string.IsNullOrEmpty(nameReplacement) - ? item.Home - : WebDavPath.Combine(WebDavPath.Parent(item.Home), nameReplacement); + var path = string.IsNullOrEmpty(nameReplacement) + ? item.Home + : WebDavPath.Combine(WebDavPath.Parent(item.Home), nameReplacement); - var file = new File(path ?? item.Name, item.Size, new FileHashMrc(item.Hash)); - foreach (var link in item.Weblink.ToPublicLinkInfos(publicBaseUrl)) - file.PublicLinks.TryAdd(link.Uri.AbsolutePath, link); + var file = new File(path ?? item.Name, item.Size, new FileHashMrc(item.Hash)); + foreach (var link in item.Weblink.ToPublicLinkInfos(publicBaseUrl)) + file.PublicLinks.TryAdd(link.Uri.AbsolutePath, link); - var dt = UnixTimeStampToDateTime(item.Mtime, file.CreationTimeUtc); - file.CreationTimeUtc = - file.LastAccessTimeUtc = - file.LastWriteTimeUtc = dt; + var dt = UnixTimeStampToDateTime(item.Mtime, file.CreationTimeUtc); + file.CreationTimeUtc = + file.LastAccessTimeUtc = + file.LastWriteTimeUtc = dt; - return file; - } - catch (Exception e) - { - Console.WriteLine(e); - throw; - } + return file; + } + catch (Exception e) + { + Console.WriteLine(e); + throw; } + } - private static DateTime UnixTimeStampToDateTime(ulong unixTimeStamp, DateTime defaultvalue) + private static DateTime UnixTimeStampToDateTime(ulong unixTimeStamp, DateTime defaultvalue) + { + try { - try - { - // Unix timestamp is seconds past epoch - var dtDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); - dtDateTime = dtDateTime.AddSeconds(unixTimeStamp); //.ToLocalTime(); - doesn't need, clients usually convert to localtime by itself - return dtDateTime; - } - catch (Exception e) - { - Logger.Error($"Error converting unixTimeStamp {unixTimeStamp} to DateTime, {e.Message}"); - return defaultvalue; - } + // Unix timestamp is seconds past epoch + var dtDateTime = new DateTime(1970, 1, 1, 0, 0, 0, 0, DateTimeKind.Utc); + dtDateTime = dtDateTime.AddSeconds(unixTimeStamp); //.ToLocalTime(); - doesn't need, clients usually convert to localtime by itself + return dtDateTime; + } + catch (Exception e) + { + Logger.Error($"Error converting unixTimeStamp {unixTimeStamp} to DateTime, {e.Message}"); + return defaultvalue; } } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/FileHashMrc.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/FileHashMrc.cs index f3d7de7d..80ff152c 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/FileHashMrc.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/FileHashMrc.cs @@ -1,32 +1,31 @@ using System; using YaR.Clouds.Extensions; -namespace YaR.Clouds.Base.Repos.MailRuCloud +namespace YaR.Clouds.Base.Repos.MailRuCloud; + +readonly struct FileHashMrc : IFileHash { - readonly struct FileHashMrc : IFileHash + public FileHashMrc(string value) { - public FileHashMrc(string value) - { - Hash = new Hash(FileHashType.Mrc, value); - } + Hash = new Hash(FileHashType.Mrc, value); + } - public FileHashMrc(byte[] value) : this(value.ToHexString()) - { - } + public FileHashMrc(byte[] value) : this(value.ToHexString()) + { + } - public Hash Get(FileHashType htype) - { - if (htype != FileHashType.Mrc) - throw new ArgumentException($"Mail.ru Cloud supportd only {FileHashType.Mrc} hash type"); + public Hash Get(FileHashType htype) + { + if (htype != FileHashType.Mrc) + throw new ArgumentException($"Mail.ru Cloud supportd only {FileHashType.Mrc} hash type"); - return Hash; - } + return Hash; + } - public Hash Hash { get; } + public Hash Hash { get; } - public override string ToString() - { - return $"{FileHashType.Mrc}={Hash.Value}"; - } + public override string ToString() + { + return $"{FileHashType.Mrc}={Hash.Value}"; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/MailRuBaseRepo.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/MailRuBaseRepo.cs index a984d984..45366f8c 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/MailRuBaseRepo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/MailRuBaseRepo.cs @@ -9,102 +9,101 @@ using YaR.Clouds.Common; using YaR.Clouds.Extensions; -namespace YaR.Clouds.Base.Repos.MailRuCloud +namespace YaR.Clouds.Base.Repos.MailRuCloud; + +abstract class MailRuBaseRepo { - abstract class MailRuBaseRepo - { - private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(MailRuBaseRepo)); + private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(MailRuBaseRepo)); - public static readonly string[] AvailDomains = {"mail", "inbox", "bk", "list", "vk", "internet"}; + public static readonly string[] AvailDomains = { "mail", "inbox", "bk", "list", "vk", "internet" }; - protected MailRuBaseRepo(IBasicCredentials credentials) - { - Credentials = credentials; + protected MailRuBaseRepo(IBasicCredentials credentials) + { + Credentials = credentials; - if (AvailDomains.Any(d => credentials.Login.Contains($"@{d}."))) - return; + if (AvailDomains.Any(d => credentials.Login.Contains($"@{d}."))) + return; - string domains = AvailDomains.Aggregate((c, n) => c + ", @" + n); - Logger.Warn($"Missing domain part ({domains}) in login, file and folder deleting will be denied"); - } + string domains = AvailDomains.Aggregate((c, n) => c + ", @" + n); + Logger.Warn($"Missing domain part ({domains}) in login, file and folder deleting will be denied"); + } - protected readonly IBasicCredentials Credentials; + protected readonly IBasicCredentials Credentials; - public IAuth Auth { get; protected set; } - public abstract HttpCommonSettings HttpSettings { get; } + public IAuth Auth { get; protected set; } + public abstract HttpCommonSettings HttpSettings { get; } - public abstract Task GetShardInfo(ShardType shardType); + public abstract Task GetShardInfo(ShardType shardType); - public string ConvertToVideoLink(Uri publicLink, SharedVideoResolution videoResolution) - { - string linkstring = publicLink.PathAndQuery; - return GetShardInfo(ShardType.WeblinkVideo).Result.Url + - videoResolution.ToEnumMemberValue() + "/" + - Common.Base64Encode(linkstring.TrimStart('/')) + - ".m3u8?double_encode=1"; - } - - public ICloudHasher GetHasher() - { - return new MailRuSha1Hash(); - } - - public bool SupportsAddSmallFileByHash => true; - public bool SupportsDeduplicate => true; + public string ConvertToVideoLink(Uri publicLink, SharedVideoResolution videoResolution) + { + string linkstring = publicLink.PathAndQuery; + return GetShardInfo(ShardType.WeblinkVideo).Result.Url + + videoResolution.ToEnumMemberValue() + "/" + + Common.Base64Encode(linkstring.TrimStart('/')) + + ".m3u8?double_encode=1"; + } + public ICloudHasher GetHasher() + { + return new MailRuSha1Hash(); + } - private HttpRequestMessage UploadClientRequest(PushStreamContent content, File file) - { - var shard = GetShardInfo(ShardType.Upload).Result; - var url = new Uri($"{shard.Url}?token={Auth.AccessToken}"); //cloud_domain=2&x-email={Authenticator.Login.Replace("@", "%40")}& - - var request = new HttpRequestMessage - { - RequestUri = url, - Method = HttpMethod.Put - }; - request.Headers.TryAddWithoutValidation("User-Agent", HttpSettings.UserAgent); - - //request.Headers.Add("Host", url.Host); - //request.Headers.Add("Connection", "keep-alive"); - //request.Headers.Add("X-Requested-With", "XMLHttpRequest"); - //request.Headers.Add("Accept", "*/*"); - //request.Headers.Add("Origin", "https://cloud.mail.ru"); - //request.Headers.Add("Sec-Fetch-Site", "same-site"); - //request.Headers.Add("Sec-Fetch-Mode", "cors"); - //request.Headers.Add("Sec-Fetch-Dest", "empty"); - //string path = file.Path.Replace("\\", "/"); - //request.Headers.Add("Referer", $"https://cloud.mail.ru/home{path}"); - //request.Headers.Add("Accept-Encoding", "gzip, deflate, br"); - //request.Headers.Add("Accept-Language", "ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7"); - //request.Headers.TryAddWithoutValidation("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36"); - //request.Headers.Add("X-CSRF-Token", Authenticator.AccessToken); - //request.Headers.Add("Token", Authenticator.AccessToken); - //request.Headers.Add("Access-token", Authenticator.AccessToken); - //content.Headers.ContentType = new MediaTypeHeaderValue("image/png"); - - request.Content = content; - request.Content.Headers.ContentLength = file.OriginalSize; - - return request; - } - - public async Task DoUpload(HttpClient client, PushStreamContent content, File file) - { - var request = UploadClientRequest(content, file); - var responseMessage = await client.SendAsync(request); - var ures = responseMessage.ToUploadPathResult(); + public bool SupportsAddSmallFileByHash => true; + public bool SupportsDeduplicate => true; - return ures; - } + private HttpRequestMessage UploadClientRequest(PushStreamContent content, File file) + { + var shard = GetShardInfo(ShardType.Upload).Result; + var url = new Uri($"{shard.Url}?token={Auth.AccessToken}"); //cloud_domain=2&x-email={Authenticator.Login.Replace("@", "%40")}& - public IEnumerable PublicBaseUrls { get; set; } = new[] + var request = new HttpRequestMessage { - "https://cloud.mail.ru/public", - "https:/cloud.mail.ru/public" //TODO: may be obsolete? + RequestUri = url, + Method = HttpMethod.Put }; + request.Headers.TryAddWithoutValidation("User-Agent", HttpSettings.UserAgent); + + //request.Headers.Add("Host", url.Host); + //request.Headers.Add("Connection", "keep-alive"); + //request.Headers.Add("X-Requested-With", "XMLHttpRequest"); + //request.Headers.Add("Accept", "*/*"); + //request.Headers.Add("Origin", "https://cloud.mail.ru"); + //request.Headers.Add("Sec-Fetch-Site", "same-site"); + //request.Headers.Add("Sec-Fetch-Mode", "cors"); + //request.Headers.Add("Sec-Fetch-Dest", "empty"); + //string path = file.Path.Replace("\\", "/"); + //request.Headers.Add("Referer", $"https://cloud.mail.ru/home{path}"); + //request.Headers.Add("Accept-Encoding", "gzip, deflate, br"); + //request.Headers.Add("Accept-Language", "ru-RU,ru;q=0.9,en-US;q=0.8,en;q=0.7"); + //request.Headers.TryAddWithoutValidation("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36"); + //request.Headers.Add("X-CSRF-Token", Authenticator.AccessToken); + //request.Headers.Add("Token", Authenticator.AccessToken); + //request.Headers.Add("Access-token", Authenticator.AccessToken); + //content.Headers.ContentType = new MediaTypeHeaderValue("image/png"); + + request.Content = content; + request.Content.Headers.ContentLength = file.OriginalSize; + + return request; + } - public string PublicBaseUrlDefault => PublicBaseUrls.FirstOrDefault(); + public async Task DoUpload(HttpClient client, PushStreamContent content, File file) + { + var request = UploadClientRequest(content, file); + var responseMessage = await client.SendAsync(request); + var ures = responseMessage.ToUploadPathResult(); + + return ures; } + + + public IEnumerable PublicBaseUrls { get; set; } = new[] + { + "https://cloud.mail.ru/public", + "https:/cloud.mail.ru/public" //TODO: may be obsolete? + }; + + public string PublicBaseUrlDefault => PublicBaseUrls.FirstOrDefault(); } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/MailRuSha1Hash.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/MailRuSha1Hash.cs index 95a2a794..6621910a 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/MailRuSha1Hash.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/MailRuSha1Hash.cs @@ -3,95 +3,94 @@ using System.Security.Cryptography; using System.Text; -namespace YaR.Clouds.Base.Repos.MailRuCloud +namespace YaR.Clouds.Base.Repos.MailRuCloud; + +public class MailRuSha1Hash : ICloudHasher { - public class MailRuSha1Hash : ICloudHasher + public MailRuSha1Hash() { - public MailRuSha1Hash() - { - _sha1.Initialize(); - AppendInitBuffer(); - } + _sha1.Initialize(); + AppendInitBuffer(); + } - public string Name => "mrcsha1"; + public string Name => "mrcsha1"; - public void Append(byte[] buffer, int offset, int length) - { - if (_isClosed) - throw new Exception("Cannot append because MRSHA1 already calculated."); + public void Append(byte[] buffer, int offset, int length) + { + if (_isClosed) + throw new Exception("Cannot append because MRSHA1 already calculated."); - if (_length < 20) - Array.Copy(buffer, offset, _smallContent, _length, Math.Min(length, 20 - _length)); + if (_length < 20) + Array.Copy(buffer, offset, _smallContent, _length, Math.Min(length, 20 - _length)); - _sha1.TransformBlock(buffer, offset, length, null, 0); - _length += length; - } + _sha1.TransformBlock(buffer, offset, length, null, 0); + _length += length; + } - public void Append(byte[] buffer) - { - Append(buffer, 0, buffer.Length); - } + public void Append(byte[] buffer) + { + Append(buffer, 0, buffer.Length); + } - public void Append(Stream stream) - { - if (_isClosed) - throw new Exception("Cannot append because MRSHA1 already calculated."); + public void Append(Stream stream) + { + if (_isClosed) + throw new Exception("Cannot append because MRSHA1 already calculated."); - var buffer = new byte[8192]; - int read; - while ((read = stream.Read(buffer, 0, buffer.Length)) > 0) - { - Append(buffer, 0, read); - } + var buffer = new byte[8192]; + int read; + while ((read = stream.Read(buffer, 0, buffer.Length)) > 0) + { + Append(buffer, 0, read); } + } - public string HashString => Hash.Hash.Value; //BitConverter.ToString(Hash).Replace("-", string.Empty); + public string HashString => Hash.Hash.Value; //BitConverter.ToString(Hash).Replace("-", string.Empty); - public IFileHash Hash + public IFileHash Hash + { + get { - get - { - if (null != _hash) - return new FileHashMrc(_hash); - - if (_length <= 20) - _hash = _smallContent; - else - { - AppendFinalBuffer(); - - _sha1.TransformFinalBlock(Array.Empty(), 0, 0); - _hash = _sha1.Hash; - } - _isClosed = true; + if (null != _hash) return new FileHashMrc(_hash); + + if (_length <= 20) + _hash = _smallContent; + else + { + AppendFinalBuffer(); + + _sha1.TransformFinalBlock(Array.Empty(), 0, 0); + _hash = _sha1.Hash; } + _isClosed = true; + return new FileHashMrc(_hash); } + } - public long Length => 20; + public long Length => 20; - private byte[] _hash; + private byte[] _hash; - private readonly byte[] _smallContent = new byte[20]; - private readonly SHA1 _sha1 = SHA1.Create(); - private long _length; - private bool _isClosed; + private readonly byte[] _smallContent = new byte[20]; + private readonly SHA1 _sha1 = SHA1.Create(); + private long _length; + private bool _isClosed; - private void AppendInitBuffer() - { - var initBuffer = Encoding.UTF8.GetBytes("mrCloud"); - _sha1.TransformBlock(initBuffer, 0, initBuffer.Length, null, 0); - } + private void AppendInitBuffer() + { + var initBuffer = Encoding.UTF8.GetBytes("mrCloud"); + _sha1.TransformBlock(initBuffer, 0, initBuffer.Length, null, 0); + } - private void AppendFinalBuffer() - { - var finalBuffer = Encoding.UTF8.GetBytes(_length.ToString()); - _sha1.TransformBlock(finalBuffer, 0, finalBuffer.Length, null, 0); - } + private void AppendFinalBuffer() + { + var finalBuffer = Encoding.UTF8.GetBytes(_length.ToString()); + _sha1.TransformBlock(finalBuffer, 0, finalBuffer.Length, null, 0); + } - public void Dispose() - { - _sha1?.Dispose(); - } + public void Dispose() + { + _sha1?.Dispose(); } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/MobileRequestRepo.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/MobileRequestRepo.cs index 7193a30d..04416150 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/MobileRequestRepo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/MobileRequestRepo.cs @@ -10,263 +10,262 @@ using YaR.Clouds.Base.Requests.Types; using YaR.Clouds.Common; -namespace YaR.Clouds.Base.Repos.MailRuCloud.Mobile +namespace YaR.Clouds.Base.Repos.MailRuCloud.Mobile; + +/// +/// Part of Mobile protocol. +/// Not usable. +/// +class MobileRequestRepo : MailRuBaseRepo, IRequestRepo { - /// - /// Part of Mobile protocol. - /// Not usable. - /// - class MobileRequestRepo : MailRuBaseRepo, IRequestRepo - { - private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(MobileRequestRepo)); + private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(MobileRequestRepo)); - private readonly SemaphoreSlim _connectionLimiter; + private readonly SemaphoreSlim _connectionLimiter; - public override HttpCommonSettings HttpSettings { get; } = new() - { - ClientId = "cloud-win", - UserAgent = "CloudDiskOWindows 17.12.0009 beta WzBbt1Ygbm", - BaseDomain = "https://cloud.mail.ru" - }; + public override HttpCommonSettings HttpSettings { get; } = new() + { + ClientId = "cloud-win", + UserAgent = "CloudDiskOWindows 17.12.0009 beta WzBbt1Ygbm", + BaseDomain = "https://cloud.mail.ru" + }; - public MobileRequestRepo(CloudSettings settings, IWebProxy proxy, IAuth auth, int listDepth) - : base(new Credentials(settings, auth.Login, auth.Password)) - { - ServicePointManager.DefaultConnectionLimit = int.MaxValue; + public MobileRequestRepo(CloudSettings settings, IWebProxy proxy, IAuth auth, int listDepth) + : base(new Credentials(settings, auth.Login, auth.Password)) + { + ServicePointManager.DefaultConnectionLimit = int.MaxValue; - // required for Windows 7 breaking connection - ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13 | SecurityProtocolType.Tls12; + // required for Windows 7 breaking connection + ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13 | SecurityProtocolType.Tls12; - _connectionLimiter = new SemaphoreSlim(settings.MaxConnectionCount); - _listDepth = listDepth; + _connectionLimiter = new SemaphoreSlim(settings.MaxConnectionCount); + _listDepth = listDepth; - HttpSettings.CloudSettings = settings; - HttpSettings.Proxy = proxy; + HttpSettings.CloudSettings = settings; + HttpSettings.Proxy = proxy; - Auth = auth; + Auth = auth; - _metaServer = new Cached(_ => - { - Logger.Debug("MetaServer expired, refreshing."); - var server = new MobMetaServerRequest(HttpSettings).MakeRequestAsync(_connectionLimiter).Result; - return server; - }, - _ => TimeSpan.FromSeconds(MetaServerExpiresSec)); - - //_downloadServer = new Cached(old => - // { - // Logger.Debug("DownloadServer expired, refreshing."); - // var server = new GetServerRequest(HttpSettings).MakeRequestAsync(_connectionLimiter).Result; - // return server; - // }, - // value => TimeSpan.FromSeconds(DownloadServerExpiresSec)); - } + _metaServer = new Cached(_ => + { + Logger.Debug("MetaServer expired, refreshing."); + var server = new MobMetaServerRequest(HttpSettings).MakeRequestAsync(_connectionLimiter).Result; + return server; + }, + _ => TimeSpan.FromSeconds(MetaServerExpiresSec)); + + //_downloadServer = new Cached(old => + // { + // Logger.Debug("DownloadServer expired, refreshing."); + // var server = new GetServerRequest(HttpSettings).MakeRequestAsync(_connectionLimiter).Result; + // return server; + // }, + // value => TimeSpan.FromSeconds(DownloadServerExpiresSec)); + } - private readonly Cached _metaServer; - private const int MetaServerExpiresSec = 20 * 60; + private readonly Cached _metaServer; + private const int MetaServerExpiresSec = 20 * 60; - //private readonly Cached _downloadServer; - private readonly int _listDepth; - //private const int DownloadServerExpiresSec = 20 * 60; + //private readonly Cached _downloadServer; + private readonly int _listDepth; + //private const int DownloadServerExpiresSec = 20 * 60; - //public HttpWebRequest UploadRequest(File file, UploadMultipartBoundary boundary) - //{ - // throw new NotImplementedException(); - //} + //public HttpWebRequest UploadRequest(File file, UploadMultipartBoundary boundary) + //{ + // throw new NotImplementedException(); + //} - public Stream GetDownloadStream(File file, long? start = null, long? end = null) - { - throw new NotImplementedException(); - } + public Stream GetDownloadStream(File file, long? start = null, long? end = null) + { + throw new NotImplementedException(); + } - //public HttpWebRequest DownloadRequest(long instart, long inend, File file, ShardInfo shard) - //{ - // string url = $"{_downloadServer.Value.Url}{Uri.EscapeDataString(file.FullPath)}?token={Authenticator.AccessToken}&client_id={HttpSettings.ClientId}"; + //public HttpWebRequest DownloadRequest(long instart, long inend, File file, ShardInfo shard) + //{ + // string url = $"{_downloadServer.Value.Url}{Uri.EscapeDataString(file.FullPath)}?token={Authenticator.AccessToken}&client_id={HttpSettings.ClientId}"; - // var request = (HttpWebRequest)WebRequest.Create(url); + // var request = (HttpWebRequest)WebRequest.Create(url); - // request.Headers.Add("Accept-Ranges", "bytes"); - // request.AddRange(instart, inend); - // request.Proxy = HttpSettings.Proxy; - // request.CookieContainer = Authenticator.Cookies; - // request.Method = "GET"; - // request.ContentType = MediaTypeNames.Application.Octet; - // request.Accept = "*/*"; - // request.UserAgent = HttpSettings.UserAgent; - // request.AllowReadStreamBuffering = false; + // request.Headers.Add("Accept-Ranges", "bytes"); + // request.AddRange(instart, inend); + // request.Proxy = HttpSettings.Proxy; + // request.CookieContainer = Authenticator.Cookies; + // request.Method = "GET"; + // request.ContentType = MediaTypeNames.Application.Octet; + // request.Accept = "*/*"; + // request.UserAgent = HttpSettings.UserAgent; + // request.AllowReadStreamBuffering = false; - // request.Timeout = 15 * 1000; + // request.Timeout = 15 * 1000; - // return request; - //} + // return request; + //} - //public void BanShardInfo(ShardInfo banShard) - //{ - // //TODO: implement - // Logger.Warn($"{nameof(MobileRequestRepo)}.{nameof(BanShardInfo)} not implemented"); - //} + //public void BanShardInfo(ShardInfo banShard) + //{ + // //TODO: implement + // Logger.Warn($"{nameof(MobileRequestRepo)}.{nameof(BanShardInfo)} not implemented"); + //} - public override Task GetShardInfo(ShardType shardType) - { - //TODO: must hide shard functionality into repo after DownloadStream and UploadStream refact + public override Task GetShardInfo(ShardType shardType) + { + //TODO: must hide shard functionality into repo after DownloadStream and UploadStream refact - var shi = new ShardInfo - { - Url = _metaServer.Value.Url, - Type = shardType - }; + var shi = new ShardInfo + { + Url = _metaServer.Value.Url, + Type = shardType + }; - return Task.FromResult(shi); - } + return Task.FromResult(shi); + } - public Task CloneItem(string fromUrl, string toPath) - { - throw new NotImplementedException(); - } + public Task CloneItem(string fromUrl, string toPath) + { + throw new NotImplementedException(); + } - public Task Copy(string sourceFullPath, string destinationPath, ConflictResolver? conflictResolver = null) - { - throw new NotImplementedException(); - } + public Task Copy(string sourceFullPath, string destinationPath, ConflictResolver? conflictResolver = null) + { + throw new NotImplementedException(); + } - public Task Move(string sourceFullPath, string destinationPath, ConflictResolver? conflictResolver = null) - { - throw new NotImplementedException(); - } + public Task Move(string sourceFullPath, string destinationPath, ConflictResolver? conflictResolver = null) + { + throw new NotImplementedException(); + } - public async Task FolderInfo(RemotePath path, int offset = 0, int limit = int.MaxValue, int depth = 1) - { - if (path.IsLink) - throw new NotImplementedException(nameof(FolderInfo)); + public async Task FolderInfo(RemotePath path, int offset = 0, int limit = int.MaxValue, int depth = 1) + { + if (path.IsLink) + throw new NotImplementedException(nameof(FolderInfo)); - var req = new ListRequest(HttpSettings, Auth, _metaServer.Value.Url, path.Path, _listDepth); - var res = await req.MakeRequestAsync(_connectionLimiter); + var req = new ListRequest(HttpSettings, Auth, _metaServer.Value.Url, path.Path, _listDepth); + var res = await req.MakeRequestAsync(_connectionLimiter); - switch (res.Item) + switch (res.Item) + { + case FsFolder fsFolder: { - case FsFolder fsFolder: + var folder = new Folder(fsFolder.Size == null ? 0 : (long)fsFolder.Size.Value, fsFolder.FullPath); + var children = new List(); + foreach (var fsi in fsFolder.Items) { - var folder = new Folder(fsFolder.Size == null ? 0 : (long)fsFolder.Size.Value, fsFolder.FullPath); - var children = new List(); - foreach (var fsi in fsFolder.Items) + switch (fsi) { - switch (fsi) + case FsFile fsfi: { - case FsFile fsfi: + var fi = new File(fsfi.FullPath, (long)fsfi.Size, new FileHashMrc(fsfi.Sha1)) { - var fi = new File(fsfi.FullPath, (long)fsfi.Size, new FileHashMrc(fsfi.Sha1)) - { - CreationTimeUtc = fsfi.ModifDate, - LastWriteTimeUtc = fsfi.ModifDate - }; - children.Add(fi); - break; - } - case FsFolder fsfo: - { - var fo = new Folder(fsfo.Size == null ? 0 : (long) fsfo.Size.Value, fsfo.FullPath); - children.Add(fo); - break; - } - default: - throw new Exception($"Unknown item type {fsi.GetType()}"); + CreationTimeUtc = fsfi.ModifDate, + LastWriteTimeUtc = fsfi.ModifDate + }; + children.Add(fi); + break; + } + case FsFolder fsfo: + { + var fo = new Folder(fsfo.Size == null ? 0 : (long)fsfo.Size.Value, fsfo.FullPath); + children.Add(fo); + break; } + default: + throw new Exception($"Unknown item type {fsi.GetType()}"); } - folder.Descendants = folder.Descendants.AddRange(children); - return folder; } - case FsFile fsFile: + folder.Descendants = folder.Descendants.AddRange(children); + return folder; + } + case FsFile fsFile: + { + var fi = new File(fsFile.FullPath, (long)fsFile.Size, new FileHashMrc(fsFile.Sha1)) { - var fi = new File(fsFile.FullPath, (long)fsFile.Size, new FileHashMrc(fsFile.Sha1)) - { - CreationTimeUtc = fsFile.ModifDate, - LastWriteTimeUtc = fsFile.ModifDate - }; + CreationTimeUtc = fsFile.ModifDate, + LastWriteTimeUtc = fsFile.ModifDate + }; - return fi; - } - default: - return null; + return fi; } + default: + return null; } + } - public Task ItemInfo(RemotePath path, int offset = 0, int limit = int.MaxValue) - { - throw new NotImplementedException(); - } - + public Task ItemInfo(RemotePath path, int offset = 0, int limit = int.MaxValue) + { + throw new NotImplementedException(); + } - public async Task AccountInfo() - { - var req = await new AccountInfoRequest(HttpSettings, Auth).MakeRequestAsync(_connectionLimiter); - var res = req.ToAccountInfo(); - return res; - } - public Task Publish(string fullPath) - { - throw new NotImplementedException(); - } + public async Task AccountInfo() + { + var req = await new AccountInfoRequest(HttpSettings, Auth).MakeRequestAsync(_connectionLimiter); + var res = req.ToAccountInfo(); + return res; + } - public Task Unpublish(Uri publicLink, string fullPath = null) - { - throw new NotImplementedException(); - } + public Task Publish(string fullPath) + { + throw new NotImplementedException(); + } - public Task Remove(string fullPath) - { - throw new NotImplementedException(); - } + public Task Unpublish(Uri publicLink, string fullPath = null) + { + throw new NotImplementedException(); + } - public async Task Rename(string fullPath, string newName) - { - string target = WebDavPath.Combine(WebDavPath.Parent(fullPath), newName); + public Task Remove(string fullPath) + { + throw new NotImplementedException(); + } - await new MoveRequest(HttpSettings, Auth, _metaServer.Value.Url, fullPath, target) - .MakeRequestAsync(_connectionLimiter); - var res = new RenameResult { IsSuccess = true }; - return res; - } + public async Task Rename(string fullPath, string newName) + { + string target = WebDavPath.Combine(WebDavPath.Parent(fullPath), newName); - public Dictionary GetShardInfo1() - { - throw new NotImplementedException("Mobile GetShardInfo1 not implemented"); - } + await new MoveRequest(HttpSettings, Auth, _metaServer.Value.Url, fullPath, target) + .MakeRequestAsync(_connectionLimiter); + var res = new RenameResult { IsSuccess = true }; + return res; + } - public IEnumerable GetShareLinks(string fullPath) - { - throw new NotImplementedException("Mobile GetShareLink not implemented"); - } + public Dictionary GetShardInfo1() + { + throw new NotImplementedException("Mobile GetShardInfo1 not implemented"); + } - public void CleanTrash() - { - throw new NotImplementedException(); - } + public IEnumerable GetShareLinks(string fullPath) + { + throw new NotImplementedException("Mobile GetShareLink not implemented"); + } - public async Task CreateFolder(string path) - { - var folerRequest = await new CreateFolderRequest(HttpSettings, Auth, _metaServer.Value.Url, path) - .MakeRequestAsync(_connectionLimiter); - return folerRequest.ToCreateFolderResult(); - } + public void CleanTrash() + { + throw new NotImplementedException(); + } - public async Task AddFile(string fileFullPath, IFileHash fileHash, FileSize fileSize, DateTime dateTime, ConflictResolver? conflictResolver) - { - var res = await new MobAddFileRequest(HttpSettings, Auth, _metaServer.Value.Url, - fileFullPath, fileHash.Hash.Value, fileSize, dateTime, conflictResolver) - .MakeRequestAsync(_connectionLimiter); + public async Task CreateFolder(string path) + { + var folerRequest = await new CreateFolderRequest(HttpSettings, Auth, _metaServer.Value.Url, path) + .MakeRequestAsync(_connectionLimiter); + return folerRequest.ToCreateFolderResult(); + } - return res.ToAddFileResult(); - } + public async Task AddFile(string fileFullPath, IFileHash fileHash, FileSize fileSize, DateTime dateTime, ConflictResolver? conflictResolver) + { + var res = await new MobAddFileRequest(HttpSettings, Auth, _metaServer.Value.Url, + fileFullPath, fileHash.Hash.Value, fileSize, dateTime, conflictResolver) + .MakeRequestAsync(_connectionLimiter); - public async Task DetectOutsideChanges() => await Task.FromResult(null); + return res.ToAddFileResult(); } + + public async Task DetectOutsideChanges() => await Task.FromResult(null); } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/AccountInfoRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/AccountInfoRequest.cs index d04ee195..2feeab60 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/AccountInfoRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/AccountInfoRequest.cs @@ -1,14 +1,13 @@ using YaR.Clouds.Base.Requests; using YaR.Clouds.Base.Requests.Types; -namespace YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests +namespace YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests; + +class AccountInfoRequest : BaseRequestJson { - class AccountInfoRequest : BaseRequestJson + public AccountInfoRequest(HttpCommonSettings settings, IAuth auth) : base(settings, auth) { - public AccountInfoRequest(HttpCommonSettings settings, IAuth auth) : base(settings, auth) - { - } - - protected override string RelationalUri => $"{_settings.BaseDomain}/api/m1/user?access_token={_auth.AccessToken}"; } + + protected override string RelationalUri => $"{_settings.BaseDomain}/api/m1/user?access_token={_auth.AccessToken}"; } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/BaseRequestMobile.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/BaseRequestMobile.cs index 759cea93..d6d97016 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/BaseRequestMobile.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/BaseRequestMobile.cs @@ -2,22 +2,21 @@ using YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests.Types; using YaR.Clouds.Base.Requests; -namespace YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests +namespace YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests; + +abstract class BaseRequestMobile : BaseRequest where T : class { - abstract class BaseRequestMobile : BaseRequest where T : class - { - private readonly string _metaServer; + private readonly string _metaServer; - protected BaseRequestMobile(HttpCommonSettings settings, IAuth auth, string metaServer) : base(settings, auth) - { - _metaServer = metaServer; - } + protected BaseRequestMobile(HttpCommonSettings settings, IAuth auth, string metaServer) : base(settings, auth) + { + _metaServer = metaServer; + } - protected override string RelationalUri => $"{_metaServer}?token={_auth.AccessToken}&client_id={_settings.ClientId}"; + protected override string RelationalUri => $"{_metaServer}?token={_auth.AccessToken}&client_id={_settings.ClientId}"; - protected override ResponseBodyStream Transport(Stream stream) - { - return new ResponseBodyStream(stream); - } + protected override ResponseBodyStream Transport(Stream stream) + { + return new ResponseBodyStream(stream); } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/CreateFolderRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/CreateFolderRequest.cs index 66875a9c..98560907 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/CreateFolderRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/CreateFolderRequest.cs @@ -3,64 +3,63 @@ using YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests.Types; using YaR.Clouds.Base.Requests; -namespace YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests +namespace YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests; + +class CreateFolderRequest : BaseRequestMobile { - class CreateFolderRequest : BaseRequestMobile + private readonly string _fullPath; + + public CreateFolderRequest(HttpCommonSettings settings, IAuth auth, string metaServer, string fullPath) + : base(settings, auth, metaServer) { - private readonly string _fullPath; + _fullPath = fullPath; + } - public CreateFolderRequest(HttpCommonSettings settings, IAuth auth, string metaServer, string fullPath) - : base(settings, auth, metaServer) - { - _fullPath = fullPath; - } + protected override byte[] CreateHttpContent() + { + using var stream = new RequestBodyStream(); - protected override byte[] CreateHttpContent() - { - using var stream = new RequestBodyStream(); + stream.WritePu16((byte)Operation.CreateFolder); + stream.WritePu16(Revision); + stream.WriteString(_fullPath); + stream.WritePu32(0); + var body = stream.GetBytes(); + return body; + } - stream.WritePu16((byte)Operation.CreateFolder); - stream.WritePu16(Revision); - stream.WriteString(_fullPath); - stream.WritePu32(0); - var body = stream.GetBytes(); - return body; - } + protected override RequestResponse DeserializeMessage(NameValueCollection responseHeaders, ResponseBodyStream data) + { + var opres = (OpResult)(int)data.OperationResult; - protected override RequestResponse DeserializeMessage(NameValueCollection responseHeaders, ResponseBodyStream data) + if (opres == OpResult.Ok) { - var opres = (OpResult)(int)data.OperationResult; - - if (opres == OpResult.Ok) + return new RequestResponse { - return new RequestResponse + Ok = true, + Result = new Result { - Ok = true, - Result = new Result - { - OperationResult = data.OperationResult, - Path = _fullPath - } - }; - } - - throw new Exception($"{nameof(CreateFolderRequest)} failed with result code {opres}"); + OperationResult = data.OperationResult, + Path = _fullPath + } + }; } - private const int Revision = 0; + throw new Exception($"{nameof(CreateFolderRequest)} failed with result code {opres}"); + } - public class Result : BaseResponseResult - { - public string Path { get; set; } - } + private const int Revision = 0; - private enum OpResult - { - Ok = 0, - SourceNotExists = 1, - AlreadyExists = 4, - AlreadyExistsDifferentCase = 9, - Failed254 = 254 - } + public class Result : BaseResponseResult + { + public string Path { get; set; } + } + + private enum OpResult + { + Ok = 0, + SourceNotExists = 1, + AlreadyExists = 4, + AlreadyExistsDifferentCase = 9, + Failed254 = 254 } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/GetServerRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/GetServerRequest.cs index 1ca53e44..0f29fb99 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/GetServerRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/GetServerRequest.cs @@ -1,13 +1,12 @@ using YaR.Clouds.Base.Requests; -namespace YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests +namespace YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests; + +internal class GetServerRequest : ServerRequest { - internal class GetServerRequest : ServerRequest + public GetServerRequest(HttpCommonSettings settings) : base(settings) { - public GetServerRequest(HttpCommonSettings settings) : base(settings) - { - } - - protected override string RelationalUri => "https://dispatcher.cloud.mail.ru/d"; } + + protected override string RelationalUri => "https://dispatcher.cloud.mail.ru/d"; } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/GetUploadServerRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/GetUploadServerRequest.cs index 0d651105..75792afe 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/GetUploadServerRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/GetUploadServerRequest.cs @@ -1,13 +1,12 @@ using YaR.Clouds.Base.Requests; -namespace YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests +namespace YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests; + +internal class GetUploadServerRequest : ServerRequest { - internal class GetUploadServerRequest : ServerRequest + public GetUploadServerRequest(HttpCommonSettings settings) : base(settings) { - public GetUploadServerRequest(HttpCommonSettings settings) : base(settings) - { - } - - protected override string RelationalUri => "https://dispatcher.cloud.mail.ru/u"; } + + protected override string RelationalUri => "https://dispatcher.cloud.mail.ru/u"; } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/ListRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/ListRequest.cs index 5061fc75..5dde690a 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/ListRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/ListRequest.cs @@ -4,255 +4,254 @@ using YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests.Types; using YaR.Clouds.Base.Requests; -namespace YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests +namespace YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests; + +internal class ListRequest : BaseRequestMobile { - internal class ListRequest : BaseRequestMobile + private readonly string _fullPath; + + public ListRequest(HttpCommonSettings settings, IAuth auth, string metaServer, string fullPath, int depth) + : base(settings, auth, metaServer) { - private readonly string _fullPath; + _fullPath = fullPath; + Depth = depth; + } - public ListRequest(HttpCommonSettings settings, IAuth auth, string metaServer, string fullPath, int depth) - : base(settings, auth, metaServer) - { - _fullPath = fullPath; - Depth = depth; - } + /// + /// Folder list depth + /// + public long Depth { get; set; } + public Option Options { get; set; } = Option.Unknown128 | Option.Unknown256 | Option.FolderSize | Option.TotalSpace | Option.UsedSpace; + + [Flags] + internal enum Option + { /// - /// Folder list depth + /// Request total cloud space /// - public long Depth { get; set; } - - public Option Options { get; set; } = Option.Unknown128 | Option.Unknown256 | Option.FolderSize | Option.TotalSpace | Option.UsedSpace; + TotalSpace = 1, + /// + /// Dunno, something when delete + /// + Delete = 2, + Fingerprint = 4, + Unknown8 = 8, + Unknown16 = 16, + /// + /// Request folders size + /// + FolderSize = 32, + /// + /// Request used cloud space + /// + UsedSpace = 64, + Unknown128 = 128, + Unknown256 = 256 + } - [Flags] - internal enum Option - { - /// - /// Request total cloud space - /// - TotalSpace = 1, - /// - /// Dunno, something when delete - /// - Delete = 2, - Fingerprint = 4, - Unknown8 = 8, - Unknown16 = 16, - /// - /// Request folders size - /// - FolderSize = 32, - /// - /// Request used cloud space - /// - UsedSpace = 64, - Unknown128 = 128, - Unknown256 = 256 - } + protected override byte[] CreateHttpContent() + { + using var stream = new RequestBodyStream(); - protected override byte[] CreateHttpContent() - { - using var stream = new RequestBodyStream(); + stream.WritePu16((byte)Operation.FolderList); + stream.WriteString(_fullPath); + stream.WritePu32(Depth); - stream.WritePu16((byte)Operation.FolderList); - stream.WriteString(_fullPath); - stream.WritePu32(Depth); + stream.WritePu32((int)Options); - stream.WritePu32((int)Options); + stream.WriteWithLength(Array.Empty()); - stream.WriteWithLength(Array.Empty()); + var body = stream.GetBytes(); + return body; + } - var body = stream.GetBytes(); - return body; + protected override RequestResponse DeserializeMessage(NameValueCollection responseHeaders, ResponseBodyStream data) + { + switch (data.OperationResult) + { + case OperationResult.Ok: + break; + case OperationResult.NotExists: + //throw new NotFoundException($"{nameof(ListRequest)} failed with result code {data.OperationResult}", HttpStatusCode.NotFound); + throw new RequestException(_fullPath) { StatusCode = HttpStatusCode.NotFound }; + default: + throw new Exception($"{nameof(ListRequest)} failed with result code {data.OperationResult}"); } - protected override RequestResponse DeserializeMessage(NameValueCollection responseHeaders, ResponseBodyStream data) + + var res = new Result { - switch (data.OperationResult) - { - case OperationResult.Ok: - break; - case OperationResult.NotExists: - //throw new NotFoundException($"{nameof(ListRequest)} failed with result code {data.OperationResult}", HttpStatusCode.NotFound); - throw new RequestException(_fullPath) {StatusCode = HttpStatusCode.NotFound}; - default: - throw new Exception($"{nameof(ListRequest)} failed with result code {data.OperationResult}"); - } + OperationResult = data.OperationResult, + Revision = Revision.FromStream(data), + FullPath = _fullPath + }; + if ((Options & Option.TotalSpace) != 0) + res.TotalSpace = data.ReadULong(); + if ((Options & Option.UsedSpace) != 0) + res.UsedSpace = data.ReadULong(); + + res.FingerPrint = data.ReadBytesByLength(); + res.Item = Deserialize(data, _fullPath); + + return new RequestResponse + { + Ok = data.OperationResult == OperationResult.Ok, + Result = res + }; + } + private enum ParseOp + { + Done = 0, + ReadItem = 1, + Pin = 2, + PinUpper = 3, + Unknown15 = 15 + } - var res = new Result - { - OperationResult = data.OperationResult, - Revision = Revision.FromStream(data), - FullPath = _fullPath - }; - if ((Options & Option.TotalSpace) != 0) - res.TotalSpace = data.ReadULong(); - if ((Options & Option.UsedSpace) != 0) - res.UsedSpace = data.ReadULong(); - - res.FingerPrint = data.ReadBytesByLength(); - res.Item = Deserialize(data, _fullPath); - - return new RequestResponse - { - Ok = data.OperationResult == OperationResult.Ok, - Result = res - }; - } + private FsItem Deserialize(ResponseBodyStream data, string fullPath) + { + fullPath = WebDavPath.Clean(fullPath); - private enum ParseOp - { - Done = 0, - ReadItem = 1, - Pin = 2, - PinUpper = 3, - Unknown15 = 15 - } + var fakeRoot = new FsFolder(WebDavPath.Parent(fullPath), null, CloudFolderType.Generic, null, null); + var currentFolder = fakeRoot; + FsFolder lastFolder = null; + int lvl = 0; - private FsItem Deserialize(ResponseBodyStream data, string fullPath) + var parseOp = (ParseOp)data.ReadShort(); + while (parseOp != ParseOp.Done) { - fullPath = WebDavPath.Clean(fullPath); + switch (parseOp) + { + case ParseOp.ReadItem: + break; - var fakeRoot = new FsFolder(WebDavPath.Parent(fullPath), null, CloudFolderType.Generic, null, null); - var currentFolder = fakeRoot; - FsFolder lastFolder = null; - int lvl = 0; + case ParseOp.Pin: + currentFolder = lastFolder ?? throw new Exception("lastFolder = null"); + lvl++; + parseOp = (ParseOp)data.ReadShort(); + continue; - var parseOp = (ParseOp)data.ReadShort(); - while (parseOp != ParseOp.Done) - { - switch (parseOp) + case ParseOp.PinUpper: + if (currentFolder == fakeRoot) { - case ParseOp.ReadItem: - break; - - case ParseOp.Pin: - currentFolder = lastFolder ?? throw new Exception("lastFolder = null"); - lvl++; - parseOp = (ParseOp)data.ReadShort(); - continue; - - case ParseOp.PinUpper: - if (currentFolder == fakeRoot) - { - parseOp = (ParseOp)data.ReadShort(); - continue; - } - - currentFolder = currentFolder.Parent ?? throw new Exception("No parent folder B"); - lvl--; - parseOp = (ParseOp)data.ReadShort(); - if (currentFolder == null) - throw new Exception("No parent folder A"); - continue; - - case ParseOp.Unknown15: - long skip = data.ReadPu32(); - for (long i = 0; i < skip; i++) - { - data.ReadPu32(); - data.ReadPu32(); - } - break; - default: - throw new Exception("Unknown parse operation"); + parseOp = (ParseOp)data.ReadShort(); + continue; } - FsItem item = GetItem(data, currentFolder); - currentFolder.Items.Add(item); - - if (item is FsFolder fsFolder) - { - lastFolder = fsFolder; - fsFolder.IsChildrenLoaded = lvl < Depth; - } + currentFolder = currentFolder.Parent ?? throw new Exception("No parent folder B"); + lvl--; parseOp = (ParseOp)data.ReadShort(); + if (currentFolder == null) + throw new Exception("No parent folder A"); + continue; + + case ParseOp.Unknown15: + long skip = data.ReadPu32(); + for (long i = 0; i < skip; i++) + { + data.ReadPu32(); + data.ReadPu32(); + } + break; + default: + throw new Exception("Unknown parse operation"); + } + FsItem item = GetItem(data, currentFolder); + currentFolder.Items.Add(item); + + if (item is FsFolder fsFolder) + { + lastFolder = fsFolder; + fsFolder.IsChildrenLoaded = lvl < Depth; } - var res = fakeRoot.Items[0]; - return res; + parseOp = (ParseOp)data.ReadShort(); } + var res = fakeRoot.Items[0]; + return res; + } - private FsItem GetItem(ResponseBodyStream data, FsFolder folder) - { - FsItem item; - TreeId treeId; - int head = data.ReadIntSpl(); - if ((head & 4096) != 0) - { - data.ReadNBytes(16); // var nodeId = - } - string name = data.ReadNBytesAsString(data.ReadShort()); + private FsItem GetItem(ResponseBodyStream data, FsFolder folder) + { + FsItem item; + TreeId treeId; - data.ReadULong(); // dunno + int head = data.ReadIntSpl(); + if ((head & 4096) != 0) + { + data.ReadNBytes(16); // var nodeId = + } + string name = data.ReadNBytesAsString(data.ReadShort()); - ulong? GetFolderSize() => (Options & Option.FolderSize) != 0 - ? data.ReadULong() - : null; - void ProcessDelete() - { - if ((Options & Option.Delete) == 0) - return; + data.ReadULong(); // dunno - data.ReadPu32(); // dunno - data.ReadPu32(); // dunno - } + ulong? GetFolderSize() => (Options & Option.FolderSize) != 0 + ? data.ReadULong() + : null; + void ProcessDelete() + { + if ((Options & Option.Delete) == 0) + return; - int opresult = head & 3; - switch (opresult) - { - case 0: // folder? - treeId = data.ReadTreeId(); - data.ReadULong(); // dunno - data.ReadULong(); // dunno - ProcessDelete(); + data.ReadPu32(); // dunno + data.ReadPu32(); // dunno + } - item = new FsFolder(WebDavPath.Combine(folder.FullPath, name), treeId, CloudFolderType.MountPoint, folder, GetFolderSize()); - break; + int opresult = head & 3; + switch (opresult) + { + case 0: // folder? + treeId = data.ReadTreeId(); + data.ReadULong(); // dunno + data.ReadULong(); // dunno + ProcessDelete(); - case 1: - var modifDate = data.ReadDate(); - ulong size = data.ReadULong(); - var sha1 = data.ReadNBytes(20); + item = new FsFolder(WebDavPath.Combine(folder.FullPath, name), treeId, CloudFolderType.MountPoint, folder, GetFolderSize()); + break; - //item = new FsFile(WebDavPath.Combine(folder.FullPath == string.Empty ? _fullPath : folder.FullPath, name), modifDate, sha1, size); - item = new FsFile(WebDavPath.Combine(folder.FullPath, name), modifDate, sha1, size); - break; + case 1: + var modifDate = data.ReadDate(); + ulong size = data.ReadULong(); + var sha1 = data.ReadNBytes(20); - case 2: - data.ReadULong(); // dunno - ProcessDelete(); + //item = new FsFile(WebDavPath.Combine(folder.FullPath == string.Empty ? _fullPath : folder.FullPath, name), modifDate, sha1, size); + item = new FsFile(WebDavPath.Combine(folder.FullPath, name), modifDate, sha1, size); + break; - item = new FsFolder(WebDavPath.Combine(folder.FullPath, name), null, CloudFolderType.Generic, folder, GetFolderSize()); - break; + case 2: + data.ReadULong(); // dunno + ProcessDelete(); - case 3: - data.ReadULong(); // dunno - treeId = data.ReadTreeId(); - ProcessDelete(); + item = new FsFolder(WebDavPath.Combine(folder.FullPath, name), null, CloudFolderType.Generic, folder, GetFolderSize()); + break; - item = new FsFolder(WebDavPath.Combine(folder.FullPath, name), treeId, CloudFolderType.Shared, folder, GetFolderSize()); - break; - default: - throw new Exception("unknown opresult " + opresult); + case 3: + data.ReadULong(); // dunno + treeId = data.ReadTreeId(); + ProcessDelete(); - } + item = new FsFolder(WebDavPath.Combine(folder.FullPath, name), treeId, CloudFolderType.Shared, folder, GetFolderSize()); + break; + default: + throw new Exception("unknown opresult " + opresult); - return item; } + return item; + } + - public class Result : RevisionResponseResult - { - public string FullPath { get; set; } - public ulong TotalSpace { get; set; } - public ulong UsedSpace { get; set; } - public byte[] FingerPrint { get; set; } + public class Result : RevisionResponseResult + { + public string FullPath { get; set; } + public ulong TotalSpace { get; set; } + public ulong UsedSpace { get; set; } + public byte[] FingerPrint { get; set; } - public FsItem Item { get; set; } - } + public FsItem Item { get; set; } } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/MobAddFileRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/MobAddFileRequest.cs index 1ecafc2f..4bce180d 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/MobAddFileRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/MobAddFileRequest.cs @@ -5,104 +5,103 @@ using YaR.Clouds.Base.Requests; using YaR.Clouds.Extensions; -namespace YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests +namespace YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests; + +class MobAddFileRequest : BaseRequestMobile { - class MobAddFileRequest : BaseRequestMobile + private readonly string _fullPath; + private readonly byte[] _hash; + private readonly long _size; + private readonly ConflictResolver _conflictResolver; + private readonly DateTime _dateTime; + + public MobAddFileRequest(HttpCommonSettings settings, IAuth auth, string metaServer, string fullPath, byte[] hash, long size, DateTime? dateTime, ConflictResolver? conflict) + : base(settings, auth, metaServer) { - private readonly string _fullPath; - private readonly byte[] _hash; - private readonly long _size; - private readonly ConflictResolver _conflictResolver; - private readonly DateTime _dateTime; - - public MobAddFileRequest(HttpCommonSettings settings, IAuth auth, string metaServer, string fullPath, byte[] hash, long size, DateTime? dateTime, ConflictResolver? conflict) - : base(settings, auth, metaServer) - { - _fullPath = fullPath; - _hash = hash ?? new byte[20]; // zero length file - _size = size; - _conflictResolver = conflict ?? ConflictResolver.Rewrite; - _dateTime = (dateTime ?? DateTime.Now).ToUniversalTime(); - } - - public MobAddFileRequest(HttpCommonSettings settings, IAuth auth, string metaServer, string fullPath, string hash, long size, DateTime? dateTime, ConflictResolver? conflict) - : this(settings, auth, metaServer, fullPath, hash?.HexStringToByteArray(), size, dateTime, conflict) - { - } + _fullPath = fullPath; + _hash = hash ?? new byte[20]; // zero length file + _size = size; + _conflictResolver = conflict ?? ConflictResolver.Rewrite; + _dateTime = (dateTime ?? DateTime.Now).ToUniversalTime(); + } - protected override byte[] CreateHttpContent() - { - using var stream = new RequestBodyStream(); + public MobAddFileRequest(HttpCommonSettings settings, IAuth auth, string metaServer, string fullPath, string hash, long size, DateTime? dateTime, ConflictResolver? conflict) + : this(settings, auth, metaServer, fullPath, hash?.HexStringToByteArray(), size, dateTime, conflict) + { + } - stream.WritePu16((byte)Operation.AddFile); - stream.WritePu16(Revision); - stream.WriteString(_fullPath); - stream.WritePu64(_size); + protected override byte[] CreateHttpContent() + { + using var stream = new RequestBodyStream(); - stream.WritePu64(_dateTime.ToUnix()); - stream.WritePu32(00); + stream.WritePu16((byte)Operation.AddFile); + stream.WritePu16(Revision); + stream.WriteString(_fullPath); + stream.WritePu64(_size); - stream.Write(_hash); + stream.WritePu64(_dateTime.ToUnix()); + stream.WritePu32(00); - long mask = ConflictResolver.Rename == _conflictResolver // 1 = overwrite, 55 = don't add if not changed, add with rename if changed - ? 55 - : 1; - stream.WritePu32(mask); + stream.Write(_hash); - if ((mask & 32) != 0) - { - stream.Write(_hash); - stream.WritePu64(_size); - } + long mask = ConflictResolver.Rename == _conflictResolver // 1 = overwrite, 55 = don't add if not changed, add with rename if changed + ? 55 + : 1; + stream.WritePu32(mask); - var body = stream.GetBytes(); - return body; + if ((mask & 32) != 0) + { + stream.Write(_hash); + stream.WritePu64(_size); } - private static readonly OpResult[] SuccessCodes = { OpResult.Ok, OpResult.NotModified, OpResult.Dunno04, OpResult.Dunno09 }; + var body = stream.GetBytes(); + return body; + } - protected override RequestResponse DeserializeMessage(NameValueCollection responseHeaders, ResponseBodyStream data) - { - var opres = (OpResult)(int)data.OperationResult; + private static readonly OpResult[] SuccessCodes = { OpResult.Ok, OpResult.NotModified, OpResult.Dunno04, OpResult.Dunno09 }; + + protected override RequestResponse DeserializeMessage(NameValueCollection responseHeaders, ResponseBodyStream data) + { + var opres = (OpResult)(int)data.OperationResult; - //if (!SuccessCodes.Contains(opres)) - // throw new Exception($"{nameof(MobAddFileRequest)} failed with operation result code {opres} ({(int)opres})"); + //if (!SuccessCodes.Contains(opres)) + // throw new Exception($"{nameof(MobAddFileRequest)} failed with operation result code {opres} ({(int)opres})"); - bool isSuccess = SuccessCodes.Contains(opres); + bool isSuccess = SuccessCodes.Contains(opres); - var res = new RequestResponse + var res = new RequestResponse + { + Ok = true, + Result = new Result { - Ok = true, - Result = new Result - { - IsSuccess = isSuccess, - OperationResult = data.OperationResult, - Path = _fullPath - } - }; - - return res; - } + IsSuccess = isSuccess, + OperationResult = data.OperationResult, + Path = _fullPath + } + }; - private const int Revision = 0; + return res; + } - private enum OpResult - { - Ok = 0, - Error01 = 1, - Dunno04 = 4, - WrongPath = 5, - NoFreeSpace = 7, - Dunno09 = 9, - NotModified = 12, - FailedA = 253, - FailedB = 254 - } + private const int Revision = 0; - public class Result : BaseResponseResult - { - public bool IsSuccess { get; set; } - public string Path { get; set; } - } + private enum OpResult + { + Ok = 0, + Error01 = 1, + Dunno04 = 4, + WrongPath = 5, + NoFreeSpace = 7, + Dunno09 = 9, + NotModified = 12, + FailedA = 253, + FailedB = 254 + } + + public class Result : BaseResponseResult + { + public bool IsSuccess { get; set; } + public string Path { get; set; } } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/MobMetaServerRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/MobMetaServerRequest.cs index 1180e049..70335a60 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/MobMetaServerRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/MobMetaServerRequest.cs @@ -1,13 +1,12 @@ using YaR.Clouds.Base.Requests; -namespace YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests +namespace YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests; + +internal class MobMetaServerRequest : ServerRequest { - internal class MobMetaServerRequest : ServerRequest + public MobMetaServerRequest(HttpCommonSettings settings) : base(settings) { - public MobMetaServerRequest(HttpCommonSettings settings) : base(settings) - { - } - - protected override string RelationalUri => "https://dispatcher.cloud.mail.ru/m"; } + + protected override string RelationalUri => "https://dispatcher.cloud.mail.ru/m"; } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/MoveRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/MoveRequest.cs index 3ba8d697..0d505512 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/MoveRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/MoveRequest.cs @@ -3,71 +3,70 @@ using YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests.Types; using YaR.Clouds.Base.Requests; -namespace YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests +namespace YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests; + +class MoveRequest : BaseRequestMobile { - class MoveRequest : BaseRequestMobile + private readonly string _fromPath; + private readonly string _toPath; + + public MoveRequest(HttpCommonSettings settings, IAuth auth, string metaServer, string fromPath, string toPath) + : base(settings, auth, metaServer) { - private readonly string _fromPath; - private readonly string _toPath; + _fromPath = fromPath; + _toPath = toPath; + } - public MoveRequest(HttpCommonSettings settings, IAuth auth, string metaServer, string fromPath, string toPath) - : base(settings, auth, metaServer) - { - _fromPath = fromPath; - _toPath = toPath; - } + protected override byte[] CreateHttpContent() + { + using var stream = new RequestBodyStream(); - protected override byte[] CreateHttpContent() - { - using var stream = new RequestBodyStream(); + stream.WritePu16((byte)Operation.Rename); + stream.WritePu32(00); // old revision + stream.WriteString(_fromPath); + stream.WritePu32(00); // new revision + stream.WriteString(_toPath); + stream.WritePu32(00); //dunno - stream.WritePu16((byte)Operation.Rename); - stream.WritePu32(00); // old revision - stream.WriteString(_fromPath); - stream.WritePu32(00); // new revision - stream.WriteString(_toPath); - stream.WritePu32(00); //dunno + var body = stream.GetBytes(); + return body; + } - var body = stream.GetBytes(); - return body; - } + protected override RequestResponse DeserializeMessage(NameValueCollection responseHeaders, ResponseBodyStream data) + { + var opres = (OpResult)(int)data.OperationResult; - protected override RequestResponse DeserializeMessage(NameValueCollection responseHeaders, ResponseBodyStream data) + if (opres == OpResult.Ok) { - var opres = (OpResult) (int) data.OperationResult; - - if (opres == OpResult.Ok) + return new RequestResponse { - return new RequestResponse + Ok = true, + Result = new Result { - Ok = true, - Result = new Result - { - OperationResult = data.OperationResult, - OneRevision = Revision.FromStream(data), - TwoRevision = Revision.FromStream(data) - } - }; - } - - throw new Exception($"{nameof(MoveRequest)} failed with result code {opres}"); + OperationResult = data.OperationResult, + OneRevision = Revision.FromStream(data), + TwoRevision = Revision.FromStream(data) + } + }; } - public class Result : BaseResponseResult - { - public Revision OneRevision; - public Revision TwoRevision; - } + throw new Exception($"{nameof(MoveRequest)} failed with result code {opres}"); + } - private enum OpResult - { - Ok = 0, - SourceNotExists = 1, - Failed002 = 2, - AlreadyExists = 4, - Failed005 = 5, - Failed254 = 254 - } + public class Result : BaseResponseResult + { + public Revision OneRevision; + public Revision TwoRevision; + } + private enum OpResult + { + Ok = 0, + SourceNotExists = 1, + Failed002 = 2, + AlreadyExists = 4, + Failed005 = 5, + Failed254 = 254 } + } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/OAuthRefreshRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/OAuthRefreshRequest.cs index cddbc35f..77b02014 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/OAuthRefreshRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/OAuthRefreshRequest.cs @@ -3,50 +3,49 @@ using Newtonsoft.Json; using YaR.Clouds.Base.Requests; -namespace YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests +namespace YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests; + +class OAuthRefreshRequest : BaseRequestJson { - class OAuthRefreshRequest : BaseRequestJson + private readonly string _refreshToken; + + public OAuthRefreshRequest(HttpCommonSettings settings, string refreshToken) : base(settings, null) + { + _refreshToken = refreshToken; + } + + protected override string RelationalUri => "https://o2.mail.ru/token"; + + protected override byte[] CreateHttpContent() + { + var data = $"client_id={_settings.ClientId}&grant_type=refresh_token&refresh_token={_refreshToken}"; + return Encoding.UTF8.GetBytes(data); + } + + protected override HttpWebRequest CreateRequest(string baseDomain = null) + { + var request = base.CreateRequest(baseDomain); + request.Host = request.RequestUri.Host; + request.UserAgent = _settings.UserAgent; + request.Accept = "*/*"; + request.ServicePoint.Expect100Continue = false; + + return request; + } + + + public class Result { - private readonly string _refreshToken; - - public OAuthRefreshRequest(HttpCommonSettings settings, string refreshToken) : base(settings, null) - { - _refreshToken = refreshToken; - } - - protected override string RelationalUri => "https://o2.mail.ru/token"; - - protected override byte[] CreateHttpContent() - { - var data = $"client_id={_settings.ClientId}&grant_type=refresh_token&refresh_token={_refreshToken}"; - return Encoding.UTF8.GetBytes(data); - } - - protected override HttpWebRequest CreateRequest(string baseDomain = null) - { - var request = base.CreateRequest(baseDomain); - request.Host = request.RequestUri.Host; - request.UserAgent = _settings.UserAgent; - request.Accept = "*/*"; - request.ServicePoint.Expect100Continue = false; - - return request; - } - - - public class Result - { - [JsonProperty("access_token")] - public string AccessToken { get; set; } - [JsonProperty("expires_in")] - public int ExpiresIn { get; set; } - - [JsonProperty("error")] - public string Error { get; set; } - [JsonProperty("error_code")] - public int ErrorCode { get; set; } - [JsonProperty("error_description")] - public string ErrorDescription { get; set; } - } + [JsonProperty("access_token")] + public string AccessToken { get; set; } + [JsonProperty("expires_in")] + public int ExpiresIn { get; set; } + + [JsonProperty("error")] + public string Error { get; set; } + [JsonProperty("error_code")] + public int ErrorCode { get; set; } + [JsonProperty("error_description")] + public string ErrorDescription { get; set; } } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/OAuthSecondStepRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/OAuthSecondStepRequest.cs index 2b35646c..38506e10 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/OAuthSecondStepRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/OAuthSecondStepRequest.cs @@ -2,30 +2,29 @@ using System.Text; using YaR.Clouds.Base.Requests; -namespace YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests +namespace YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests; + +class OAuthSecondStepRequest : BaseRequestJson { - class OAuthSecondStepRequest : BaseRequestJson - { - private readonly string _login; - private readonly string _tsaToken; - private readonly string _authCode; + private readonly string _login; + private readonly string _tsaToken; + private readonly string _authCode; - public OAuthSecondStepRequest(HttpCommonSettings settings, string login, string tsaToken, string authCode) - : base(settings, null) - { - _login = login; - _tsaToken = tsaToken; - _authCode = authCode; - } + public OAuthSecondStepRequest(HttpCommonSettings settings, string login, string tsaToken, string authCode) + : base(settings, null) + { + _login = login; + _tsaToken = tsaToken; + _authCode = authCode; + } - protected override string RelationalUri => "https://o2.mail.ru/token"; + protected override string RelationalUri => "https://o2.mail.ru/token"; - protected override byte[] CreateHttpContent() - { + protected override byte[] CreateHttpContent() + { #pragma warning disable SYSLIB0013 // Type or member is obsolete - var data = $"client_id={_settings.ClientId}&grant_type=password&username={Uri.EscapeUriString(_login)}&tsa_token={_tsaToken}&auth_code={_authCode}"; + var data = $"client_id={_settings.ClientId}&grant_type=password&username={Uri.EscapeUriString(_login)}&tsa_token={_tsaToken}&auth_code={_authCode}"; #pragma warning restore SYSLIB0013 // Type or member is obsolete - return Encoding.UTF8.GetBytes(data); - } + return Encoding.UTF8.GetBytes(data); } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/ServerRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/ServerRequest.cs index d1952d4d..db53cd2d 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/ServerRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/ServerRequest.cs @@ -3,28 +3,27 @@ using YaR.Clouds.Base.Requests; using YaR.Clouds.Base.Requests.Types; -namespace YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests +namespace YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests; + +internal abstract class ServerRequest : BaseRequestString { - internal abstract class ServerRequest : BaseRequestString + protected ServerRequest(HttpCommonSettings settings) : base(settings, null) { - protected ServerRequest(HttpCommonSettings settings) : base(settings, null) - { - } + } - protected override RequestResponse DeserializeMessage(NameValueCollection responseHeaders, string data) + protected override RequestResponse DeserializeMessage(NameValueCollection responseHeaders, string data) + { + var datas = data.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); + var msg = new RequestResponse { - var datas = data.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries); - var msg = new RequestResponse + Ok = true, + Result = new ServerRequestResult { - Ok = true, - Result = new ServerRequestResult - { - Url = datas[0], - Ip = datas[1], - Unknown = int.Parse(datas[2]) - } - }; - return msg; - } + Url = datas[0], + Ip = datas[1], + Unknown = int.Parse(datas[2]) + } + }; + return msg; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/SharedFoldersListRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/SharedFoldersListRequest.cs index b13f6f4b..cf864424 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/SharedFoldersListRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/SharedFoldersListRequest.cs @@ -4,72 +4,71 @@ using YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests.Types; using YaR.Clouds.Base.Requests; -namespace YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests +namespace YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests; + +internal class SharedFoldersListRequest : BaseRequestMobile { - internal class SharedFoldersListRequest : BaseRequestMobile + public SharedFoldersListRequest(HttpCommonSettings settings, IAuth auth, string metaServer) : base(settings, auth, metaServer) { - public SharedFoldersListRequest(HttpCommonSettings settings, IAuth auth, string metaServer) : base(settings, auth, metaServer) - { - } + } - protected override byte[] CreateHttpContent() - { - using var stream = new RequestBodyStream(); + protected override byte[] CreateHttpContent() + { + using var stream = new RequestBodyStream(); - stream.WritePu16((byte)Operation.SharedFoldersList); - //stream.WriteWithLength(new byte[0]); - var body = stream.GetBytes(); - return body; - } + stream.WritePu16((byte)Operation.SharedFoldersList); + //stream.WriteWithLength(new byte[0]); + var body = stream.GetBytes(); + return body; + } - protected override RequestResponse DeserializeMessage(NameValueCollection responseHeaders, ResponseBodyStream data) + protected override RequestResponse DeserializeMessage(NameValueCollection responseHeaders, ResponseBodyStream data) + { + switch (data.OperationResult) { - switch (data.OperationResult) - { - case OperationResult.Ok: - break; - default: - throw new Exception($"{nameof(SharedFoldersListRequest)} failed with result code {data.OperationResult}"); - } + case OperationResult.Ok: + break; + default: + throw new Exception($"{nameof(SharedFoldersListRequest)} failed with result code {data.OperationResult}"); + } - var res = new Result - { - OperationResult = data.OperationResult, - Container = new Dictionary() - }; + var res = new Result + { + OperationResult = data.OperationResult, + Container = new Dictionary() + }; - //var z = data.ReadAllBytes(); - // - var opres = data.OperationResult; //data.ReadShort(); - switch (opres) + //var z = data.ReadAllBytes(); + // + var opres = data.OperationResult; //data.ReadShort(); + switch (opres) + { + case 0: + long cnt = data.ReadPu32(); + for (long j = 0; j < cnt; j++) { - case 0: - long cnt = data.ReadPu32(); - for (long j = 0; j < cnt; j++) - { - var treeId = data.ReadTreeId(); - string fullPath = data.ReadString(); - res.Container[fullPath] = new FsFolder(fullPath, treeId, CloudFolderType.Shared, null, 0); - data.ReadULong(); - } - break; - default: - throw new Exception($"{nameof(SharedFoldersListRequest)}: Unknown parse operation {opres}"); + var treeId = data.ReadTreeId(); + string fullPath = data.ReadString(); + res.Container[fullPath] = new FsFolder(fullPath, treeId, CloudFolderType.Shared, null, 0); + data.ReadULong(); } - - return new RequestResponse - { - Ok = data.OperationResult == OperationResult.Ok, - Result = res - }; + break; + default: + throw new Exception($"{nameof(SharedFoldersListRequest)}: Unknown parse operation {opres}"); } - internal class Result + return new RequestResponse { - public OperationResult OperationResult; - public Dictionary Container; - } + Ok = data.OperationResult == OperationResult.Ok, + Result = res + }; + } + + internal class Result + { + public OperationResult OperationResult; + public Dictionary Container; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/WeblinkGetServerRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/WeblinkGetServerRequest.cs index af2335c5..97632f0d 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/WeblinkGetServerRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/Mobile/Requests/WeblinkGetServerRequest.cs @@ -1,24 +1,23 @@ using YaR.Clouds.Base.Requests; using YaR.Clouds.Base.Requests.Types; -namespace YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests -{ - //internal class WeblinkGetServerRequest : ServerRequest - //{ - // public WeblinkGetServerRequest(HttpCommonSettings settings) : base(settings) - // { - // } +namespace YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests; - // protected override string RelationalUri => "https://dispatcher.cloud.mail.ru/G"; - //} +//internal class WeblinkGetServerRequest : ServerRequest +//{ +// public WeblinkGetServerRequest(HttpCommonSettings settings) : base(settings) +// { +// } - class WeblinkGetServerRequest : BaseRequestJson - { - public WeblinkGetServerRequest(HttpCommonSettings settings) - : base(settings, null) - { - } +// protected override string RelationalUri => "https://dispatcher.cloud.mail.ru/G"; +//} - protected override string RelationalUri => $"{_settings.BaseDomain}/api/v2/dispatcher?api=2&email=anonym&x-email=anonym"; +class WeblinkGetServerRequest : BaseRequestJson +{ + public WeblinkGetServerRequest(HttpCommonSettings settings) + : base(settings, null) + { } + + protected override string RelationalUri => $"{_settings.BaseDomain}/api/v2/dispatcher?api=2&email=anonym&x-email=anonym"; } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/OAuth.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/OAuth.cs index 7a6b93fe..073655f3 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/OAuth.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/OAuth.cs @@ -8,90 +8,89 @@ using YaR.Clouds.Common; using static YaR.Clouds.Cloud; -namespace YaR.Clouds.Base.Repos.MailRuCloud +namespace YaR.Clouds.Base.Repos.MailRuCloud; + +internal class OAuth : IAuth { - internal class OAuth : IAuth + private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(OAuth)); + + private readonly SemaphoreSlim _connectionLimiter; + private readonly HttpCommonSettings _settings; + private readonly IBasicCredentials _creds; + + private readonly AuthCodeRequiredDelegate _onAuthCodeRequired; + + public OAuth(SemaphoreSlim connectionLimiter, + HttpCommonSettings settings, IBasicCredentials credentials, AuthCodeRequiredDelegate onAuthCodeRequired) { - private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(OAuth)); - - private readonly SemaphoreSlim _connectionLimiter; - private readonly HttpCommonSettings _settings; - private readonly IBasicCredentials _creds; - - private readonly AuthCodeRequiredDelegate _onAuthCodeRequired; - - public OAuth(SemaphoreSlim connectionLimiter, - HttpCommonSettings settings, IBasicCredentials credentials, AuthCodeRequiredDelegate onAuthCodeRequired) - { - _settings = settings; - _connectionLimiter = connectionLimiter; - _creds = credentials; - _onAuthCodeRequired = onAuthCodeRequired; - Cookies = new CookieContainer(); - - _authToken = new Cached(old => - { - Logger.Debug(old is null - ? "OAuth: authorizing." - : "OAuth: AuthToken expired, refreshing."); - - var token = string.IsNullOrEmpty(old?.RefreshToken) - ? Auth().Result - : Refresh(old.RefreshToken).Result; - - return token; - }, - value => value?.ExpiresIn.Add(-TimeSpan.FromMinutes(5)) ?? TimeSpan.MaxValue); - //value => TimeSpan.FromSeconds(20)); - } - - public bool IsAnonymous => _creds.IsAnonymous; - public string Login => _creds.Login; - public string Password => _creds.Password; - public string AccessToken => _authToken.Value?.Token; - public string DownloadToken => _authToken.Value?.Token; - public CookieContainer Cookies { get; } - - public void ExpireDownloadToken() - { - } - - /// - /// Token for authorization in mobile version - /// - private readonly Cached _authToken; - - private async Task Auth() - { - if (_creds.IsAnonymous) - return null; - - var req = await new OAuthRequest(_settings, _creds).MakeRequestAsync(_connectionLimiter); - var res = req.ToAuthTokenResult(); - - if (!res.IsSecondStepRequired) - return res; - - if (null == _onAuthCodeRequired) - throw new Exception("No 2Factor plugin found."); - - string code = _onAuthCodeRequired.Invoke(_creds.Login, true); - if (string.IsNullOrWhiteSpace(code)) - throw new Exception("Empty 2Factor code"); - - var ssreq = await new OAuthSecondStepRequest(_settings, _creds.Login, res.TsaToken, code) - .MakeRequestAsync(_connectionLimiter); - - res = ssreq.ToAuthTokenResult(); + _settings = settings; + _connectionLimiter = connectionLimiter; + _creds = credentials; + _onAuthCodeRequired = onAuthCodeRequired; + Cookies = new CookieContainer(); + + _authToken = new Cached(old => + { + Logger.Debug(old is null + ? "OAuth: authorizing." + : "OAuth: AuthToken expired, refreshing."); + + var token = string.IsNullOrEmpty(old?.RefreshToken) + ? Auth().Result + : Refresh(old.RefreshToken).Result; + + return token; + }, + value => value?.ExpiresIn.Add(-TimeSpan.FromMinutes(5)) ?? TimeSpan.MaxValue); + //value => TimeSpan.FromSeconds(20)); + } - return res; - } + public bool IsAnonymous => _creds.IsAnonymous; + public string Login => _creds.Login; + public string Password => _creds.Password; + public string AccessToken => _authToken.Value?.Token; + public string DownloadToken => _authToken.Value?.Token; + public CookieContainer Cookies { get; } - private async Task Refresh(string refreshToken) - { - var req = await new OAuthRefreshRequest(_settings, refreshToken).MakeRequestAsync(_connectionLimiter); - var res = req.ToAuthTokenResult(refreshToken); + public void ExpireDownloadToken() + { + } + + /// + /// Token for authorization in mobile version + /// + private readonly Cached _authToken; + + private async Task Auth() + { + if (_creds.IsAnonymous) + return null; + + var req = await new OAuthRequest(_settings, _creds).MakeRequestAsync(_connectionLimiter); + var res = req.ToAuthTokenResult(); + + if (!res.IsSecondStepRequired) return res; - } + + if (null == _onAuthCodeRequired) + throw new Exception("No 2Factor plugin found."); + + string code = _onAuthCodeRequired.Invoke(_creds.Login, true); + if (string.IsNullOrWhiteSpace(code)) + throw new Exception("Empty 2Factor code"); + + var ssreq = await new OAuthSecondStepRequest(_settings, _creds.Login, res.TsaToken, code) + .MakeRequestAsync(_connectionLimiter); + + res = ssreq.ToAuthTokenResult(); + + return res; + } + + private async Task Refresh(string refreshToken) + { + var req = await new OAuthRefreshRequest(_settings, refreshToken).MakeRequestAsync(_connectionLimiter); + var res = req.ToAuthTokenResult(refreshToken); + return res; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/ShardManager.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/ShardManager.cs index 46b7b05e..725701d6 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/ShardManager.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/ShardManager.cs @@ -5,82 +5,81 @@ using YaR.Clouds.Base.Requests.Types; using YaR.Clouds.Common; -namespace YaR.Clouds.Base.Repos.MailRuCloud +namespace YaR.Clouds.Base.Repos.MailRuCloud; + +class ShardManager { - class ShardManager + //TODO: refact required + + private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(ShardManager)); + + public ShardManager(SemaphoreSlim connectionLimiter, IRequestRepo repo) { - //TODO: refact required + var httpsettings = repo.HttpSettings; - private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(ShardManager)); + _metaServer = new Cached(_ => + { + Logger.Debug("Requesting new meta server"); + var server = new MobMetaServerRequest(httpsettings).MakeRequestAsync(connectionLimiter).Result; + return server; + }, + _ => TimeSpan.FromSeconds(MetaServerExpiresSec)); - public ShardManager(SemaphoreSlim connectionLimiter, IRequestRepo repo) - { - var httpsettings = repo.HttpSettings; + BannedShards = new Cached>(_ => new List(), + _ => TimeSpan.FromMinutes(2)); - _metaServer = new Cached(_ => + //CachedShards = new Cached>(old => new ShardInfoRequest(httpsettings, auth).MakeRequestAsync().Result.ToShardInfo(), + // value => TimeSpan.FromSeconds(ShardsExpiresInSec)); + + CachedShards = new Cached>(_ => repo.GetShardInfo1(), + _ => TimeSpan.FromSeconds(ShardsExpiresInSec)); + + DownloadServersPending = new Pending>(8, + () => new Cached(_ => { - Logger.Debug("Requesting new meta server"); - var server = new MobMetaServerRequest(httpsettings).MakeRequestAsync(connectionLimiter).Result; + var server = new GetServerRequest(httpsettings).MakeRequestAsync(connectionLimiter).Result; + Logger.Debug($"Download server changed to {server.Url}"); return server; }, - _ => TimeSpan.FromSeconds(MetaServerExpiresSec)); - - BannedShards = new Cached>(_ => new List(), - _ => TimeSpan.FromMinutes(2)); + _ => TimeSpan.FromSeconds(DownloadServerExpiresSec) + )); - //CachedShards = new Cached>(old => new ShardInfoRequest(httpsettings, auth).MakeRequestAsync().Result.ToShardInfo(), - // value => TimeSpan.FromSeconds(ShardsExpiresInSec)); + UploadServer = new Cached(_ => + { + var server = new GetUploadServerRequest(httpsettings).MakeRequestAsync(connectionLimiter).Result; + Logger.Debug($"Upload server changed to {server.Url}"); + return new ShardInfo { Count = 0, Type = ShardType.Upload, Url = server.Url }; + }, + _ => TimeSpan.FromSeconds(ShardsExpiresInSec)); - CachedShards = new Cached>(_ => repo.GetShardInfo1(), - _ => TimeSpan.FromSeconds(ShardsExpiresInSec)); - DownloadServersPending = new Pending>(8, - () => new Cached(_ => - { - var server = new GetServerRequest(httpsettings).MakeRequestAsync(connectionLimiter).Result; - Logger.Debug($"Download server changed to {server.Url}"); - return server; - }, - _ => TimeSpan.FromSeconds(DownloadServerExpiresSec) - )); - - UploadServer = new Cached(_ => + WebLinkDownloadServersPending = new Pending>(8, + () => new Cached(_ => { - var server = new GetUploadServerRequest(httpsettings).MakeRequestAsync(connectionLimiter).Result; - Logger.Debug($"Upload server changed to {server.Url}"); - return new ShardInfo { Count = 0, Type = ShardType.Upload, Url = server.Url }; + var data = new WeblinkGetServerRequest(httpsettings).MakeRequestAsync(connectionLimiter).Result; + var serverUrl = data.Body.WeblinkGet[0].Url; + Logger.Debug($"weblink Download server changed to {serverUrl}"); + var res = new ServerRequestResult { Url = serverUrl }; + return res; }, - _ => TimeSpan.FromSeconds(ShardsExpiresInSec)); - - - WebLinkDownloadServersPending = new Pending>(8, - () => new Cached(_ => - { - var data = new WeblinkGetServerRequest(httpsettings).MakeRequestAsync(connectionLimiter).Result; - var serverUrl = data.Body.WeblinkGet[0].Url; - Logger.Debug($"weblink Download server changed to {serverUrl}"); - var res = new ServerRequestResult { Url = serverUrl }; - return res; - }, - _ => TimeSpan.FromSeconds(DownloadServerExpiresSec) - )); - } + _ => TimeSpan.FromSeconds(DownloadServerExpiresSec) + )); + } - public Pending> DownloadServersPending { get; } - public Pending> WebLinkDownloadServersPending { get; } + public Pending> DownloadServersPending { get; } + public Pending> WebLinkDownloadServersPending { get; } - public ShardInfo MetaServer => new() {Url = _metaServer.Value.Url, Count = _metaServer.Value.Unknown}; - private readonly Cached _metaServer; + public ShardInfo MetaServer => new() { Url = _metaServer.Value.Url, Count = _metaServer.Value.Unknown }; + private readonly Cached _metaServer; - public Cached> CachedShards { get; } - public Cached UploadServer { get; } + public Cached> CachedShards { get; } + public Cached UploadServer { get; } - public Cached> BannedShards { get; } + public Cached> BannedShards { get; } - private const int ShardsExpiresInSec = 30 * 60; - private const int MetaServerExpiresSec = 20 * 60; - private const int DownloadServerExpiresSec = 3 * 60; - } + private const int ShardsExpiresInSec = 30 * 60; + private const int MetaServerExpiresSec = 20 * 60; + private const int DownloadServerExpiresSec = 3 * 60; } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebBin/Requests/DownloadRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebBin/Requests/DownloadRequest.cs index 175c2bde..b0df7cac 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebBin/Requests/DownloadRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebBin/Requests/DownloadRequest.cs @@ -4,73 +4,72 @@ using System.Collections.Generic; using YaR.Clouds.Base.Requests; -namespace YaR.Clouds.Base.Repos.MailRuCloud.WebBin.Requests +namespace YaR.Clouds.Base.Repos.MailRuCloud.WebBin.Requests; + +class DownloadRequest { - class DownloadRequest + public DownloadRequest(HttpCommonSettings settings, IAuth auth, + File file, long inStart, long inEnd, string downServerUrl, IEnumerable publicBaseUrls) { - public DownloadRequest(HttpCommonSettings settings, IAuth auth, - File file, long inStart, long inEnd, string downServerUrl, IEnumerable publicBaseUrls) - { - Request = CreateRequest(settings, auth, file, inStart, inEnd, downServerUrl, publicBaseUrls); - } + Request = CreateRequest(settings, auth, file, inStart, inEnd, downServerUrl, publicBaseUrls); + } - public HttpWebRequest Request { get; } + public HttpWebRequest Request { get; } - private static HttpWebRequest CreateRequest(HttpCommonSettings settings, - IAuth auth, File file, long instart, long inend, string downServerUrl, IEnumerable publicBaseUrls) - //(IAuth authenticator, IWebProxy proxy, string url, long inStart, long inEnd, string userAgent) - { - bool isLinked = !file.PublicLinks.IsEmpty; + private static HttpWebRequest CreateRequest(HttpCommonSettings settings, + IAuth auth, File file, long instart, long inend, string downServerUrl, IEnumerable publicBaseUrls) + //(IAuth authenticator, IWebProxy proxy, string url, long inStart, long inEnd, string userAgent) + { + bool isLinked = !file.PublicLinks.IsEmpty; - string url; + string url; - if (isLinked) - { - var urii = file.PublicLinks.Values.FirstOrDefault()?.Uri; - var uriistr = urii?.OriginalString; - var baseura = uriistr == null - ? null - : publicBaseUrls.FirstOrDefault(pbu => uriistr.StartsWith(pbu, StringComparison.InvariantCulture)); - if (string.IsNullOrEmpty(baseura)) - throw new ArgumentException("URL does not starts with base URL"); + if (isLinked) + { + var urii = file.PublicLinks.Values.FirstOrDefault()?.Uri; + var uriistr = urii?.OriginalString; + var baseura = uriistr == null + ? null + : publicBaseUrls.FirstOrDefault(pbu => uriistr.StartsWith(pbu, StringComparison.InvariantCulture)); + if (string.IsNullOrEmpty(baseura)) + throw new ArgumentException("URL does not starts with base URL"); - url = $"{downServerUrl}{WebDavPath.EscapeDataString(uriistr.Remove(0, baseura.Length))}"; - } - else - { - url = $"{downServerUrl}{Uri.EscapeDataString(file.FullPath.TrimStart('/'))}"; - url += $"?client_id={settings.ClientId}&token={auth.AccessToken}"; - } + url = $"{downServerUrl}{WebDavPath.EscapeDataString(uriistr.Remove(0, baseura.Length))}"; + } + else + { + url = $"{downServerUrl}{Uri.EscapeDataString(file.FullPath.TrimStart('/'))}"; + url += $"?client_id={settings.ClientId}&token={auth.AccessToken}"; + } - var uri = new Uri(url); + var uri = new Uri(url); #pragma warning disable SYSLIB0014 // Type or member is obsolete - HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri.OriginalString); + HttpWebRequest request = (HttpWebRequest)WebRequest.Create(uri.OriginalString); #pragma warning restore SYSLIB0014 // Type or member is obsolete - request.AllowAutoRedirect = true; + request.AllowAutoRedirect = true; - request.AddRange(instart, inend); - request.Proxy = settings.Proxy; - //request.CookieContainer = authenticator.Cookies; - request.Method = "GET"; - //request.Accept = "*/*"; - //request.UserAgent = settings.UserAgent; - //request.Host = uri.Host; - request.AllowWriteStreamBuffering = false; + request.AddRange(instart, inend); + request.Proxy = settings.Proxy; + //request.CookieContainer = authenticator.Cookies; + request.Method = "GET"; + //request.Accept = "*/*"; + //request.UserAgent = settings.UserAgent; + //request.Host = uri.Host; + request.AllowWriteStreamBuffering = false; - if (isLinked) - request.Headers.Add("Accept-Ranges", "bytes"); + if (isLinked) + request.Headers.Add("Accept-Ranges", "bytes"); - request.Timeout = 15 * 1000; - request.ReadWriteTimeout = 15 * 1000; + request.Timeout = 15 * 1000; + request.ReadWriteTimeout = 15 * 1000; - return request; - } + return request; + } - public static implicit operator HttpWebRequest(DownloadRequest v) - { - return v.Request; - } + public static implicit operator HttpWebRequest(DownloadRequest v) + { + return v.Request; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebBin/WebBinRequestRepo.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebBin/WebBinRequestRepo.cs index f207237f..81825031 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebBin/WebBinRequestRepo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebBin/WebBinRequestRepo.cs @@ -22,426 +22,425 @@ using MoveRequest = YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests.MoveRequest; using static YaR.Clouds.Cloud; -namespace YaR.Clouds.Base.Repos.MailRuCloud.WebBin +namespace YaR.Clouds.Base.Repos.MailRuCloud.WebBin; + +/// +/// Combination of WebM1 and Mobile protocols +/// +class WebBinRequestRepo : MailRuBaseRepo, IRequestRepo { - /// - /// Combination of WebM1 and Mobile protocols - /// - class WebBinRequestRepo : MailRuBaseRepo, IRequestRepo - { - private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(WebBinRequestRepo)); + private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(WebBinRequestRepo)); - private readonly SemaphoreSlim _connectionLimiter; - private readonly AuthCodeRequiredDelegate _onAuthCodeRequired; + private readonly SemaphoreSlim _connectionLimiter; + private readonly AuthCodeRequiredDelegate _onAuthCodeRequired; - protected ShardManager ShardManager { get; private set; } + protected ShardManager ShardManager { get; private set; } - protected IRequestRepo AnonymousRepo => _anonymousRepo ??= - new AnonymousRepo(HttpSettings.CloudSettings, Credentials, _onAuthCodeRequired); - private IRequestRepo _anonymousRepo; + protected IRequestRepo AnonymousRepo => _anonymousRepo ??= + new AnonymousRepo(HttpSettings.CloudSettings, Credentials, _onAuthCodeRequired); + private IRequestRepo _anonymousRepo; - public sealed override HttpCommonSettings HttpSettings { get; } = new() - { - //ClientId = "cloud-android" - ClientId = "cloud-win", - BaseDomain = "https://cloud.mail.ru" - }; + public sealed override HttpCommonSettings HttpSettings { get; } = new() + { + //ClientId = "cloud-android" + ClientId = "cloud-win", + BaseDomain = "https://cloud.mail.ru" + }; - public WebBinRequestRepo(CloudSettings settings, IBasicCredentials credentials, AuthCodeRequiredDelegate onAuthCodeRequired) - : base(credentials) - { - ServicePointManager.DefaultConnectionLimit = int.MaxValue; + public WebBinRequestRepo(CloudSettings settings, IBasicCredentials credentials, AuthCodeRequiredDelegate onAuthCodeRequired) + : base(credentials) + { + ServicePointManager.DefaultConnectionLimit = int.MaxValue; - // required for Windows 7 breaking connection - ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13 | SecurityProtocolType.Tls12; + // required for Windows 7 breaking connection + ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13 | SecurityProtocolType.Tls12; - _connectionLimiter = new SemaphoreSlim(settings.MaxConnectionCount); + _connectionLimiter = new SemaphoreSlim(settings.MaxConnectionCount); - HttpSettings.CloudSettings = settings; - HttpSettings.UserAgent = settings.UserAgent; - HttpSettings.Proxy = settings.Proxy; + HttpSettings.CloudSettings = settings; + HttpSettings.UserAgent = settings.UserAgent; + HttpSettings.Proxy = settings.Proxy; - _onAuthCodeRequired = onAuthCodeRequired; - Auth = new OAuth(_connectionLimiter, HttpSettings, credentials, onAuthCodeRequired); + _onAuthCodeRequired = onAuthCodeRequired; + Auth = new OAuth(_connectionLimiter, HttpSettings, credentials, onAuthCodeRequired); - ShardManager = new ShardManager(_connectionLimiter, this); - } + ShardManager = new ShardManager(_connectionLimiter, this); + } - public Stream GetDownloadStream(File file, long? start = null, long? end = null) - { - var istream = GetDownloadStreamInternal(file, start, end); - return istream; - } + public Stream GetDownloadStream(File file, long? start = null, long? end = null) + { + var istream = GetDownloadStreamInternal(file, start, end); + return istream; + } - private DownloadStream GetDownloadStreamInternal(File file, long? start = null, long? end = null) - { - bool isLinked = !file.PublicLinks.IsEmpty; + private DownloadStream GetDownloadStreamInternal(File file, long? start = null, long? end = null) + { + bool isLinked = !file.PublicLinks.IsEmpty; - Cached downServer = null; - var pendingServers = isLinked - ? ShardManager.WebLinkDownloadServersPending - : ShardManager.DownloadServersPending; - Stopwatch watch = new Stopwatch(); + Cached downServer = null; + var pendingServers = isLinked + ? ShardManager.WebLinkDownloadServersPending + : ShardManager.DownloadServersPending; + Stopwatch watch = new Stopwatch(); - HttpWebRequest request = null; - CustomDisposable ResponseGenerator(long instart, long inend, File file) + HttpWebRequest request = null; + CustomDisposable ResponseGenerator(long instart, long inend, File file) + { + var resp = Retry.Do(() => { - var resp = Retry.Do(() => - { - downServer = pendingServers.Next(downServer); + downServer = pendingServers.Next(downServer); - request = new DownloadRequest(HttpSettings, Auth, file, instart, inend, downServer.Value.Url, PublicBaseUrls); + request = new DownloadRequest(HttpSettings, Auth, file, instart, inend, downServer.Value.Url, PublicBaseUrls); - watch.Start(); - var response = (HttpWebResponse)request.GetResponse(); - return new CustomDisposable - { - Value = response, - OnDispose = () => - { - pendingServers.Free(downServer); - watch.Stop(); - Logger.Debug($"HTTP:{request.Method}:{request.RequestUri.AbsoluteUri} ({watch.Elapsed.Milliseconds} ms)"); - } - }; - }, - exception => - exception is WebException { Response: HttpWebResponse { StatusCode: HttpStatusCode.NotFound } }, - exception => + watch.Start(); + var response = (HttpWebResponse)request.GetResponse(); + return new CustomDisposable { - pendingServers.Free(downServer); - Logger.Warn($"Retrying HTTP:{request.Method}:{request.RequestUri.AbsoluteUri} on exception {exception.Message}"); - }, - TimeSpan.FromSeconds(1), 2); - - return resp; - } + Value = response, + OnDispose = () => + { + pendingServers.Free(downServer); + watch.Stop(); + Logger.Debug($"HTTP:{request.Method}:{request.RequestUri.AbsoluteUri} ({watch.Elapsed.Milliseconds} ms)"); + } + }; + }, + exception => + exception is WebException { Response: HttpWebResponse { StatusCode: HttpStatusCode.NotFound } }, + exception => + { + pendingServers.Free(downServer); + Logger.Warn($"Retrying HTTP:{request.Method}:{request.RequestUri.AbsoluteUri} on exception {exception.Message}"); + }, + TimeSpan.FromSeconds(1), 2); - var stream = new DownloadStream(ResponseGenerator, file, start, end); - return stream; + return resp; } - /// - /// Get shard info that to do post get request. Can be use for anonymous user. - /// - /// Shard type as numeric type. - /// Shard info. - public override async Task GetShardInfo(ShardType shardType) - { - //TODO: rewrite ShardManager - if (shardType == ShardType.Upload) return ShardManager.UploadServer.Value; + var stream = new DownloadStream(ResponseGenerator, file, start, end); + return stream; + } + + /// + /// Get shard info that to do post get request. Can be use for anonymous user. + /// + /// Shard type as numeric type. + /// Shard info. + public override async Task GetShardInfo(ShardType shardType) + { + //TODO: rewrite ShardManager + if (shardType == ShardType.Upload) return ShardManager.UploadServer.Value; - bool refreshed = false; - for (int i = 0; i < 10; i++) + bool refreshed = false; + for (int i = 0; i < 10; i++) + { + await Task.Delay(80 * i); + var ishards = await Task.Run(() => ShardManager.CachedShards.Value); + var ishard = ishards[shardType]; + var banned = ShardManager.BannedShards.Value; + if (banned.All(bsh => bsh.Url != ishard.Url)) { - await Task.Delay(80 * i); - var ishards = await Task.Run(() => ShardManager.CachedShards.Value); - var ishard = ishards[shardType]; - var banned = ShardManager.BannedShards.Value; - if (banned.All(bsh => bsh.Url != ishard.Url)) - { - if (refreshed) Auth.ExpireDownloadToken(); - return ishard; - } - ShardManager.CachedShards.Expire(); - refreshed = true; + if (refreshed) Auth.ExpireDownloadToken(); + return ishard; } + ShardManager.CachedShards.Expire(); + refreshed = true; + } - Logger.Error("Cannot get working shard."); + Logger.Error("Cannot get working shard."); - var shards = await Task.Run(() => ShardManager.CachedShards.Value); - var shard = shards[shardType]; - return shard; - } + var shards = await Task.Run(() => ShardManager.CachedShards.Value); + var shard = shards[shardType]; + return shard; + } - public async Task CloneItem(string fromUrl, string toPath) - { - var req = await new CloneItemRequest(HttpSettings, Auth, fromUrl, toPath).MakeRequestAsync(_connectionLimiter); - var res = req.ToCloneItemResult(); - return res; - } + public async Task CloneItem(string fromUrl, string toPath) + { + var req = await new CloneItemRequest(HttpSettings, Auth, fromUrl, toPath).MakeRequestAsync(_connectionLimiter); + var res = req.ToCloneItemResult(); + return res; + } - public async Task Copy(string sourceFullPath, string destinationPath, ConflictResolver? conflictResolver = null) - { - var req = await new CopyRequest(HttpSettings, Auth, sourceFullPath, destinationPath, conflictResolver).MakeRequestAsync(_connectionLimiter); - var res = req.ToCopyResult(); - return res; - } + public async Task Copy(string sourceFullPath, string destinationPath, ConflictResolver? conflictResolver = null) + { + var req = await new CopyRequest(HttpSettings, Auth, sourceFullPath, destinationPath, conflictResolver).MakeRequestAsync(_connectionLimiter); + var res = req.ToCopyResult(); + return res; + } - public async Task Move(string sourceFullPath, string destinationPath, ConflictResolver? conflictResolver = null) - { - //var req = await new MoveRequest(HttpSettings, Auth, sourceFullPath, destinationPath).MakeRequestAsync(_connectionLimiter); - //var res = req.ToCopyResult(); - //return res; + public async Task Move(string sourceFullPath, string destinationPath, ConflictResolver? conflictResolver = null) + { + //var req = await new MoveRequest(HttpSettings, Auth, sourceFullPath, destinationPath).MakeRequestAsync(_connectionLimiter); + //var res = req.ToCopyResult(); + //return res; - var req = await new MoveRequest(HttpSettings, Auth, ShardManager.MetaServer.Url, sourceFullPath, destinationPath) - .MakeRequestAsync(_connectionLimiter); + var req = await new MoveRequest(HttpSettings, Auth, ShardManager.MetaServer.Url, sourceFullPath, destinationPath) + .MakeRequestAsync(_connectionLimiter); - var res = req.ToCopyResult(WebDavPath.Name(destinationPath)); - return res; + var res = req.ToCopyResult(WebDavPath.Name(destinationPath)); + return res; - } + } - private async Task FolderInfo(string path, int depth = 1) + private async Task FolderInfo(string path, int depth = 1) + { + try { - try + ListRequest.Result dataRes = + await new ListRequest(HttpSettings, Auth, ShardManager.MetaServer.Url, path, depth) + .MakeRequestAsync(_connectionLimiter); + + // если файл разбит или зашифрован - то надо взять все куски + // в протоколе V2 на запрос к файлу сразу приходит листинг каталога, в котором он лежит + // здесь (протокол Bin) приходит информация именно по указанному файлу + // поэтому вот такой костыль с двойным запросом + //TODO: переделать двойной запрос к файлу + if (dataRes.Item is FsFile { Size: < 2048 }) { - ListRequest.Result dataRes = - await new ListRequest(HttpSettings, Auth, ShardManager.MetaServer.Url, path, depth) - .MakeRequestAsync(_connectionLimiter); - - // если файл разбит или зашифрован - то надо взять все куски - // в протоколе V2 на запрос к файлу сразу приходит листинг каталога, в котором он лежит - // здесь (протокол Bin) приходит информация именно по указанному файлу - // поэтому вот такой костыль с двойным запросом - //TODO: переделать двойной запрос к файлу - if (dataRes.Item is FsFile { Size: < 2048 }) - { - string name = WebDavPath.Name(path); - path = WebDavPath.Parent(path); + string name = WebDavPath.Name(path); + path = WebDavPath.Parent(path); - dataRes = await new ListRequest(HttpSettings, Auth, ShardManager.MetaServer.Url, path, 1) - .MakeRequestAsync(_connectionLimiter); + dataRes = await new ListRequest(HttpSettings, Auth, ShardManager.MetaServer.Url, path, 1) + .MakeRequestAsync(_connectionLimiter); - var folder = dataRes.ToFolder(); + var folder = dataRes.ToFolder(); - return folder.Descendants.FirstOrDefault(f => f.Name == name); - } - return dataRes.ToEntry(); - } - catch (RequestException re) when (re.StatusCode == HttpStatusCode.NotFound) - { - return null; - } - catch (WebException e) when (e.Response is HttpWebResponse { StatusCode: HttpStatusCode.NotFound }) - { - return null; + return folder.Descendants.FirstOrDefault(f => f.Name == name); } + return dataRes.ToEntry(); } - - public async Task FolderInfo(RemotePath path, int offset = 0, int limit = int.MaxValue, int depth = 1) + catch (RequestException re) when (re.StatusCode == HttpStatusCode.NotFound) { - if (Credentials.IsAnonymous) - return await AnonymousRepo.FolderInfo(path, offset, limit); - - if (!path.IsLink && depth > 1) - return await FolderInfo(path.Path, depth); + return null; + } + catch (WebException e) when (e.Response is HttpWebResponse { StatusCode: HttpStatusCode.NotFound }) + { + return null; + } + } - FolderInfoResult dataRes; - try - { - dataRes = await new FolderInfoRequest(HttpSettings, Auth, path, offset, limit) - .MakeRequestAsync(_connectionLimiter); - } - catch (WebException e) when (e.Response is HttpWebResponse { StatusCode: HttpStatusCode.NotFound }) - { - return null; - } + public async Task FolderInfo(RemotePath path, int offset = 0, int limit = int.MaxValue, int depth = 1) + { + if (Credentials.IsAnonymous) + return await AnonymousRepo.FolderInfo(path, offset, limit); - Cloud.ItemType itemType; + if (!path.IsLink && depth > 1) + return await FolderInfo(path.Path, depth); - //TODO: subject to refact, bad-bad-bad - if (!path.IsLink || path.Link.ItemType == Cloud.ItemType.Unknown) - itemType = dataRes.Body.Home == path.Path || - WebDavPath.PathEquals("/" + dataRes.Body.Weblink, path.Path) - ? Cloud.ItemType.Folder - : Cloud.ItemType.File; - else - itemType = path.Link.ItemType; + FolderInfoResult dataRes; + try + { + dataRes = await new FolderInfoRequest(HttpSettings, Auth, path, offset, limit) + .MakeRequestAsync(_connectionLimiter); + } + catch (WebException e) when (e.Response is HttpWebResponse { StatusCode: HttpStatusCode.NotFound }) + { + return null; + } + Cloud.ItemType itemType; - var entry = itemType == Cloud.ItemType.File - ? (IEntry)dataRes.ToFile( - PublicBaseUrlDefault, - home: WebDavPath.Parent(path.Path ?? string.Empty), - ulink: path.Link, - fileName: path.Link == null ? WebDavPath.Name(path.Path) : path.Link.OriginalName, - nameReplacement: path.Link?.IsLinkedToFileSystem ?? true ? WebDavPath.Name(path.Path) : path.Link.Name) - : (IEntry)dataRes.ToFolder(PublicBaseUrlDefault, path.Path, path.Link); + //TODO: subject to refact, bad-bad-bad + if (!path.IsLink || path.Link.ItemType == Cloud.ItemType.Unknown) + itemType = dataRes.Body.Home == path.Path || + WebDavPath.PathEquals("/" + dataRes.Body.Weblink, path.Path) + ? Cloud.ItemType.Folder + : Cloud.ItemType.File; + else + itemType = path.Link.ItemType; - if (limit == int.MaxValue && entry is Folder fld) - fld.IsChildrenLoaded = limit == int.MaxValue; - return entry; - } + var entry = itemType == Cloud.ItemType.File + ? (IEntry)dataRes.ToFile( + PublicBaseUrlDefault, + home: WebDavPath.Parent(path.Path ?? string.Empty), + ulink: path.Link, + fileName: path.Link == null ? WebDavPath.Name(path.Path) : path.Link.OriginalName, + nameReplacement: path.Link?.IsLinkedToFileSystem ?? true ? WebDavPath.Name(path.Path) : path.Link.Name) + : (IEntry)dataRes.ToFolder(PublicBaseUrlDefault, path.Path, path.Link); + if (limit == int.MaxValue && entry is Folder fld) + fld.IsChildrenLoaded = limit == int.MaxValue; + return entry; + } - public async Task ItemInfo(RemotePath path, int offset = 0, int limit = int.MaxValue) - { - var req = await new ItemInfoRequest(HttpSettings, Auth, path, offset, limit).MakeRequestAsync(_connectionLimiter); - return req; - } - public async Task AccountInfo() - { - var req = await new AccountInfoRequest(HttpSettings, Auth).MakeRequestAsync(_connectionLimiter); - var res = req.ToAccountInfo(); - return res; - } - public async Task Publish(string fullPath) - { - var req = await new PublishRequest(HttpSettings, Auth, fullPath).MakeRequestAsync(_connectionLimiter); - var res = req.ToPublishResult(); + public async Task ItemInfo(RemotePath path, int offset = 0, int limit = int.MaxValue) + { + var req = await new ItemInfoRequest(HttpSettings, Auth, path, offset, limit).MakeRequestAsync(_connectionLimiter); + return req; + } - if (res.IsSuccess) - { - CachedSharedList.Value[fullPath] = [new PublicLinkInfo(PublicBaseUrlDefault + res.Url)]; - } + public async Task AccountInfo() + { + var req = await new AccountInfoRequest(HttpSettings, Auth).MakeRequestAsync(_connectionLimiter); + var res = req.ToAccountInfo(); + return res; + } - return res; - } + public async Task Publish(string fullPath) + { + var req = await new PublishRequest(HttpSettings, Auth, fullPath).MakeRequestAsync(_connectionLimiter); + var res = req.ToPublishResult(); - public async Task Unpublish(Uri publicLink, string fullPath) + if (res.IsSuccess) { - foreach (var item in CachedSharedList.Value - .Where(kvp => kvp.Value.Any(u => u.Uri.Equals(publicLink))).ToList()) - { - CachedSharedList.Value.Remove(item.Key); - } - - var req = await new UnpublishRequest(this, HttpSettings, Auth, publicLink.OriginalString).MakeRequestAsync(_connectionLimiter); - var res = req.ToUnpublishResult(); - return res; + CachedSharedList.Value[fullPath] = [new PublicLinkInfo(PublicBaseUrlDefault + res.Url)]; } - public async Task Remove(string fullPath) - { - var req = await new RemoveRequest(HttpSettings, Auth, fullPath).MakeRequestAsync(_connectionLimiter); - var res = req.ToRemoveResult(); - return res; - } + return res; + } - public async Task Rename(string fullPath, string newName) + public async Task Unpublish(Uri publicLink, string fullPath) + { + foreach (var item in CachedSharedList.Value + .Where(kvp => kvp.Value.Any(u => u.Uri.Equals(publicLink))).ToList()) { - //var req = await new RenameRequest(HttpSettings, Authent, fullPath, newName).MakeRequestAsync(_connectionLimiter); - //var res = req.ToRenameResult(); - //return res; + CachedSharedList.Value.Remove(item.Key); + } - string newFullPath = WebDavPath.Combine(WebDavPath.Parent(fullPath), newName); - var req = await new MoveRequest(HttpSettings, Auth, ShardManager.MetaServer.Url, fullPath, newFullPath) - .MakeRequestAsync(_connectionLimiter); + var req = await new UnpublishRequest(this, HttpSettings, Auth, publicLink.OriginalString).MakeRequestAsync(_connectionLimiter); + var res = req.ToUnpublishResult(); + return res; + } - var res = req.ToRenameResult(); - return res; - } + public async Task Remove(string fullPath) + { + var req = await new RemoveRequest(HttpSettings, Auth, fullPath).MakeRequestAsync(_connectionLimiter); + var res = req.ToRemoveResult(); + return res; + } - public Dictionary GetShardInfo1() - { - return Auth.IsAnonymous - ? new WebV2.Requests - .ShardInfoRequest(HttpSettings, Auth).MakeRequestAsync(_connectionLimiter).Result.ToShardInfo() - : new ShardInfoRequest(HttpSettings, Auth).MakeRequestAsync(_connectionLimiter).Result.ToShardInfo(); - } + public async Task Rename(string fullPath, string newName) + { + //var req = await new RenameRequest(HttpSettings, Authent, fullPath, newName).MakeRequestAsync(_connectionLimiter); + //var res = req.ToRenameResult(); + //return res; + string newFullPath = WebDavPath.Combine(WebDavPath.Parent(fullPath), newName); + var req = await new MoveRequest(HttpSettings, Auth, ShardManager.MetaServer.Url, fullPath, newFullPath) + .MakeRequestAsync(_connectionLimiter); - public Cached>> CachedSharedList - { - get - { - return _cachedSharedList ??= new Cached>>(_ => - { - var z = GetShareListInner().Result; + var res = req.ToRenameResult(); + return res; + } - var res = z.Body.List - .ToDictionary( - fik => fik.Home, - fiv => Enumerable.Repeat(new PublicLinkInfo(PublicBaseUrlDefault + fiv.Weblink), - 1)); + public Dictionary GetShardInfo1() + { + return Auth.IsAnonymous + ? new WebV2.Requests + .ShardInfoRequest(HttpSettings, Auth).MakeRequestAsync(_connectionLimiter).Result.ToShardInfo() + : new ShardInfoRequest(HttpSettings, Auth).MakeRequestAsync(_connectionLimiter).Result.ToShardInfo(); + } - return res; - }, - _ => TimeSpan.FromSeconds(30)); - } - } - private Cached>> _cachedSharedList; - private async Task GetShareListInner() + public Cached>> CachedSharedList + { + get { - var res = await new SharedListRequest(HttpSettings, Auth) - .MakeRequestAsync(_connectionLimiter); + return _cachedSharedList ??= new Cached>>(_ => + { + var z = GetShareListInner().Result; - return res; + var res = z.Body.List + .ToDictionary( + fik => fik.Home, + fiv => Enumerable.Repeat(new PublicLinkInfo(PublicBaseUrlDefault + fiv.Weblink), + 1)); + + return res; + }, + _ => TimeSpan.FromSeconds(30)); } + } + private Cached>> _cachedSharedList; - public IEnumerable GetShareLinks(string path) - { - if (!CachedSharedList.Value.TryGetValue(path, out var links)) - yield break; + private async Task GetShareListInner() + { + var res = await new SharedListRequest(HttpSettings, Auth) + .MakeRequestAsync(_connectionLimiter); - foreach (var link in links) - yield return link; - } + return res; + } - public void CleanTrash() - { - throw new NotImplementedException(); - } + public IEnumerable GetShareLinks(string path) + { + if (!CachedSharedList.Value.TryGetValue(path, out var links)) + yield break; - public async Task CreateFolder(string path) - { - /* - * В названии папок нельзя использовать символы «" * / : < > ? \ |». - * Также название не может состоять только из точки «.» или из двух точек «..» - */ - path = StripBadSymbols(path); + foreach (var link in links) + yield return link; + } + + public void CleanTrash() + { + throw new NotImplementedException(); + } - //return (await new CreateFolderRequest(HttpSettings, Authenticator, path).MakeRequestAsync()) - // .ToCreateFolderResult(); + public async Task CreateFolder(string path) + { + /* + * В названии папок нельзя использовать символы «" * / : < > ? \ |». + * Также название не может состоять только из точки «.» или из двух точек «..» + */ + path = StripBadSymbols(path); - return (await new CreateFolderRequest(HttpSettings, Auth, ShardManager.MetaServer.Url, path).MakeRequestAsync(_connectionLimiter)) - .ToCreateFolderResult(); - } + //return (await new CreateFolderRequest(HttpSettings, Authenticator, path).MakeRequestAsync()) + // .ToCreateFolderResult(); - public async Task AddFile(string fileFullPath, IFileHash fileHash, FileSize fileSize, DateTime dateTime, ConflictResolver? conflictResolver) - { - /* - * В названии папок нельзя использовать символы «" * / : < > ? \ |». - * Также название не может состоять только из точки «.» или из двух точек «..» - */ - fileFullPath = StripBadSymbols(fileFullPath); + return (await new CreateFolderRequest(HttpSettings, Auth, ShardManager.MetaServer.Url, path).MakeRequestAsync(_connectionLimiter)) + .ToCreateFolderResult(); + } - //var res = await new CreateFileRequest(Proxy, Authenticator, fileFullPath, fileHash, fileSize, conflictResolver) - // .MakeRequestAsync(_connectionLimiter); - //return res.ToAddFileResult(); + public async Task AddFile(string fileFullPath, IFileHash fileHash, FileSize fileSize, DateTime dateTime, ConflictResolver? conflictResolver) + { + /* + * В названии папок нельзя использовать символы «" * / : < > ? \ |». + * Также название не может состоять только из точки «.» или из двух точек «..» + */ + fileFullPath = StripBadSymbols(fileFullPath); - //using Mobile request because of supporting file modified time + //var res = await new CreateFileRequest(Proxy, Authenticator, fileFullPath, fileHash, fileSize, conflictResolver) + // .MakeRequestAsync(_connectionLimiter); + //return res.ToAddFileResult(); - //TODO: refact, make mixed repo - var req = await new MobAddFileRequest(HttpSettings, Auth, ShardManager.MetaServer.Url, fileFullPath, fileHash.Hash.Value, fileSize, dateTime, conflictResolver) - .MakeRequestAsync(_connectionLimiter); + //using Mobile request because of supporting file modified time - var res = req.ToAddFileResult(); - return res; - } + //TODO: refact, make mixed repo + var req = await new MobAddFileRequest(HttpSettings, Auth, ShardManager.MetaServer.Url, fileFullPath, fileHash.Hash.Value, fileSize, dateTime, conflictResolver) + .MakeRequestAsync(_connectionLimiter); - public string StripBadSymbols(string fullPath) - { - /* - * В названии папок нельзя использовать символы «" * / : < > ? \ |». - * Также название не может состоять только из точки «.» или из двух точек «..» - */ - string name = WebDavPath.Name(fullPath); - string newName = name - .Replace("*", "\u2022") // • - .Replace(":", "\u205e") // ⁞ - .Replace("<", "\u00ab") // « - .Replace(">", "\u00bb") // » - .Replace("?", "\u203d") // ‽ - .Replace("|", "\u2502") // │ - .Replace("/", "~") - .Replace("\\", "~") - ; - return name != newName - ? WebDavPath.Combine(WebDavPath.Parent(fullPath), newName) - : name; - } + var res = req.ToAddFileResult(); + return res; + } - public async Task DetectOutsideChanges() => await Task.FromResult(null); + public string StripBadSymbols(string fullPath) + { + /* + * В названии папок нельзя использовать символы «" * / : < > ? \ |». + * Также название не может состоять только из точки «.» или из двух точек «..» + */ + string name = WebDavPath.Name(fullPath); + string newName = name + .Replace("*", "\u2022") // • + .Replace(":", "\u205e") // ⁞ + .Replace("<", "\u00ab") // « + .Replace(">", "\u00bb") // » + .Replace("?", "\u203d") // ‽ + .Replace("|", "\u2502") // │ + .Replace("/", "~") + .Replace("\\", "~") + ; + return name != newName + ? WebDavPath.Combine(WebDavPath.Parent(fullPath), newName) + : name; } + + public async Task DetectOutsideChanges() => await Task.FromResult(null); } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/AccountInfoRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/AccountInfoRequest.cs index 80998d20..50efe0a4 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/AccountInfoRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/AccountInfoRequest.cs @@ -1,14 +1,13 @@ using YaR.Clouds.Base.Requests; using YaR.Clouds.Base.Requests.Types; -namespace YaR.Clouds.Base.Repos.MailRuCloud.WebM1.Requests +namespace YaR.Clouds.Base.Repos.MailRuCloud.WebM1.Requests; + +class AccountInfoRequest : BaseRequestJson { - class AccountInfoRequest : BaseRequestJson + public AccountInfoRequest(HttpCommonSettings settings, IAuth auth) : base(settings, auth) { - public AccountInfoRequest(HttpCommonSettings settings, IAuth auth) : base(settings, auth) - { - } - - protected override string RelationalUri => $"{_settings.BaseDomain}/api/m1/user?access_token={_auth.AccessToken}"; } + + protected override string RelationalUri => $"{_settings.BaseDomain}/api/m1/user?access_token={_auth.AccessToken}"; } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/CloneItemRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/CloneItemRequest.cs index c028bb5a..ef1f39cd 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/CloneItemRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/CloneItemRequest.cs @@ -2,20 +2,19 @@ using YaR.Clouds.Base.Requests; using YaR.Clouds.Base.Requests.Types; -namespace YaR.Clouds.Base.Repos.MailRuCloud.WebM1.Requests -{ - class CloneItemRequest : BaseRequestJson> - { - private readonly string _fromUrl; - private readonly string _toPath; +namespace YaR.Clouds.Base.Repos.MailRuCloud.WebM1.Requests; - public CloneItemRequest(HttpCommonSettings settings, IAuth auth, string fromUrl, string toPath) - : base(settings, auth) - { - _fromUrl = fromUrl; - _toPath = toPath; - } +class CloneItemRequest : BaseRequestJson> +{ + private readonly string _fromUrl; + private readonly string _toPath; - protected override string RelationalUri => $"{_settings.BaseDomain}/api/m1/clone?conflict=rename&folder={Uri.EscapeDataString(_toPath)}&weblink={Uri.EscapeDataString(_fromUrl)}&access_token={_auth.AccessToken}"; + public CloneItemRequest(HttpCommonSettings settings, IAuth auth, string fromUrl, string toPath) + : base(settings, auth) + { + _fromUrl = fromUrl; + _toPath = toPath; } + + protected override string RelationalUri => $"{_settings.BaseDomain}/api/m1/clone?conflict=rename&folder={Uri.EscapeDataString(_toPath)}&weblink={Uri.EscapeDataString(_fromUrl)}&access_token={_auth.AccessToken}"; } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/CopyRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/CopyRequest.cs index a6c1f3cf..83165319 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/CopyRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/CopyRequest.cs @@ -3,36 +3,35 @@ using YaR.Clouds.Base.Requests; using YaR.Clouds.Base.Requests.Types; -namespace YaR.Clouds.Base.Repos.MailRuCloud.WebM1.Requests +namespace YaR.Clouds.Base.Repos.MailRuCloud.WebM1.Requests; + +class CopyRequest : BaseRequestJson> { - class CopyRequest : BaseRequestJson> - { - private readonly string _sourceFullPath; - private readonly string _destinationPath; - private readonly ConflictResolver _conflictResolver; + private readonly string _sourceFullPath; + private readonly string _destinationPath; + private readonly ConflictResolver _conflictResolver; - /// - /// - /// - /// - /// - /// - /// (without item name) - /// - public CopyRequest(HttpCommonSettings settings, IAuth auth, string sourceFullPath, string destinationPath, ConflictResolver? conflictResolver = null) - : base(settings, auth) - { - _sourceFullPath = sourceFullPath; - _destinationPath = destinationPath; - _conflictResolver = conflictResolver ?? ConflictResolver.Rename; - } + /// + /// + /// + /// + /// + /// + /// (without item name) + /// + public CopyRequest(HttpCommonSettings settings, IAuth auth, string sourceFullPath, string destinationPath, ConflictResolver? conflictResolver = null) + : base(settings, auth) + { + _sourceFullPath = sourceFullPath; + _destinationPath = destinationPath; + _conflictResolver = conflictResolver ?? ConflictResolver.Rename; + } - protected override string RelationalUri => $"/api/m1/file/copy?access_token={_auth.AccessToken}"; + protected override string RelationalUri => $"/api/m1/file/copy?access_token={_auth.AccessToken}"; - protected override byte[] CreateHttpContent() - { - var data = $"home={Uri.EscapeDataString(_sourceFullPath)}&email={_auth.Login}&x-email={_auth.Login}&conflict={_conflictResolver}&folder={Uri.EscapeDataString(_destinationPath)}"; - return Encoding.UTF8.GetBytes(data); - } + protected override byte[] CreateHttpContent() + { + var data = $"home={Uri.EscapeDataString(_sourceFullPath)}&email={_auth.Login}&x-email={_auth.Login}&conflict={_conflictResolver}&folder={Uri.EscapeDataString(_destinationPath)}"; + return Encoding.UTF8.GetBytes(data); } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/CreateFileRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/CreateFileRequest.cs index 1de0701a..3fd6eba8 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/CreateFileRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/CreateFileRequest.cs @@ -3,31 +3,30 @@ using YaR.Clouds.Base.Requests; using YaR.Clouds.Base.Requests.Types; -namespace YaR.Clouds.Base.Repos.MailRuCloud.WebM1.Requests +namespace YaR.Clouds.Base.Repos.MailRuCloud.WebM1.Requests; + +class CreateFileRequest : BaseRequestJson> { - class CreateFileRequest : BaseRequestJson> - { - private readonly string _fullPath; - private readonly string _hash; - private readonly long _size; - private readonly ConflictResolver _conflictResolver; + private readonly string _fullPath; + private readonly string _hash; + private readonly long _size; + private readonly ConflictResolver _conflictResolver; - public CreateFileRequest(HttpCommonSettings settings, IAuth auth, string fullPath, string hash, long size, ConflictResolver? conflictResolver) - : base(settings, auth) - { - _fullPath = fullPath; - _hash = hash; - _size = size; - _conflictResolver = conflictResolver ?? ConflictResolver.Rename; - } + public CreateFileRequest(HttpCommonSettings settings, IAuth auth, string fullPath, string hash, long size, ConflictResolver? conflictResolver) + : base(settings, auth) + { + _fullPath = fullPath; + _hash = hash; + _size = size; + _conflictResolver = conflictResolver ?? ConflictResolver.Rename; + } - protected override string RelationalUri => $"/api/m1/file/add?access_token={_auth.AccessToken}"; + protected override string RelationalUri => $"/api/m1/file/add?access_token={_auth.AccessToken}"; - protected override byte[] CreateHttpContent() - { - string data = $"home={Uri.EscapeDataString(_fullPath)}&conflict={_conflictResolver}&hash={_hash}&size={_size}"; + protected override byte[] CreateHttpContent() + { + string data = $"home={Uri.EscapeDataString(_fullPath)}&conflict={_conflictResolver}&hash={_hash}&size={_size}"; - return Encoding.UTF8.GetBytes(data); - } + return Encoding.UTF8.GetBytes(data); } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/CreateFolderRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/CreateFolderRequest.cs index 99f41ed8..116b8ad8 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/CreateFolderRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/CreateFolderRequest.cs @@ -3,24 +3,23 @@ using YaR.Clouds.Base.Requests; using YaR.Clouds.Base.Requests.Types; -namespace YaR.Clouds.Base.Repos.MailRuCloud.WebM1.Requests +namespace YaR.Clouds.Base.Repos.MailRuCloud.WebM1.Requests; + +class CreateFolderRequest : BaseRequestJson> { - class CreateFolderRequest : BaseRequestJson> - { - private readonly string _fullPath; + private readonly string _fullPath; - public CreateFolderRequest(HttpCommonSettings settings, IAuth auth, string fullPath) - : base(settings, auth) - { - _fullPath = fullPath; - } + public CreateFolderRequest(HttpCommonSettings settings, IAuth auth, string fullPath) + : base(settings, auth) + { + _fullPath = fullPath; + } - protected override string RelationalUri => $"/api/m1/folder/add?access_token={_auth.AccessToken}"; + protected override string RelationalUri => $"/api/m1/folder/add?access_token={_auth.AccessToken}"; - protected override byte[] CreateHttpContent() - { - var data = $"home={Uri.EscapeDataString(_fullPath)}&conflict=rename"; - return Encoding.UTF8.GetBytes(data); - } + protected override byte[] CreateHttpContent() + { + var data = $"home={Uri.EscapeDataString(_fullPath)}&conflict=rename"; + return Encoding.UTF8.GetBytes(data); } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/FolderInfoRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/FolderInfoRequest.cs index 294b6063..56b2295d 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/FolderInfoRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/FolderInfoRequest.cs @@ -5,133 +5,132 @@ using YaR.Clouds.Base.Requests; using YaR.Clouds.Base.Requests.Types; -namespace YaR.Clouds.Base.Repos.MailRuCloud.WebM1.Requests -{ - internal class FolderInfoRequest : BaseRequestJson - { - private readonly string _path; - private readonly bool _isWebLink; - private readonly int _offset; - private readonly int _limit; +namespace YaR.Clouds.Base.Repos.MailRuCloud.WebM1.Requests; - /* - * Внимание! - * При выборке с limit меньшим, чем количество файлов в папке, - * Mail.Ru выдает список файлов от начала папки, игнорируя название файла в path. - * Например, в папке файлы: a, b, c, d... z. Делаем выборку /x с limit равным 1. Сервер вернет файл a вместо x. - * - * Когда выборка делается с limit равным 1 или 2 вместо int.MaxValue, - * это означает, что была одна из операций создания, удаления, переименования и т.д., - * после которой нужно подтверждение наличия файла или директории с соответствующим названием, - * вместо всего списка выбирается только одно названия для подтверждения. - * Из-за описанной выше проблемы при выборке одного названия - * при обращении к серверу запрашивается информация типа file вместо folder, - * а затем данные перекладываются в структуру, для которой давно есть обработка. - */ +internal class FolderInfoRequest : BaseRequestJson +{ + private readonly string _path; + private readonly bool _isWebLink; + private readonly int _offset; + private readonly int _limit; - public FolderInfoRequest(HttpCommonSettings settings, IAuth auth, RemotePath path, int offset = 0, int limit = int.MaxValue) - : base(settings, auth) - { - _isWebLink = path.IsLink; + /* + * Внимание! + * При выборке с limit меньшим, чем количество файлов в папке, + * Mail.Ru выдает список файлов от начала папки, игнорируя название файла в path. + * Например, в папке файлы: a, b, c, d... z. Делаем выборку /x с limit равным 1. Сервер вернет файл a вместо x. + * + * Когда выборка делается с limit равным 1 или 2 вместо int.MaxValue, + * это означает, что была одна из операций создания, удаления, переименования и т.д., + * после которой нужно подтверждение наличия файла или директории с соответствующим названием, + * вместо всего списка выбирается только одно названия для подтверждения. + * Из-за описанной выше проблемы при выборке одного названия + * при обращении к серверу запрашивается информация типа file вместо folder, + * а затем данные перекладываются в структуру, для которой давно есть обработка. + */ - if (path.IsLink) - { - string ustr = path.Link.Href.OriginalString; - _path = "/" + ustr.Remove(0, ustr.IndexOf("/public/", StringComparison.Ordinal) + "/public/".Length); - } - else - { - _path = path.Path; - } + public FolderInfoRequest(HttpCommonSettings settings, IAuth auth, RemotePath path, int offset = 0, int limit = int.MaxValue) + : base(settings, auth) + { + _isWebLink = path.IsLink; - _offset = offset; - _limit = limit; + if (path.IsLink) + { + string ustr = path.Link.Href.OriginalString; + _path = "/" + ustr.Remove(0, ustr.IndexOf("/public/", StringComparison.Ordinal) + "/public/".Length); } - - protected override string RelationalUri - => _limit <= 2 - ? $"/api/m1/file?access_token={_auth.AccessToken}" - : $"/api/m1/folder?access_token={_auth.AccessToken}&offset={_offset}&limit={_limit}"; - - protected override byte[] CreateHttpContent() + else { - // path sent using POST cause of unprintable Unicode characters may exists - // https://github.com/yar229/WebDavMailRuCloud/issues/54 - var data = _isWebLink - ? $"weblink={_path}" - : $"home={Uri.EscapeDataString(_path)}"; - return Encoding.UTF8.GetBytes(data); + _path = path.Path; } - protected override RequestResponse DeserializeMessage(NameValueCollection responseHeaders, Stream stream) - { - RequestResponse response = base.DeserializeMessage(responseHeaders, stream); + _offset = offset; + _limit = limit; + } + + protected override string RelationalUri + => _limit <= 2 + ? $"/api/m1/file?access_token={_auth.AccessToken}" + : $"/api/m1/folder?access_token={_auth.AccessToken}&offset={_offset}&limit={_limit}"; + + protected override byte[] CreateHttpContent() + { + // path sent using POST cause of unprintable Unicode characters may exists + // https://github.com/yar229/WebDavMailRuCloud/issues/54 + var data = _isWebLink + ? $"weblink={_path}" + : $"home={Uri.EscapeDataString(_path)}"; + return Encoding.UTF8.GetBytes(data); + } + + protected override RequestResponse DeserializeMessage(NameValueCollection responseHeaders, Stream stream) + { + RequestResponse response = base.DeserializeMessage(responseHeaders, stream); - if (_limit > 2) - return response; + if (_limit > 2) + return response; - #region Данные об одиночном файле или папке нужно переложить структуру списка + #region Данные об одиночном файле или папке нужно переложить структуру списка - FolderInfoResult.FolderInfoBody body = null; - FolderInfoResult folderInfoResult = null; - if (response.Result is not null) + FolderInfoResult.FolderInfoBody body = null; + FolderInfoResult folderInfoResult = null; + if (response.Result is not null) + { + if (response.Result.Body is not null) { - if (response.Result.Body is not null) + string home = response.Result.Body.Home; + string name = response.Result.Body.Name; + if (response.Result.Body.Kind == "file") { - string home = response.Result.Body.Home; - string name = response.Result.Body.Name; - if (response.Result.Body.Kind == "file") - { - home = WebDavPath.Parent(response.Result.Body.Home); - name = WebDavPath.Name(home); - } - - FolderInfoResult.FolderInfoBody.FolderInfoProps item = new() - { - Weblink = response.Result.Body.Weblink, - Size = response.Result.Body.Size, - Name = response.Result.Body.Name, - Home = response.Result.Body.Home, - Kind = response.Result.Body.Kind, - Hash = response.Result.Body.Hash, - Mtime = response.Result.Body.Mtime, - }; - body = new() - { - Name = name, - Home = home, - Kind = response.Result.Body.Kind, - Hash = response.Result.Body.Hash, - Mtime = response.Result.Body.Mtime, - Size = response.Result.Body.Size, - Weblink = response.Result.Body.Weblink, - List = [item], - Count = new FolderInfoResult.FolderInfoBody.FolderInfoCount() - { - Files = response.Result.Body.Kind == "file" ? 1 : 0, - Folders = response.Result.Body.Kind == "folder" ? 1 : 0, - }, - }; + home = WebDavPath.Parent(response.Result.Body.Home); + name = WebDavPath.Name(home); } - folderInfoResult = new FolderInfoResult() + + FolderInfoResult.FolderInfoBody.FolderInfoProps item = new() + { + Weblink = response.Result.Body.Weblink, + Size = response.Result.Body.Size, + Name = response.Result.Body.Name, + Home = response.Result.Body.Home, + Kind = response.Result.Body.Kind, + Hash = response.Result.Body.Hash, + Mtime = response.Result.Body.Mtime, + }; + body = new() { - Time = response.Result.Time, - Email = response.Result.Email, - Status = response.Result.Status, - Body = body, + Name = name, + Home = home, + Kind = response.Result.Body.Kind, + Hash = response.Result.Body.Hash, + Mtime = response.Result.Body.Mtime, + Size = response.Result.Body.Size, + Weblink = response.Result.Body.Weblink, + List = [item], + Count = new FolderInfoResult.FolderInfoBody.FolderInfoCount() + { + Files = response.Result.Body.Kind == "file" ? 1 : 0, + Folders = response.Result.Body.Kind == "folder" ? 1 : 0, + }, }; } - RequestResponse dataRes = new() + folderInfoResult = new FolderInfoResult() { - Ok = response.Ok, - ErrorCode = response.ErrorCode, - Description = response.Description, - Result = folderInfoResult, + Time = response.Result.Time, + Email = response.Result.Email, + Status = response.Result.Status, + Body = body, }; + } + RequestResponse dataRes = new() + { + Ok = response.Ok, + ErrorCode = response.ErrorCode, + Description = response.Description, + Result = folderInfoResult, + }; - return dataRes; + return dataRes; - #endregion - } + #endregion } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/ItemInfoRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/ItemInfoRequest.cs index 9ceb8226..775578c2 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/ItemInfoRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/ItemInfoRequest.cs @@ -2,42 +2,41 @@ using YaR.Clouds.Base.Requests; using YaR.Clouds.Base.Requests.Types; -namespace YaR.Clouds.Base.Repos.MailRuCloud.WebM1.Requests +namespace YaR.Clouds.Base.Repos.MailRuCloud.WebM1.Requests; + +class ItemInfoRequest : BaseRequestJson { - class ItemInfoRequest : BaseRequestJson - { - private readonly string _path; - private readonly bool _isWebLink; - private readonly int _offset; - private readonly int _limit; + private readonly string _path; + private readonly bool _isWebLink; + private readonly int _offset; + private readonly int _limit; - public ItemInfoRequest(HttpCommonSettings settings, IAuth auth, RemotePath path, int offset = 0, int limit = int.MaxValue) - : base(settings, auth) - { - _isWebLink = path.IsLink; - - if (path.IsLink) - { - string ustr = path.Link.Href.OriginalString; - _path = "/" + ustr.Remove(0, ustr.IndexOf("/public/", StringComparison.Ordinal) + "/public/".Length); - } - else - _path = path.Path; + public ItemInfoRequest(HttpCommonSettings settings, IAuth auth, RemotePath path, int offset = 0, int limit = int.MaxValue) + : base(settings, auth) + { + _isWebLink = path.IsLink; - _offset = offset; - _limit = limit; + if (path.IsLink) + { + string ustr = path.Link.Href.OriginalString; + _path = "/" + ustr.Remove(0, ustr.IndexOf("/public/", StringComparison.Ordinal) + "/public/".Length); } + else + _path = path.Path; + + _offset = offset; + _limit = limit; + } - protected override string RelationalUri + protected override string RelationalUri + { + get { - get - { - var uri = _isWebLink - ? $"/api/m1/file?access_token={_auth.AccessToken}&weblink={_path}&offset={_offset}&limit={_limit}" - : $"/api/m1/file?access_token={_auth.AccessToken}&home={Uri.EscapeDataString(_path)}&offset={_offset}&limit={_limit}"; - return uri; - } + var uri = _isWebLink + ? $"/api/m1/file?access_token={_auth.AccessToken}&weblink={_path}&offset={_offset}&limit={_limit}" + : $"/api/m1/file?access_token={_auth.AccessToken}&home={Uri.EscapeDataString(_path)}&offset={_offset}&limit={_limit}"; + return uri; } } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/MoveRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/MoveRequest.cs index 7d711f81..f7efcfdc 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/MoveRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/MoveRequest.cs @@ -3,26 +3,25 @@ using YaR.Clouds.Base.Requests; using YaR.Clouds.Base.Requests.Types; -namespace YaR.Clouds.Base.Repos.MailRuCloud.WebM1.Requests +namespace YaR.Clouds.Base.Repos.MailRuCloud.WebM1.Requests; + +class MoveRequest : BaseRequestJson> { - class MoveRequest : BaseRequestJson> - { - private readonly string _sourceFullPath; - private readonly string _destinationPath; + private readonly string _sourceFullPath; + private readonly string _destinationPath; - public MoveRequest(HttpCommonSettings settings, IAuth auth, string sourceFullPath, string destinationPath) - : base(settings, auth) - { - _sourceFullPath = sourceFullPath; - _destinationPath = destinationPath; - } + public MoveRequest(HttpCommonSettings settings, IAuth auth, string sourceFullPath, string destinationPath) + : base(settings, auth) + { + _sourceFullPath = sourceFullPath; + _destinationPath = destinationPath; + } - protected override string RelationalUri => $"/api/m1/file/move?access_token={_auth.AccessToken}"; + protected override string RelationalUri => $"/api/m1/file/move?access_token={_auth.AccessToken}"; - protected override byte[] CreateHttpContent() - { - var data = $"home={Uri.EscapeDataString(_sourceFullPath)}&email={_auth.Login}&conflict=rename&folder={Uri.EscapeDataString(_destinationPath)}"; - return Encoding.UTF8.GetBytes(data); - } + protected override byte[] CreateHttpContent() + { + var data = $"home={Uri.EscapeDataString(_sourceFullPath)}&email={_auth.Login}&conflict=rename&folder={Uri.EscapeDataString(_destinationPath)}"; + return Encoding.UTF8.GetBytes(data); } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/PublishRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/PublishRequest.cs index bd3ac53e..576afd1f 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/PublishRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/PublishRequest.cs @@ -3,24 +3,23 @@ using YaR.Clouds.Base.Requests; using YaR.Clouds.Base.Requests.Types; -namespace YaR.Clouds.Base.Repos.MailRuCloud.WebM1.Requests +namespace YaR.Clouds.Base.Repos.MailRuCloud.WebM1.Requests; + +class PublishRequest : BaseRequestJson> { - class PublishRequest : BaseRequestJson> - { - private readonly string _fullPath; + private readonly string _fullPath; - public PublishRequest(HttpCommonSettings settings, IAuth auth, string fullPath) - : base(settings, auth) - { - _fullPath = fullPath; - } + public PublishRequest(HttpCommonSettings settings, IAuth auth, string fullPath) + : base(settings, auth) + { + _fullPath = fullPath; + } - protected override string RelationalUri => $"/api/m1/file/publish?access_token={_auth.AccessToken}"; + protected override string RelationalUri => $"/api/m1/file/publish?access_token={_auth.AccessToken}"; - protected override byte[] CreateHttpContent() - { - var data = $"home={Uri.EscapeDataString(_fullPath)}&email={_auth.Login}&x-email={_auth.Login}"; - return Encoding.UTF8.GetBytes(data); - } + protected override byte[] CreateHttpContent() + { + var data = $"home={Uri.EscapeDataString(_fullPath)}&email={_auth.Login}&x-email={_auth.Login}"; + return Encoding.UTF8.GetBytes(data); } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/RemoveRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/RemoveRequest.cs index 2ab7d369..337899bb 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/RemoveRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/RemoveRequest.cs @@ -3,26 +3,25 @@ using YaR.Clouds.Base.Requests; using YaR.Clouds.Base.Requests.Types; -namespace YaR.Clouds.Base.Repos.MailRuCloud.WebM1.Requests +namespace YaR.Clouds.Base.Repos.MailRuCloud.WebM1.Requests; + +class RemoveRequest : BaseRequestJson> { - class RemoveRequest : BaseRequestJson> - { - private readonly string _fullPath; + private readonly string _fullPath; - public RemoveRequest(HttpCommonSettings settings, IAuth auth, string fullPath) - : base(settings, auth) - { - _fullPath = fullPath; - } + public RemoveRequest(HttpCommonSettings settings, IAuth auth, string fullPath) + : base(settings, auth) + { + _fullPath = fullPath; + } - protected override string RelationalUri => $"/api/m1/file/remove?access_token={_auth.AccessToken}"; + protected override string RelationalUri => $"/api/m1/file/remove?access_token={_auth.AccessToken}"; - protected override byte[] CreateHttpContent() - { - // path sended using POST cause of unprintable Unicode charactes may exists - // https://github.com/yar229/WebDavMailRuCloud/issues/54 - string data = $"home={Uri.EscapeDataString(_fullPath)}"; - return Encoding.UTF8.GetBytes(data); - } + protected override byte[] CreateHttpContent() + { + // path sended using POST cause of unprintable Unicode charactes may exists + // https://github.com/yar229/WebDavMailRuCloud/issues/54 + string data = $"home={Uri.EscapeDataString(_fullPath)}"; + return Encoding.UTF8.GetBytes(data); } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/RenameRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/RenameRequest.cs index 819fa33d..33bc0c09 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/RenameRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/RenameRequest.cs @@ -3,26 +3,25 @@ using YaR.Clouds.Base.Requests; using YaR.Clouds.Base.Requests.Types; -namespace YaR.Clouds.Base.Repos.MailRuCloud.WebM1.Requests +namespace YaR.Clouds.Base.Repos.MailRuCloud.WebM1.Requests; + +class RenameRequest : BaseRequestJson> { - class RenameRequest : BaseRequestJson> - { - private readonly string _fullPath; - private readonly string _newName; + private readonly string _fullPath; + private readonly string _newName; - public RenameRequest(HttpCommonSettings settings, IAuth auth, string fullPath, string newName) - : base(settings, auth) - { - _fullPath = fullPath; - _newName = newName; - } + public RenameRequest(HttpCommonSettings settings, IAuth auth, string fullPath, string newName) + : base(settings, auth) + { + _fullPath = fullPath; + _newName = newName; + } - protected override string RelationalUri => $"/api/m1/file/rename?access_token={_auth.AccessToken}"; + protected override string RelationalUri => $"/api/m1/file/rename?access_token={_auth.AccessToken}"; - protected override byte[] CreateHttpContent() - { - var data = $"home={Uri.EscapeDataString(_fullPath)}&email={_auth.Login}&conflict=rename&name={Uri.EscapeDataString(_newName)}"; - return Encoding.UTF8.GetBytes(data); - } + protected override byte[] CreateHttpContent() + { + var data = $"home={Uri.EscapeDataString(_fullPath)}&email={_auth.Login}&conflict=rename&name={Uri.EscapeDataString(_newName)}"; + return Encoding.UTF8.GetBytes(data); } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/ShardInfoRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/ShardInfoRequest.cs index ddd78256..fde5e5c0 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/ShardInfoRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/ShardInfoRequest.cs @@ -1,23 +1,22 @@ using YaR.Clouds.Base.Requests; using YaR.Clouds.Base.Requests.Types; -namespace YaR.Clouds.Base.Repos.MailRuCloud.WebM1.Requests +namespace YaR.Clouds.Base.Repos.MailRuCloud.WebM1.Requests; + +class ShardInfoRequest : BaseRequestJson { - class ShardInfoRequest : BaseRequestJson + public ShardInfoRequest(HttpCommonSettings settings, IAuth auth) : base(settings, auth) { - public ShardInfoRequest(HttpCommonSettings settings, IAuth auth) : base(settings, auth) - { - } + } - protected override string RelationalUri + protected override string RelationalUri + { + get { - get - { - var uri = $"{_settings.BaseDomain}/api/m1/dispatcher?client_id={_settings.ClientId}"; - if (!string.IsNullOrEmpty(_auth.AccessToken)) - uri += $"&access_token={_auth.AccessToken}"; - return uri; - } + var uri = $"{_settings.BaseDomain}/api/m1/dispatcher?client_id={_settings.ClientId}"; + if (!string.IsNullOrEmpty(_auth.AccessToken)) + uri += $"&access_token={_auth.AccessToken}"; + return uri; } } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/SharedListRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/SharedListRequest.cs index 79a90d53..c28ef6e1 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/SharedListRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/SharedListRequest.cs @@ -1,23 +1,22 @@ using YaR.Clouds.Base.Requests; using YaR.Clouds.Base.Requests.Types; -namespace YaR.Clouds.Base.Repos.MailRuCloud.WebM1.Requests +namespace YaR.Clouds.Base.Repos.MailRuCloud.WebM1.Requests; + +class SharedListRequest : BaseRequestJson { - class SharedListRequest : BaseRequestJson - { - public SharedListRequest(HttpCommonSettings settings, IAuth auth) - : base(settings, auth) - { - } + public SharedListRequest(HttpCommonSettings settings, IAuth auth) + : base(settings, auth) + { + } - protected override string RelationalUri + protected override string RelationalUri + { + get { - get - { - var uri = $"/api/m1/folder/shared/links?access_token={_auth.AccessToken}"; - return uri; - } + var uri = $"/api/m1/folder/shared/links?access_token={_auth.AccessToken}"; + return uri; } } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/UnpublishRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/UnpublishRequest.cs index 4450e0ea..edb69ceb 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/UnpublishRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/Requests/UnpublishRequest.cs @@ -3,28 +3,27 @@ using YaR.Clouds.Base.Requests; using YaR.Clouds.Base.Requests.Types; -namespace YaR.Clouds.Base.Repos.MailRuCloud.WebM1.Requests +namespace YaR.Clouds.Base.Repos.MailRuCloud.WebM1.Requests; + +class UnpublishRequest : BaseRequestJson> { - class UnpublishRequest : BaseRequestJson> - { - private readonly string _publicLink; + private readonly string _publicLink; - public UnpublishRequest(IRequestRepo repo, HttpCommonSettings settings, IAuth auth, string publicLink) - : base(settings, auth) - { - _publicLink = publicLink; + public UnpublishRequest(IRequestRepo repo, HttpCommonSettings settings, IAuth auth, string publicLink) + : base(settings, auth) + { + _publicLink = publicLink; - if (repo.PublicBaseUrlDefault.Length > 0 && - _publicLink.StartsWith(repo.PublicBaseUrlDefault, StringComparison.InvariantCultureIgnoreCase)) - _publicLink = _publicLink.Remove(0, repo.PublicBaseUrlDefault.Length); - } + if (repo.PublicBaseUrlDefault.Length > 0 && + _publicLink.StartsWith(repo.PublicBaseUrlDefault, StringComparison.InvariantCultureIgnoreCase)) + _publicLink = _publicLink.Remove(0, repo.PublicBaseUrlDefault.Length); + } - protected override string RelationalUri => $"/api/m1/file/unpublish?access_token={_auth.AccessToken}"; + protected override string RelationalUri => $"/api/m1/file/unpublish?access_token={_auth.AccessToken}"; - protected override byte[] CreateHttpContent() - { - var data = $"weblink={Uri.EscapeDataString(_publicLink)}&email={_auth.Login}&x-email={_auth.Login}"; - return Encoding.UTF8.GetBytes(data); - } + protected override byte[] CreateHttpContent() + { + var data = $"weblink={Uri.EscapeDataString(_publicLink)}&email={_auth.Login}&x-email={_auth.Login}"; + return Encoding.UTF8.GetBytes(data); } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/WebM1RequestRepo.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/WebM1RequestRepo.cs index 521cbcac..b28aa4bf 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/WebM1RequestRepo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebM1/WebM1RequestRepo.cs @@ -16,377 +16,376 @@ using MoveRequest = YaR.Clouds.Base.Repos.MailRuCloud.Mobile.Requests.MoveRequest; using static YaR.Clouds.Cloud; -namespace YaR.Clouds.Base.Repos.MailRuCloud.WebM1 +namespace YaR.Clouds.Base.Repos.MailRuCloud.WebM1; + +/// +/// Part of WebM1 protocol. +/// Not usable. +/// +abstract class WebM1RequestRepo : MailRuBaseRepo, IRequestRepo { - /// - /// Part of WebM1 protocol. - /// Not usable. - /// - abstract class WebM1RequestRepo : MailRuBaseRepo, IRequestRepo - { - private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(WebM1RequestRepo)); - private readonly AuthCodeRequiredDelegate _onAuthCodeRequired; + private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(WebM1RequestRepo)); + private readonly AuthCodeRequiredDelegate _onAuthCodeRequired; - private readonly SemaphoreSlim _connectionLimiter; + private readonly SemaphoreSlim _connectionLimiter; - protected ShardManager ShardManager { get; private set; } + protected ShardManager ShardManager { get; private set; } - protected IRequestRepo AnonymousRepo => throw new NotImplementedException(); + protected IRequestRepo AnonymousRepo => throw new NotImplementedException(); - public sealed override HttpCommonSettings HttpSettings { get; } = new() - { - ClientId = "cloud-win", - UserAgent = "CloudDiskOWindows 17.12.0009 beta WzBbt1Ygbm", - BaseDomain = "https://cloud.mail.ru" - }; - - protected WebM1RequestRepo(CloudSettings settings, IWebProxy proxy, - IBasicCredentials credentials, AuthCodeRequiredDelegate onAuthCodeRequired) - : base(credentials) - { - ServicePointManager.DefaultConnectionLimit = int.MaxValue; + public sealed override HttpCommonSettings HttpSettings { get; } = new() + { + ClientId = "cloud-win", + UserAgent = "CloudDiskOWindows 17.12.0009 beta WzBbt1Ygbm", + BaseDomain = "https://cloud.mail.ru" + }; + + protected WebM1RequestRepo(CloudSettings settings, IWebProxy proxy, + IBasicCredentials credentials, AuthCodeRequiredDelegate onAuthCodeRequired) + : base(credentials) + { + ServicePointManager.DefaultConnectionLimit = int.MaxValue; - // required for Windows 7 breaking connection - ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13 | SecurityProtocolType.Tls12; + // required for Windows 7 breaking connection + ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13 | SecurityProtocolType.Tls12; - _connectionLimiter = new SemaphoreSlim(settings.MaxConnectionCount); - ShardManager = new ShardManager(_connectionLimiter, this); - HttpSettings.Proxy = proxy; - HttpSettings.CloudSettings = settings; - _onAuthCodeRequired = onAuthCodeRequired; + _connectionLimiter = new SemaphoreSlim(settings.MaxConnectionCount); + ShardManager = new ShardManager(_connectionLimiter, this); + HttpSettings.Proxy = proxy; + HttpSettings.CloudSettings = settings; + _onAuthCodeRequired = onAuthCodeRequired; - Auth = new OAuth(_connectionLimiter, HttpSettings, credentials, onAuthCodeRequired); + Auth = new OAuth(_connectionLimiter, HttpSettings, credentials, onAuthCodeRequired); - CachedSharedList = new Cached>>(_ => - { - var z = GetShareListInner().Result; + CachedSharedList = new Cached>>(_ => + { + var z = GetShareListInner().Result; - var res = z.Body.List - .ToDictionary( - fik => fik.Home, - fiv => Enumerable.Repeat(new PublicLinkInfo(PublicBaseUrlDefault + fiv.Weblink), 1) ); + var res = z.Body.List + .ToDictionary( + fik => fik.Home, + fiv => Enumerable.Repeat(new PublicLinkInfo(PublicBaseUrlDefault + fiv.Weblink), 1)); - return res; - }, - _ => TimeSpan.FromSeconds(30)); - } + return res; + }, + _ => TimeSpan.FromSeconds(30)); + } - public Stream GetDownloadStream(File file, long? start = null, long? end = null) - { - var istream = GetDownloadStreamInternal(file, start, end); - return istream; - } + public Stream GetDownloadStream(File file, long? start = null, long? end = null) + { + var istream = GetDownloadStreamInternal(file, start, end); + return istream; + } - private DownloadStream GetDownloadStreamInternal(File afile, long? start = null, long? end = null) - { - bool isLinked = !afile.PublicLinks.IsEmpty; + private DownloadStream GetDownloadStreamInternal(File afile, long? start = null, long? end = null) + { + bool isLinked = !afile.PublicLinks.IsEmpty; - Cached downServer = null; - var pendingServers = isLinked - ? ShardManager.WebLinkDownloadServersPending - : ShardManager.DownloadServersPending; - Stopwatch watch = new Stopwatch(); + Cached downServer = null; + var pendingServers = isLinked + ? ShardManager.WebLinkDownloadServersPending + : ShardManager.DownloadServersPending; + Stopwatch watch = new Stopwatch(); - HttpWebRequest request = null; - CustomDisposable ResponseGenerator(long instart, long inend, File file) + HttpWebRequest request = null; + CustomDisposable ResponseGenerator(long instart, long inend, File file) + { + var resp = Retry.Do(() => { - var resp = Retry.Do(() => - { - downServer = pendingServers.Next(downServer); + downServer = pendingServers.Next(downServer); - string url =(isLinked - ? $"{downServer.Value.Url}{WebDavPath.EscapeDataString(file.PublicLinks.Values.FirstOrDefault()?.Uri.PathAndQuery)}" - : $"{downServer.Value.Url}{Uri.EscapeDataString(file.FullPath.TrimStart('/'))}") + - $"?client_id={HttpSettings.ClientId}&token={Auth.AccessToken}"; - var uri = new Uri(url); + string url = (isLinked + ? $"{downServer.Value.Url}{WebDavPath.EscapeDataString(file.PublicLinks.Values.FirstOrDefault()?.Uri.PathAndQuery)}" + : $"{downServer.Value.Url}{Uri.EscapeDataString(file.FullPath.TrimStart('/'))}") + + $"?client_id={HttpSettings.ClientId}&token={Auth.AccessToken}"; + var uri = new Uri(url); #pragma warning disable SYSLIB0014 // Type or member is obsolete - request = (HttpWebRequest)WebRequest.Create(uri.OriginalString); + request = (HttpWebRequest)WebRequest.Create(uri.OriginalString); #pragma warning restore SYSLIB0014 // Type or member is obsolete - request.AddRange(instart, inend); - request.Proxy = HttpSettings.Proxy; - request.CookieContainer = Auth.Cookies; - request.Method = "GET"; - request.Accept = "*/*"; - request.UserAgent = HttpSettings.UserAgent; - request.Host = uri.Host; - request.AllowWriteStreamBuffering = false; + request.AddRange(instart, inend); + request.Proxy = HttpSettings.Proxy; + request.CookieContainer = Auth.Cookies; + request.Method = "GET"; + request.Accept = "*/*"; + request.UserAgent = HttpSettings.UserAgent; + request.Host = uri.Host; + request.AllowWriteStreamBuffering = false; - if (isLinked) - { - request.Headers.Add("Accept-Ranges", "bytes"); - request.ContentType = MediaTypeNames.Application.Octet; - request.Referer = $"{HttpSettings.BaseDomain}/home/{Uri.EscapeDataString(file.Path)}"; - request.Headers.Add("Origin", HttpSettings.BaseDomain); - } + if (isLinked) + { + request.Headers.Add("Accept-Ranges", "bytes"); + request.ContentType = MediaTypeNames.Application.Octet; + request.Referer = $"{HttpSettings.BaseDomain}/home/{Uri.EscapeDataString(file.Path)}"; + request.Headers.Add("Origin", HttpSettings.BaseDomain); + } - request.Timeout = 15 * 1000; - request.ReadWriteTimeout = 15 * 1000; + request.Timeout = 15 * 1000; + request.ReadWriteTimeout = 15 * 1000; - watch.Start(); - var response = (HttpWebResponse)request.GetResponse(); - return new CustomDisposable - { - Value = response, - OnDispose = () => - { - pendingServers.Free(downServer); - watch.Stop(); - Logger.Debug($"HTTP:{request.Method}:{request.RequestUri.AbsoluteUri} ({watch.Elapsed.Milliseconds} ms)"); - } - }; - }, - exception => - exception is WebException { Response: HttpWebResponse { StatusCode: HttpStatusCode.NotFound } }, - exception => + watch.Start(); + var response = (HttpWebResponse)request.GetResponse(); + return new CustomDisposable { - pendingServers.Free(downServer); - Logger.Warn($"Retrying HTTP:{request.Method}:{request.RequestUri.AbsoluteUri} on exception {exception.Message}"); - }, - TimeSpan.FromSeconds(1), 2); - - return resp; - } + Value = response, + OnDispose = () => + { + pendingServers.Free(downServer); + watch.Stop(); + Logger.Debug($"HTTP:{request.Method}:{request.RequestUri.AbsoluteUri} ({watch.Elapsed.Milliseconds} ms)"); + } + }; + }, + exception => + exception is WebException { Response: HttpWebResponse { StatusCode: HttpStatusCode.NotFound } }, + exception => + { + pendingServers.Free(downServer); + Logger.Warn($"Retrying HTTP:{request.Method}:{request.RequestUri.AbsoluteUri} on exception {exception.Message}"); + }, + TimeSpan.FromSeconds(1), 2); - var stream = new DownloadStream(ResponseGenerator, afile, start, end); - return stream; + return resp; } + var stream = new DownloadStream(ResponseGenerator, afile, start, end); + return stream; + } - //public HttpWebRequest UploadRequest(File file, UploadMultipartBoundary boundary) - //{ - // var shard = GetShardInfo(ShardType.Upload).Result; - // var url = new Uri($"{shard.Url}?client_id={HttpSettings.ClientId}&token={Authenticator.AccessToken}"); - - // var request = (HttpWebRequest)WebRequest.Create(url); - // request.Proxy = HttpSettings.Proxy; - // request.CookieContainer = Authenticator.Cookies; - // request.Method = "PUT"; - // request.ContentLength = file.OriginalSize; - // request.Accept = "*/*"; - // request.UserAgent = HttpSettings.UserAgent; - // request.AllowWriteStreamBuffering = false; - - // request.Timeout = 15 * 1000; - // request.ReadWriteTimeout = 15 * 1000; - // //request.ServicePoint.ConnectionLimit = int.MaxValue; - - // return request; - //} - - /// - /// Get shard info that to do post get request. Can be use for anonymous user. - /// - /// Shard type as numeric type. - /// Shard info. - public override async Task GetShardInfo(ShardType shardType) - { - bool refreshed = false; - for (int i = 0; i < 10; i++) - { - await Task.Delay(80 * i); - var ishards = await Task.Run(() => ShardManager.CachedShards.Value); - var ishard = ishards[shardType]; - var banned = ShardManager.BannedShards.Value; - if (banned.All(bsh => bsh.Url != ishard.Url)) - { - if (refreshed) Auth.ExpireDownloadToken(); - return ishard; - } - ShardManager.CachedShards.Expire(); - refreshed = true; - } - Logger.Error("Cannot get working shard."); + //public HttpWebRequest UploadRequest(File file, UploadMultipartBoundary boundary) + //{ + // var shard = GetShardInfo(ShardType.Upload).Result; + // var url = new Uri($"{shard.Url}?client_id={HttpSettings.ClientId}&token={Authenticator.AccessToken}"); - var shards = await Task.Run(() => ShardManager.CachedShards.Value); - var shard = shards[shardType]; - return shard; - } + // var request = (HttpWebRequest)WebRequest.Create(url); + // request.Proxy = HttpSettings.Proxy; + // request.CookieContainer = Authenticator.Cookies; + // request.Method = "PUT"; + // request.ContentLength = file.OriginalSize; + // request.Accept = "*/*"; + // request.UserAgent = HttpSettings.UserAgent; + // request.AllowWriteStreamBuffering = false; - public async Task CloneItem(string fromUrl, string toPath) - { - var req = await new CloneItemRequest(HttpSettings, Auth, fromUrl, toPath).MakeRequestAsync(_connectionLimiter); - var res = req.ToCloneItemResult(); - return res; - } + // request.Timeout = 15 * 1000; + // request.ReadWriteTimeout = 15 * 1000; + // //request.ServicePoint.ConnectionLimit = int.MaxValue; - public async Task Copy(string sourceFullPath, string destinationPath, ConflictResolver? conflictResolver = null) - { - var req = await new CopyRequest(HttpSettings, Auth, sourceFullPath, destinationPath, conflictResolver).MakeRequestAsync(_connectionLimiter); - var res = req.ToCopyResult(); - return res; - } + // return request; + //} - public async Task Move(string sourceFullPath, string destinationPath, ConflictResolver? conflictResolver = null) + /// + /// Get shard info that to do post get request. Can be use for anonymous user. + /// + /// Shard type as numeric type. + /// Shard info. + public override async Task GetShardInfo(ShardType shardType) + { + bool refreshed = false; + for (int i = 0; i < 10; i++) { - //var req = await new MoveRequest(HttpSettings, Authenticator, sourceFullPath, destinationPath).MakeRequestAsync(_connectionLimiter); - //var res = req.ToCopyResult(); - //return res; - - var req = await new MoveRequest(HttpSettings, Auth, ShardManager.MetaServer.Url, sourceFullPath, destinationPath) - .MakeRequestAsync(_connectionLimiter); + await Task.Delay(80 * i); + var ishards = await Task.Run(() => ShardManager.CachedShards.Value); + var ishard = ishards[shardType]; + var banned = ShardManager.BannedShards.Value; + if (banned.All(bsh => bsh.Url != ishard.Url)) + { + if (refreshed) Auth.ExpireDownloadToken(); + return ishard; + } + ShardManager.CachedShards.Expire(); + refreshed = true; + } - var res = req.ToCopyResult(WebDavPath.Name(destinationPath)); - return res; + Logger.Error("Cannot get working shard."); - } + var shards = await Task.Run(() => ShardManager.CachedShards.Value); + var shard = shards[shardType]; + return shard; + } - public async Task FolderInfo(RemotePath path, int offset = 0, int limit = int.MaxValue, int depth = 1) - { - if (Credentials.IsAnonymous) - return await AnonymousRepo.FolderInfo(path, offset, limit); + public async Task CloneItem(string fromUrl, string toPath) + { + var req = await new CloneItemRequest(HttpSettings, Auth, fromUrl, toPath).MakeRequestAsync(_connectionLimiter); + var res = req.ToCloneItemResult(); + return res; + } - if (!path.IsLink && depth > 1) - return await FolderInfo(path, depth); + public async Task Copy(string sourceFullPath, string destinationPath, ConflictResolver? conflictResolver = null) + { + var req = await new CopyRequest(HttpSettings, Auth, sourceFullPath, destinationPath, conflictResolver).MakeRequestAsync(_connectionLimiter); + var res = req.ToCopyResult(); + return res; + } - FolderInfoResult datares; - try - { - datares = await new FolderInfoRequest(HttpSettings, Auth, path, offset, limit) - .MakeRequestAsync(_connectionLimiter); - } - catch (WebException e) when (e.Response is HttpWebResponse { StatusCode: HttpStatusCode.NotFound }) - { - return null; - } + public async Task Move(string sourceFullPath, string destinationPath, ConflictResolver? conflictResolver = null) + { + //var req = await new MoveRequest(HttpSettings, Authenticator, sourceFullPath, destinationPath).MakeRequestAsync(_connectionLimiter); + //var res = req.ToCopyResult(); + //return res; - Cloud.ItemType itemType; + var req = await new MoveRequest(HttpSettings, Auth, ShardManager.MetaServer.Url, sourceFullPath, destinationPath) + .MakeRequestAsync(_connectionLimiter); - //TODO: subject to refact, bad-bad-bad - if (null == path.Link || path.Link.ItemType == Cloud.ItemType.Unknown) - itemType = datares.Body.Home == path.Path || - WebDavPath.PathEquals("/" + datares.Body.Weblink, path.Path) - ? Cloud.ItemType.Folder - : Cloud.ItemType.File; - else - itemType = path.Link.ItemType; + var res = req.ToCopyResult(WebDavPath.Name(destinationPath)); + return res; + } - var entry = itemType == Cloud.ItemType.File - ? (IEntry)datares.ToFile( - PublicBaseUrlDefault, - home: WebDavPath.Parent(path.Path ?? string.Empty), - ulink: path.Link, - fileName: path.Link == null ? WebDavPath.Name(path.Path) : path.Link.OriginalName, - nameReplacement: path.Link?.IsLinkedToFileSystem ?? true ? WebDavPath.Name(path.Path) : null ) - : (IEntry)datares.ToFolder(PublicBaseUrlDefault, path.Path, path.Link); + public async Task FolderInfo(RemotePath path, int offset = 0, int limit = int.MaxValue, int depth = 1) + { + if (Credentials.IsAnonymous) + return await AnonymousRepo.FolderInfo(path, offset, limit); - return entry; - } + if (!path.IsLink && depth > 1) + return await FolderInfo(path, depth); - public async Task ItemInfo(RemotePath path, int offset = 0, int limit = int.MaxValue) + FolderInfoResult datares; + try { - var req = await new ItemInfoRequest(HttpSettings, Auth, path, offset, limit).MakeRequestAsync(_connectionLimiter); - return req; + datares = await new FolderInfoRequest(HttpSettings, Auth, path, offset, limit) + .MakeRequestAsync(_connectionLimiter); } - - public async Task AccountInfo() + catch (WebException e) when (e.Response is HttpWebResponse { StatusCode: HttpStatusCode.NotFound }) { - var req = await new AccountInfoRequest(HttpSettings, Auth).MakeRequestAsync(_connectionLimiter); - var res = req.ToAccountInfo(); - return res; + return null; } - public async Task Publish(string fullPath) - { - var req = await new PublishRequest(HttpSettings, Auth, fullPath).MakeRequestAsync(_connectionLimiter); - var res = req.ToPublishResult(); + Cloud.ItemType itemType; - if (res.IsSuccess) - { - CachedSharedList.Value[fullPath] = new List {new(PublicBaseUrlDefault + res.Url)}; - } + //TODO: subject to refact, bad-bad-bad + if (null == path.Link || path.Link.ItemType == Cloud.ItemType.Unknown) + itemType = datares.Body.Home == path.Path || + WebDavPath.PathEquals("/" + datares.Body.Weblink, path.Path) + ? Cloud.ItemType.Folder + : Cloud.ItemType.File; + else + itemType = path.Link.ItemType; - return res; - } - public async Task Unpublish(Uri publicLink, string fullPath = null) - { - foreach (var item in CachedSharedList.Value - .Where(kvp => kvp.Value.Any(u => u.Uri.Equals(publicLink))).ToList()) - { - CachedSharedList.Value.Remove(item.Key); - } + var entry = itemType == Cloud.ItemType.File + ? (IEntry)datares.ToFile( + PublicBaseUrlDefault, + home: WebDavPath.Parent(path.Path ?? string.Empty), + ulink: path.Link, + fileName: path.Link == null ? WebDavPath.Name(path.Path) : path.Link.OriginalName, + nameReplacement: path.Link?.IsLinkedToFileSystem ?? true ? WebDavPath.Name(path.Path) : null) + : (IEntry)datares.ToFolder(PublicBaseUrlDefault, path.Path, path.Link); - var req = await new UnpublishRequest(this, HttpSettings, Auth, publicLink.OriginalString).MakeRequestAsync(_connectionLimiter); - var res = req.ToUnpublishResult(); - return res; - } + return entry; + } - public async Task Remove(string fullPath) - { - var req = await new RemoveRequest(HttpSettings, Auth, fullPath).MakeRequestAsync(_connectionLimiter); - var res = req.ToRemoveResult(); - return res; - } + public async Task ItemInfo(RemotePath path, int offset = 0, int limit = int.MaxValue) + { + var req = await new ItemInfoRequest(HttpSettings, Auth, path, offset, limit).MakeRequestAsync(_connectionLimiter); + return req; + } - public async Task Rename(string fullPath, string newName) - { - //var req = await new RenameRequest(HttpSettings, Authenticator, fullPath, newName).MakeRequestAsync(_connectionLimiter); - //var res = req.ToRenameResult(); - //return res; + public async Task AccountInfo() + { + var req = await new AccountInfoRequest(HttpSettings, Auth).MakeRequestAsync(_connectionLimiter); + var res = req.ToAccountInfo(); + return res; + } - string newFullPath = WebDavPath.Combine(WebDavPath.Parent(fullPath), newName); - var req = await new MoveRequest(HttpSettings, Auth, ShardManager.MetaServer.Url, fullPath, newFullPath) - .MakeRequestAsync(_connectionLimiter); + public async Task Publish(string fullPath) + { + var req = await new PublishRequest(HttpSettings, Auth, fullPath).MakeRequestAsync(_connectionLimiter); + var res = req.ToPublishResult(); - var res = req.ToRenameResult(); - return res; + if (res.IsSuccess) + { + CachedSharedList.Value[fullPath] = new List { new(PublicBaseUrlDefault + res.Url) }; } - public Dictionary GetShardInfo1() + return res; + } + + public async Task Unpublish(Uri publicLink, string fullPath = null) + { + foreach (var item in CachedSharedList.Value + .Where(kvp => kvp.Value.Any(u => u.Uri.Equals(publicLink))).ToList()) { - return Auth.IsAnonymous - ? new WebV2.Requests - .ShardInfoRequest(HttpSettings, Auth).MakeRequestAsync(_connectionLimiter).Result.ToShardInfo() - : new ShardInfoRequest(HttpSettings, Auth).MakeRequestAsync(_connectionLimiter).Result.ToShardInfo(); + CachedSharedList.Value.Remove(item.Key); } + var req = await new UnpublishRequest(this, HttpSettings, Auth, publicLink.OriginalString).MakeRequestAsync(_connectionLimiter); + var res = req.ToUnpublishResult(); + return res; + } - public Cached>> CachedSharedList { get; } + public async Task Remove(string fullPath) + { + var req = await new RemoveRequest(HttpSettings, Auth, fullPath).MakeRequestAsync(_connectionLimiter); + var res = req.ToRemoveResult(); + return res; + } - private async Task GetShareListInner() - { - var res = await new SharedListRequest(HttpSettings, Auth) - .MakeRequestAsync(_connectionLimiter); + public async Task Rename(string fullPath, string newName) + { + //var req = await new RenameRequest(HttpSettings, Authenticator, fullPath, newName).MakeRequestAsync(_connectionLimiter); + //var res = req.ToRenameResult(); + //return res; - return res; - } + string newFullPath = WebDavPath.Combine(WebDavPath.Parent(fullPath), newName); + var req = await new MoveRequest(HttpSettings, Auth, ShardManager.MetaServer.Url, fullPath, newFullPath) + .MakeRequestAsync(_connectionLimiter); - public IEnumerable GetShareLinks(string path) - { - if (!CachedSharedList.Value.TryGetValue(path, out var links)) - yield break; + var res = req.ToRenameResult(); + return res; + } - foreach (var link in links) - yield return link; - } + public Dictionary GetShardInfo1() + { + return Auth.IsAnonymous + ? new WebV2.Requests + .ShardInfoRequest(HttpSettings, Auth).MakeRequestAsync(_connectionLimiter).Result.ToShardInfo() + : new ShardInfoRequest(HttpSettings, Auth).MakeRequestAsync(_connectionLimiter).Result.ToShardInfo(); + } - public void CleanTrash() - { - throw new NotImplementedException(); - } - public async Task CreateFolder(string path) - { - //return (await new CreateFolderRequest(HttpSettings, Authenticator, path).MakeRequestAsync()) - // .ToCreateFolderResult(); + public Cached>> CachedSharedList { get; } - var folderReqest = await new CreateFolderRequest(HttpSettings, Auth, ShardManager.MetaServer.Url, path) - .MakeRequestAsync(_connectionLimiter); + private async Task GetShareListInner() + { + var res = await new SharedListRequest(HttpSettings, Auth) + .MakeRequestAsync(_connectionLimiter); - return folderReqest.ToCreateFolderResult(); - } + return res; + } - public Task AddFile(string fileFullPath, IFileHash fileHash, FileSize fileSize, DateTime dateTime, ConflictResolver? conflictResolver) - { - throw new NotImplementedException(); - } + public IEnumerable GetShareLinks(string path) + { + if (!CachedSharedList.Value.TryGetValue(path, out var links)) + yield break; + + foreach (var link in links) + yield return link; + } - public async Task DetectOutsideChanges() => await Task.FromResult(null); + public void CleanTrash() + { + throw new NotImplementedException(); } + + public async Task CreateFolder(string path) + { + //return (await new CreateFolderRequest(HttpSettings, Authenticator, path).MakeRequestAsync()) + // .ToCreateFolderResult(); + + var folderReqest = await new CreateFolderRequest(HttpSettings, Auth, ShardManager.MetaServer.Url, path) + .MakeRequestAsync(_connectionLimiter); + + return folderReqest.ToCreateFolderResult(); + } + + public Task AddFile(string fileFullPath, IFileHash fileHash, FileSize fileSize, DateTime dateTime, ConflictResolver? conflictResolver) + { + throw new NotImplementedException(); + } + + public async Task DetectOutsideChanges() => await Task.FromResult(null); } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/AccountInfoRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/AccountInfoRequest.cs index 3a8058dc..f3fbf4dd 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/AccountInfoRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/AccountInfoRequest.cs @@ -1,14 +1,13 @@ using YaR.Clouds.Base.Requests; using YaR.Clouds.Base.Requests.Types; -namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2.Requests +namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2.Requests; + +class AccountInfoRequest : BaseRequestJson { - class AccountInfoRequest : BaseRequestJson + public AccountInfoRequest(HttpCommonSettings settings, IAuth auth) : base(settings, auth) { - public AccountInfoRequest(HttpCommonSettings settings, IAuth auth) : base(settings, auth) - { - } - - protected override string RelationalUri => $"{_settings.BaseDomain}/api/v2/user?token={_auth.AccessToken}"; } + + protected override string RelationalUri => $"{_settings.BaseDomain}/api/v2/user?token={_auth.AccessToken}"; } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/AuthTokenRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/AuthTokenRequest.cs index 6d6f63db..896573ba 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/AuthTokenRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/AuthTokenRequest.cs @@ -1,21 +1,20 @@ using YaR.Clouds.Base.Requests; using YaR.Clouds.Base.Requests.Types; -namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2.Requests +namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2.Requests; + +class AuthTokenRequest : BaseRequestJson { - class AuthTokenRequest : BaseRequestJson + public AuthTokenRequest(HttpCommonSettings settings, IAuth auth) : base(settings, auth) { - public AuthTokenRequest(HttpCommonSettings settings, IAuth auth) : base(settings, auth) - { - } + } - protected override string RelationalUri + protected override string RelationalUri + { + get { - get - { - const string uri = "/api/v2/tokens/csrf"; - return uri; - } + const string uri = "/api/v2/tokens/csrf"; + return uri; } } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/CloneItemRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/CloneItemRequest.cs index 87b2d8ad..5f2e853d 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/CloneItemRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/CloneItemRequest.cs @@ -2,20 +2,19 @@ using YaR.Clouds.Base.Requests; using YaR.Clouds.Base.Requests.Types; -namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2.Requests -{ - class CloneItemRequest : BaseRequestJson> - { - private readonly string _fromUrl; - private readonly string _toPath; +namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2.Requests; - public CloneItemRequest(HttpCommonSettings settings, IAuth auth, string fromUrl, string toPath) - : base(settings, auth) - { - _fromUrl = fromUrl; - _toPath = toPath; - } +class CloneItemRequest : BaseRequestJson> +{ + private readonly string _fromUrl; + private readonly string _toPath; - protected override string RelationalUri => $"{_settings.BaseDomain}/api/v2/clone?conflict=rename&folder={Uri.EscapeDataString(_toPath)}&weblink={Uri.EscapeDataString(_fromUrl)}&token={_auth.AccessToken}"; + public CloneItemRequest(HttpCommonSettings settings, IAuth auth, string fromUrl, string toPath) + : base(settings, auth) + { + _fromUrl = fromUrl; + _toPath = toPath; } + + protected override string RelationalUri => $"{_settings.BaseDomain}/api/v2/clone?conflict=rename&folder={Uri.EscapeDataString(_toPath)}&weblink={Uri.EscapeDataString(_fromUrl)}&token={_auth.AccessToken}"; } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/CommonSettings.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/CommonSettings.cs index fc001f68..471aeee2 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/CommonSettings.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/CommonSettings.cs @@ -1,10 +1,9 @@ -namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2.Requests +namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2.Requests; + +internal static class CommonSettings { - internal static class CommonSettings - { - public const string Domain = "mail.ru"; - public const string AuthDomain = "https://auth.mail.ru"; + public const string Domain = "mail.ru"; + public const string AuthDomain = "https://auth.mail.ru"; - public const string DefaultAcceptType = "text / html,application / xhtml + xml,application / xml; q = 0.9,*/*;q=0.8"; - } + public const string DefaultAcceptType = "text / html,application / xhtml + xml,application / xml; q = 0.9,*/*;q=0.8"; } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/CopyRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/CopyRequest.cs index d958dfa7..fe099de0 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/CopyRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/CopyRequest.cs @@ -3,40 +3,39 @@ using YaR.Clouds.Base.Requests; using YaR.Clouds.Base.Requests.Types; -namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2.Requests +namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2.Requests; + +class CopyRequest : BaseRequestJson> { - class CopyRequest : BaseRequestJson> - { - private readonly string _sourceFullPath; - private readonly string _destinationPath; - private readonly ConflictResolver _conflictResolver; + private readonly string _sourceFullPath; + private readonly string _destinationPath; + private readonly ConflictResolver _conflictResolver; - /// - /// - /// - /// - /// - /// (without item name) - /// - /// - public CopyRequest(HttpCommonSettings settings, IAuth auth, string sourceFullPath, string destinationPath, ConflictResolver? conflictResolver = null) - : base(settings, auth) - { - _sourceFullPath = sourceFullPath; - _destinationPath = destinationPath; - _conflictResolver = conflictResolver ?? ConflictResolver.Rename; - } + /// + /// + /// + /// + /// + /// (without item name) + /// + /// + public CopyRequest(HttpCommonSettings settings, IAuth auth, string sourceFullPath, string destinationPath, ConflictResolver? conflictResolver = null) + : base(settings, auth) + { + _sourceFullPath = sourceFullPath; + _destinationPath = destinationPath; + _conflictResolver = conflictResolver ?? ConflictResolver.Rename; + } - protected override string RelationalUri => "/api/v2/file/copy"; + protected override string RelationalUri => "/api/v2/file/copy"; - protected override byte[] CreateHttpContent() - { - var data = Encoding.UTF8.GetBytes(string.Format("home={0}&api={1}&token={2}&email={3}&x-email={3}&conflict={4}&folder={5}", - Uri.EscapeDataString(_sourceFullPath), 2, _auth.AccessToken, _auth.Login, - _conflictResolver, - Uri.EscapeDataString(_destinationPath))); + protected override byte[] CreateHttpContent() + { + var data = Encoding.UTF8.GetBytes(string.Format("home={0}&api={1}&token={2}&email={3}&x-email={3}&conflict={4}&folder={5}", + Uri.EscapeDataString(_sourceFullPath), 2, _auth.AccessToken, _auth.Login, + _conflictResolver, + Uri.EscapeDataString(_destinationPath))); - return data; - } + return data; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/CreateFileRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/CreateFileRequest.cs index 09bf4d4d..3b81cab1 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/CreateFileRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/CreateFileRequest.cs @@ -3,32 +3,31 @@ using YaR.Clouds.Base.Requests; using YaR.Clouds.Base.Requests.Types; -namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2.Requests +namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2.Requests; + +class CreateFileRequest : BaseRequestJson> { - class CreateFileRequest : BaseRequestJson> - { - private readonly string _fullPath; - private readonly string _hash; - private readonly long _size; - private readonly ConflictResolver _conflictResolver; + private readonly string _fullPath; + private readonly string _hash; + private readonly long _size; + private readonly ConflictResolver _conflictResolver; - public CreateFileRequest(HttpCommonSettings settings, IAuth auth, string fullPath, string hash, long size, ConflictResolver? conflictResolver) - : base(settings, auth) - { - _fullPath = fullPath; - _hash = hash; - _size = size; - _conflictResolver = conflictResolver ?? ConflictResolver.Rename; - } + public CreateFileRequest(HttpCommonSettings settings, IAuth auth, string fullPath, string hash, long size, ConflictResolver? conflictResolver) + : base(settings, auth) + { + _fullPath = fullPath; + _hash = hash; + _size = size; + _conflictResolver = conflictResolver ?? ConflictResolver.Rename; + } - protected override string RelationalUri => "/api/v2/file/add"; + protected override string RelationalUri => "/api/v2/file/add"; - protected override byte[] CreateHttpContent() - { - string filePart = $"&hash={_hash}&size={_size}"; - string data = $"home={Uri.EscapeDataString(_fullPath)}&conflict={_conflictResolver}&api=2&token={_auth.AccessToken}" + filePart; + protected override byte[] CreateHttpContent() + { + string filePart = $"&hash={_hash}&size={_size}"; + string data = $"home={Uri.EscapeDataString(_fullPath)}&conflict={_conflictResolver}&api=2&token={_auth.AccessToken}" + filePart; - return Encoding.UTF8.GetBytes(data); - } + return Encoding.UTF8.GetBytes(data); } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/CreateFolderRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/CreateFolderRequest.cs index cdce99a3..486a8caa 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/CreateFolderRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/CreateFolderRequest.cs @@ -3,23 +3,22 @@ using YaR.Clouds.Base.Requests; using YaR.Clouds.Base.Requests.Types; -namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2.Requests +namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2.Requests; + +class CreateFolderRequest : BaseRequestJson> { - class CreateFolderRequest : BaseRequestJson> - { - private readonly string _fullPath; + private readonly string _fullPath; - public CreateFolderRequest(HttpCommonSettings settings, IAuth auth, string fullPath) : base(settings, auth) - { - _fullPath = fullPath; - } + public CreateFolderRequest(HttpCommonSettings settings, IAuth auth, string fullPath) : base(settings, auth) + { + _fullPath = fullPath; + } - protected override string RelationalUri => "/api/v2/folder/add"; + protected override string RelationalUri => "/api/v2/folder/add"; - protected override byte[] CreateHttpContent() - { - var data = $"home={Uri.EscapeDataString(_fullPath)}&conflict=rename&api={2}&token={_auth.AccessToken}"; - return Encoding.UTF8.GetBytes(data); - } + protected override byte[] CreateHttpContent() + { + var data = $"home={Uri.EscapeDataString(_fullPath)}&conflict=rename&api={2}&token={_auth.AccessToken}"; + return Encoding.UTF8.GetBytes(data); } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/DownloadRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/DownloadRequest.cs index 767e2d09..1b9204b4 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/DownloadRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/DownloadRequest.cs @@ -7,53 +7,52 @@ using YaR.Clouds.Base.Requests.Types; using YaR.Clouds.Common; -namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2.Requests +namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2.Requests; + +class DownloadRequest { - class DownloadRequest + public DownloadRequest(File file, long inStart, long inEnd, IAuth auth, HttpCommonSettings settings, Cached> shards) { - public DownloadRequest(File file, long inStart, long inEnd, IAuth auth, HttpCommonSettings settings, Cached> shards) - { - Request = CreateRequest(auth, settings.Proxy, file, inStart, inEnd, settings.UserAgent, shards); - } + Request = CreateRequest(auth, settings.Proxy, file, inStart, inEnd, settings.UserAgent, shards); + } - public HttpWebRequest Request { get; } + public HttpWebRequest Request { get; } - private static HttpWebRequest CreateRequest(IAuth auth, IWebProxy proxy, File file, long instart, long inend, string userAgent, Cached> shards) - { - bool isLinked = !file.PublicLinks.IsEmpty; + private static HttpWebRequest CreateRequest(IAuth auth, IWebProxy proxy, File file, long instart, long inend, string userAgent, Cached> shards) + { + bool isLinked = !file.PublicLinks.IsEmpty; - string downloadkey = isLinked - ? auth.DownloadToken - : auth.AccessToken; + string downloadkey = isLinked + ? auth.DownloadToken + : auth.AccessToken; - var shard = isLinked - ? shards.Value[ShardType.WeblinkGet] - : shards.Value[ShardType.Get]; + var shard = isLinked + ? shards.Value[ShardType.WeblinkGet] + : shards.Value[ShardType.Get]; - string url = !isLinked - ? $"{shard.Url}{Uri.EscapeDataString(file.FullPath)}" - : $"{shard.Url}{file.PublicLinks.Values.FirstOrDefault()?.Uri.PathAndQuery.Remove(0, "/public".Length) ?? string.Empty}?key={downloadkey}"; + string url = !isLinked + ? $"{shard.Url}{Uri.EscapeDataString(file.FullPath)}" + : $"{shard.Url}{file.PublicLinks.Values.FirstOrDefault()?.Uri.PathAndQuery.Remove(0, "/public".Length) ?? string.Empty}?key={downloadkey}"; #pragma warning disable SYSLIB0014 // Type or member is obsolete - var request = (HttpWebRequest)WebRequest.Create(url); + var request = (HttpWebRequest)WebRequest.Create(url); #pragma warning restore SYSLIB0014 // Type or member is obsolete - request.Headers.Add("Accept-Ranges", "bytes"); - request.AddRange(instart, inend); - request.Proxy = proxy; - request.CookieContainer = auth.Cookies; - request.Method = "GET"; - request.ContentType = MediaTypeNames.Application.Octet; - request.Accept = "*/*"; - request.UserAgent = userAgent; - request.AllowReadStreamBuffering = false; - - return request; - } - - public static implicit operator HttpWebRequest(DownloadRequest v) - { - return v.Request; - } + request.Headers.Add("Accept-Ranges", "bytes"); + request.AddRange(instart, inend); + request.Proxy = proxy; + request.CookieContainer = auth.Cookies; + request.Method = "GET"; + request.ContentType = MediaTypeNames.Application.Octet; + request.Accept = "*/*"; + request.UserAgent = userAgent; + request.AllowReadStreamBuffering = false; + + return request; + } + + public static implicit operator HttpWebRequest(DownloadRequest v) + { + return v.Request; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/DownloadTokenRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/DownloadTokenRequest.cs index 1d813a1d..1c01b07e 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/DownloadTokenRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/DownloadTokenRequest.cs @@ -1,56 +1,55 @@ using YaR.Clouds.Base.Requests; using YaR.Clouds.Base.Requests.Types; -namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2.Requests +namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2.Requests; + +class DownloadTokenRequest : BaseRequestJson { - class DownloadTokenRequest : BaseRequestJson + public DownloadTokenRequest(HttpCommonSettings settings, IAuth auth) : base(settings, auth) { - public DownloadTokenRequest(HttpCommonSettings settings, IAuth auth) : base(settings, auth) - { - } + } - protected override string RelationalUri + protected override string RelationalUri + { + get { - get - { - var uri = $"/api/v2/tokens/download?token={_auth.AccessToken}"; - return uri; - } + var uri = $"/api/v2/tokens/download?token={_auth.AccessToken}"; + return uri; } } +} - //class DownloadTokenHtmlRequest : BaseRequestString - //{ - // public DownloadTokenHtmlRequest(HttpCommonSettings settings, IAuth auth, string url) : base(settings, auth) - // { - // RelationalUri = url; - // } - - // protected override HttpWebRequest CreateRequest(string baseDomain = null) - // { - // var request = base.CreateRequest(CommonSettings.AuthDomain); - // request.Accept = CommonSettings.DefaultAcceptType; - // return request; - // } - - // protected override string RelationalUri { get; } - - // protected override RequestResponse DeserializeMessage(string responseText) - // { - // var m = Regex.Match(responseText, - // @"""tokens"":{""download""\s*:\s*""(?.*?)"""); - - // var msg = new RequestResponse - // { - // Ok = m.Success, - // Result = new DownloadTokenResult - // { - // Body = new DownloadTokenBody{Token = m.Groups["token"].Value } - // } - // }; - // return msg; - // } - //} -} +//class DownloadTokenHtmlRequest : BaseRequestString +//{ +// public DownloadTokenHtmlRequest(HttpCommonSettings settings, IAuth auth, string url) : base(settings, auth) +// { +// RelationalUri = url; +// } + +// protected override HttpWebRequest CreateRequest(string baseDomain = null) +// { +// var request = base.CreateRequest(CommonSettings.AuthDomain); +// request.Accept = CommonSettings.DefaultAcceptType; +// return request; +// } + +// protected override string RelationalUri { get; } + +// protected override RequestResponse DeserializeMessage(string responseText) +// { +// var m = Regex.Match(responseText, +// @"""tokens"":{""download""\s*:\s*""(?.*?)"""); + +// var msg = new RequestResponse +// { +// Ok = m.Success, +// Result = new DownloadTokenResult +// { +// Body = new DownloadTokenBody{Token = m.Groups["token"].Value } +// } +// }; +// return msg; +// } +//} diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/EnsureSdcCookieRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/EnsureSdcCookieRequest.cs index 8741151d..2fa89bcb 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/EnsureSdcCookieRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/EnsureSdcCookieRequest.cs @@ -1,22 +1,21 @@ using System.Net; using YaR.Clouds.Base.Requests; -namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2.Requests +namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2.Requests; + +class EnsureSdcCookieRequest : BaseRequestString { - class EnsureSdcCookieRequest : BaseRequestString + public EnsureSdcCookieRequest(HttpCommonSettings settings, IAuth auth) + : base(settings, auth) { - public EnsureSdcCookieRequest(HttpCommonSettings settings, IAuth auth) - : base(settings, auth) - { - } - - protected override HttpWebRequest CreateRequest(string baseDomain = null) - { - var request = base.CreateRequest(CommonSettings.AuthDomain); - request.Accept = CommonSettings.DefaultAcceptType; - return request; - } + } - protected override string RelationalUri => $"/sdc?from={_settings.BaseDomain}/home"; + protected override HttpWebRequest CreateRequest(string baseDomain = null) + { + var request = base.CreateRequest(CommonSettings.AuthDomain); + request.Accept = CommonSettings.DefaultAcceptType; + return request; } + + protected override string RelationalUri => $"/sdc?from={_settings.BaseDomain}/home"; } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/FolderInfoRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/FolderInfoRequest.cs index 5bc86a24..acf681b9 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/FolderInfoRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/FolderInfoRequest.cs @@ -2,45 +2,44 @@ using YaR.Clouds.Base.Requests; using YaR.Clouds.Base.Requests.Types; -namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2.Requests +namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2.Requests; + +internal class FolderInfoRequest : BaseRequestJson { - internal class FolderInfoRequest : BaseRequestJson + private readonly string _path; + private readonly bool _isWebLink; + private readonly int _offset; + private readonly int _limit; + + public FolderInfoRequest(HttpCommonSettings settings, IAuth auth, RemotePath path, int offset = 0, int limit = int.MaxValue) + : base(settings, auth) { - private readonly string _path; - private readonly bool _isWebLink; - private readonly int _offset; - private readonly int _limit; + _isWebLink = path.IsLink; - public FolderInfoRequest(HttpCommonSettings settings, IAuth auth, RemotePath path, int offset = 0, int limit = int.MaxValue) - : base(settings, auth) + if (path.IsLink) { - _isWebLink = path.IsLink; - - if (path.IsLink) - { - string ustr = path.Link.Href.OriginalString; - _path = "/" + ustr.Remove(0, ustr.IndexOf("/public/", StringComparison.Ordinal) + "/public/".Length); - } - else - _path = path.Path; - - _offset = offset; - _limit = limit; + string ustr = path.Link.Href.OriginalString; + _path = "/" + ustr.Remove(0, ustr.IndexOf("/public/", StringComparison.Ordinal) + "/public/".Length); } + else + _path = path.Path; - protected override string RelationalUri + _offset = offset; + _limit = limit; + } + + protected override string RelationalUri + { + get { - get - { - var uri = _isWebLink - ? $"/api/v2/folder?weblink={Uri.EscapeDataString(_path)}&offset={_offset}&limit={_limit}" - : $"/api/v2/folder?home={Uri.EscapeDataString(_path)}&offset={_offset}&limit={_limit}"; + var uri = _isWebLink + ? $"/api/v2/folder?weblink={Uri.EscapeDataString(_path)}&offset={_offset}&limit={_limit}" + : $"/api/v2/folder?home={Uri.EscapeDataString(_path)}&offset={_offset}&limit={_limit}"; - if (!_auth.IsAnonymous) - uri += $"&token={_auth.AccessToken}"; + if (!_auth.IsAnonymous) + uri += $"&token={_auth.AccessToken}"; - return uri; - } + return uri; } } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/ItemInfoRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/ItemInfoRequest.cs index 3daa253e..e42e8a57 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/ItemInfoRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/ItemInfoRequest.cs @@ -2,42 +2,41 @@ using YaR.Clouds.Base.Requests; using YaR.Clouds.Base.Requests.Types; -namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2.Requests +namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2.Requests; + +class ItemInfoRequest : BaseRequestJson { - class ItemInfoRequest : BaseRequestJson - { - private readonly string _path; - private readonly bool _isWebLink; - private readonly int _offset; - private readonly int _limit; + private readonly string _path; + private readonly bool _isWebLink; + private readonly int _offset; + private readonly int _limit; - public ItemInfoRequest(HttpCommonSettings settings, IAuth auth, RemotePath path, int offset = 0, int limit = int.MaxValue) - : base(settings, auth) - { - _isWebLink = path.IsLink; - - if (path.IsLink) - { - string ustr = path.Link.Href.OriginalString; - _path = "/" + ustr.Remove(0, ustr.IndexOf("/public/", StringComparison.Ordinal) + "/public/".Length); - } - else - _path = path.Path; + public ItemInfoRequest(HttpCommonSettings settings, IAuth auth, RemotePath path, int offset = 0, int limit = int.MaxValue) + : base(settings, auth) + { + _isWebLink = path.IsLink; - _offset = offset; - _limit = limit; + if (path.IsLink) + { + string ustr = path.Link.Href.OriginalString; + _path = "/" + ustr.Remove(0, ustr.IndexOf("/public/", StringComparison.Ordinal) + "/public/".Length); } + else + _path = path.Path; + + _offset = offset; + _limit = limit; + } - protected override string RelationalUri + protected override string RelationalUri + { + get { - get - { - var uri = _isWebLink - ? $"/api/v2/file?token={_auth.AccessToken}&weblink={Uri.EscapeDataString(_path)}&offset={_offset}&limit={_limit}" - : $"/api/v2/file?token={_auth.AccessToken}&home={Uri.EscapeDataString(_path)}&offset={_offset}&limit={_limit}"; - return uri; - } + var uri = _isWebLink + ? $"/api/v2/file?token={_auth.AccessToken}&weblink={Uri.EscapeDataString(_path)}&offset={_offset}&limit={_limit}" + : $"/api/v2/file?token={_auth.AccessToken}&home={Uri.EscapeDataString(_path)}&offset={_offset}&limit={_limit}"; + return uri; } } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/LoginRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/LoginRequest.cs index ae7d7dee..f66437a7 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/LoginRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/LoginRequest.cs @@ -6,48 +6,47 @@ using YaR.Clouds.Base.Requests; using YaR.Clouds.Base.Requests.Types; -namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2.Requests +namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2.Requests; + +class LoginRequest : BaseRequestString { - class LoginRequest : BaseRequestString + public LoginRequest(HttpCommonSettings settings, IAuth auth) + : base(settings, auth) { - public LoginRequest(HttpCommonSettings settings, IAuth auth) - : base(settings, auth) - { - } + } - protected override HttpWebRequest CreateRequest(string baseDomain = null) - { - var request = base.CreateRequest(CommonSettings.AuthDomain); - request.Accept = CommonSettings.DefaultAcceptType; - return request; - } + protected override HttpWebRequest CreateRequest(string baseDomain = null) + { + var request = base.CreateRequest(CommonSettings.AuthDomain); + request.Accept = CommonSettings.DefaultAcceptType; + return request; + } - protected override string RelationalUri => "/cgi-bin/auth"; + protected override string RelationalUri => "/cgi-bin/auth"; - protected override byte[] CreateHttpContent() - { + protected override byte[] CreateHttpContent() + { #pragma warning disable SYSLIB0013 // Type or member is obsolete - string data = $"Login={Uri.EscapeUriString(_auth.Login)}&Domain={CommonSettings.Domain}&Password={Uri.EscapeUriString(_auth.Password)}"; + string data = $"Login={Uri.EscapeUriString(_auth.Login)}&Domain={CommonSettings.Domain}&Password={Uri.EscapeUriString(_auth.Password)}"; #pragma warning restore SYSLIB0013 // Type or member is obsolete - return Encoding.UTF8.GetBytes(data); - } + return Encoding.UTF8.GetBytes(data); + } + + protected override RequestResponse DeserializeMessage(NameValueCollection responseHeaders, string responseText) + { + var csrf = responseText.Contains("csrf") + ? new string(responseText.Split(new[] { "csrf" }, StringSplitOptions.None)[1].Split(',')[0].Where(char.IsLetterOrDigit).ToArray()) + : string.Empty; - protected override RequestResponse DeserializeMessage(NameValueCollection responseHeaders, string responseText) + var msg = new RequestResponse { - var csrf = responseText.Contains("csrf") - ? new string(responseText.Split(new[] {"csrf"}, StringSplitOptions.None)[1].Split(',')[0].Where(char.IsLetterOrDigit).ToArray()) - : string.Empty; - - var msg = new RequestResponse + Ok = true, + Result = new LoginResult { - Ok = true, - Result = new LoginResult - { - Csrf = csrf - } - }; - return msg; - } + Csrf = csrf + } + }; + return msg; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/MoveRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/MoveRequest.cs index 0c2d900b..3fdd2f5b 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/MoveRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/MoveRequest.cs @@ -3,28 +3,27 @@ using YaR.Clouds.Base.Requests; using YaR.Clouds.Base.Requests.Types; -namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2.Requests +namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2.Requests; + +class MoveRequest : BaseRequestJson> { - class MoveRequest : BaseRequestJson> - { - private readonly string _sourceFullPath; - private readonly string _destinationPath; + private readonly string _sourceFullPath; + private readonly string _destinationPath; - public MoveRequest(HttpCommonSettings settings, IAuth auth, string sourceFullPath, string destinationPath) - : base(settings, auth) - { - _sourceFullPath = sourceFullPath; - _destinationPath = destinationPath; - } + public MoveRequest(HttpCommonSettings settings, IAuth auth, string sourceFullPath, string destinationPath) + : base(settings, auth) + { + _sourceFullPath = sourceFullPath; + _destinationPath = destinationPath; + } - protected override string RelationalUri => "/api/v2/file/move"; + protected override string RelationalUri => "/api/v2/file/move"; - protected override byte[] CreateHttpContent() - { - var data = Encoding.UTF8.GetBytes(string.Format("home={0}&api={1}&token={2}&email={3}&x-email={3}&conflict=rename&folder={4}", - Uri.EscapeDataString(_sourceFullPath), 2, _auth.AccessToken, _auth.Login, Uri.EscapeDataString(_destinationPath))); + protected override byte[] CreateHttpContent() + { + var data = Encoding.UTF8.GetBytes(string.Format("home={0}&api={1}&token={2}&email={3}&x-email={3}&conflict=rename&folder={4}", + Uri.EscapeDataString(_sourceFullPath), 2, _auth.AccessToken, _auth.Login, Uri.EscapeDataString(_destinationPath))); - return data; - } + return data; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/PublishRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/PublishRequest.cs index 1eff18a6..65f80a8b 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/PublishRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/PublishRequest.cs @@ -3,24 +3,23 @@ using YaR.Clouds.Base.Requests; using YaR.Clouds.Base.Requests.Types; -namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2.Requests +namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2.Requests; + +class PublishRequest : BaseRequestJson> { - class PublishRequest : BaseRequestJson> - { - private readonly string _fullPath; + private readonly string _fullPath; - public PublishRequest(HttpCommonSettings settings, IAuth auth, string fullPath) : base(settings, auth) - { - _fullPath = fullPath; - } + public PublishRequest(HttpCommonSettings settings, IAuth auth, string fullPath) : base(settings, auth) + { + _fullPath = fullPath; + } - protected override string RelationalUri => "/api/v2/file/publish"; + protected override string RelationalUri => "/api/v2/file/publish"; - protected override byte[] CreateHttpContent() - { - var data = string.Format("home={0}&api={1}&token={2}&email={3}&x-email={3}", Uri.EscapeDataString(_fullPath), - 2, _auth.AccessToken, _auth.Login); - return Encoding.UTF8.GetBytes(data); - } + protected override byte[] CreateHttpContent() + { + var data = string.Format("home={0}&api={1}&token={2}&email={3}&x-email={3}", Uri.EscapeDataString(_fullPath), + 2, _auth.AccessToken, _auth.Login); + return Encoding.UTF8.GetBytes(data); } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/RemoveRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/RemoveRequest.cs index 735835bb..6fd75f7e 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/RemoveRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/RemoveRequest.cs @@ -3,25 +3,23 @@ using YaR.Clouds.Base.Requests; using YaR.Clouds.Base.Requests.Types; -namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2.Requests +namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2.Requests; + +class RemoveRequest : BaseRequestJson> { + private readonly string _fullPath; - class RemoveRequest : BaseRequestJson> + public RemoveRequest(HttpCommonSettings settings, IAuth auth, string fullPath) : base(settings, auth) { - private readonly string _fullPath; - - public RemoveRequest(HttpCommonSettings settings, IAuth auth, string fullPath) : base(settings, auth) - { - _fullPath = fullPath; - } + _fullPath = fullPath; + } - protected override string RelationalUri => "/api/v2/file/remove"; + protected override string RelationalUri => "/api/v2/file/remove"; - protected override byte[] CreateHttpContent() - { - var data = string.Format("home={0}&api={1}&token={2}&email={3}&x-email={3}", Uri.EscapeDataString(_fullPath), - 2, _auth.AccessToken, _auth.Login); - return Encoding.UTF8.GetBytes(data); - } + protected override byte[] CreateHttpContent() + { + var data = string.Format("home={0}&api={1}&token={2}&email={3}&x-email={3}", Uri.EscapeDataString(_fullPath), + 2, _auth.AccessToken, _auth.Login); + return Encoding.UTF8.GetBytes(data); } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/RenameRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/RenameRequest.cs index f568a04f..f0df6529 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/RenameRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/RenameRequest.cs @@ -3,27 +3,25 @@ using YaR.Clouds.Base.Requests; using YaR.Clouds.Base.Requests.Types; -namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2.Requests +namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2.Requests; + +class RenameRequest : BaseRequestJson> { + private readonly string _fullPath; + private readonly string _newName; - class RenameRequest : BaseRequestJson> + public RenameRequest(HttpCommonSettings settings, IAuth auth, string fullPath, string newName) : base(settings, auth) { - private readonly string _fullPath; - private readonly string _newName; - - public RenameRequest(HttpCommonSettings settings, IAuth auth, string fullPath, string newName) : base(settings, auth) - { - _fullPath = fullPath; - _newName = newName; - } + _fullPath = fullPath; + _newName = newName; + } - protected override string RelationalUri => "/api/v2/file/rename"; + protected override string RelationalUri => "/api/v2/file/rename"; - protected override byte[] CreateHttpContent() - { - var data = string.Format("home={0}&api={1}&token={2}&email={3}&x-email={3}&conflict=rename&name={4}", Uri.EscapeDataString(_fullPath), - 2, _auth.AccessToken, _auth.Login, Uri.EscapeDataString(_newName)); - return Encoding.UTF8.GetBytes(data); - } + protected override byte[] CreateHttpContent() + { + var data = string.Format("home={0}&api={1}&token={2}&email={3}&x-email={3}&conflict=rename&name={4}", Uri.EscapeDataString(_fullPath), + 2, _auth.AccessToken, _auth.Login, Uri.EscapeDataString(_newName)); + return Encoding.UTF8.GetBytes(data); } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/SecondStepAuthRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/SecondStepAuthRequest.cs index 8337a9c9..b92c947a 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/SecondStepAuthRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/SecondStepAuthRequest.cs @@ -2,28 +2,27 @@ using System.Text; using YaR.Clouds.Base.Requests; -namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2.Requests +namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2.Requests; + +class SecondStepAuthRequest : BaseRequestString { - class SecondStepAuthRequest : BaseRequestString - { - private readonly string _csrf; - private readonly string _authCode; + private readonly string _csrf; + private readonly string _authCode; - public SecondStepAuthRequest(HttpCommonSettings settings, string csrf, string authCode) : base(settings, null) - { - _csrf = csrf; - _authCode = authCode; - } + public SecondStepAuthRequest(HttpCommonSettings settings, string csrf, string authCode) : base(settings, null) + { + _csrf = csrf; + _authCode = authCode; + } - protected override string RelationalUri => $"{CommonSettings.AuthDomain}/cgi-bin/secstep"; + protected override string RelationalUri => $"{CommonSettings.AuthDomain}/cgi-bin/secstep"; - protected override byte[] CreateHttpContent() - { + protected override byte[] CreateHttpContent() + { #pragma warning disable SYSLIB0013 // Type or member is obsolete - string data = $"csrf={_csrf}&Login={Uri.EscapeUriString(_auth.Login)}&AuthCode={_authCode}"; + string data = $"csrf={_csrf}&Login={Uri.EscapeUriString(_auth.Login)}&AuthCode={_authCode}"; #pragma warning restore SYSLIB0013 // Type or member is obsolete - return Encoding.UTF8.GetBytes(data); - } + return Encoding.UTF8.GetBytes(data); } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/ShardInfoRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/ShardInfoRequest.cs index b7e54724..56a773fc 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/ShardInfoRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/ShardInfoRequest.cs @@ -1,28 +1,27 @@ using YaR.Clouds.Base.Requests; using YaR.Clouds.Base.Requests.Types; -namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2.Requests +namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2.Requests; + +class ShardInfoRequest : BaseRequestJson { - class ShardInfoRequest : BaseRequestJson + public ShardInfoRequest(HttpCommonSettings settings, IAuth auth) + : base(settings, auth) { - public ShardInfoRequest(HttpCommonSettings settings, IAuth auth) - : base(settings, auth) - { - } + } - protected override string RelationalUri + protected override string RelationalUri + { + get { - get + var uri = $"{_settings.BaseDomain}/api/v2/dispatcher?client_id={_settings.ClientId}"; + if (!_auth.IsAnonymous) + uri += $"&access_token={_auth.AccessToken}"; + else { - var uri = $"{_settings.BaseDomain}/api/v2/dispatcher?client_id={_settings.ClientId}"; - if (!_auth.IsAnonymous) - uri += $"&access_token={_auth.AccessToken}"; - else - { - uri += "&email=anonym&x-email=anonym"; - } - return uri; - } + uri += "&email=anonym&x-email=anonym"; + } + return uri; } } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/UnpublishRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/UnpublishRequest.cs index aaf20d61..4da411b7 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/UnpublishRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/UnpublishRequest.cs @@ -3,24 +3,23 @@ using YaR.Clouds.Base.Requests; using YaR.Clouds.Base.Requests.Types; -namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2.Requests +namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2.Requests; + +class UnpublishRequest : BaseRequestJson> { - class UnpublishRequest : BaseRequestJson> - { - private readonly string _publicLink; + private readonly string _publicLink; - public UnpublishRequest(HttpCommonSettings settings, IAuth auth, string publicLink) : base(settings, auth) - { - _publicLink = publicLink; - } + public UnpublishRequest(HttpCommonSettings settings, IAuth auth, string publicLink) : base(settings, auth) + { + _publicLink = publicLink; + } - protected override string RelationalUri => "/api/v2/file/unpublish"; + protected override string RelationalUri => "/api/v2/file/unpublish"; - protected override byte[] CreateHttpContent() - { - var data = string.Format("weblink={0}&api={1}&token={2}&email={3}&x-email={3}", Uri.EscapeDataString(_publicLink), - 2, _auth.AccessToken, _auth.Login); - return Encoding.UTF8.GetBytes(data); - } + protected override byte[] CreateHttpContent() + { + var data = string.Format("weblink={0}&api={1}&token={2}&email={3}&x-email={3}", Uri.EscapeDataString(_publicLink), + 2, _auth.AccessToken, _auth.Login); + return Encoding.UTF8.GetBytes(data); } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/UploadRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/UploadRequest.cs index eecaa98d..505a2354 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/UploadRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/Requests/UploadRequest.cs @@ -3,41 +3,40 @@ using System.Runtime; using YaR.Clouds.Base.Requests; -namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2.Requests +namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2.Requests; + +class UploadRequest { - class UploadRequest + public UploadRequest(string shardUrl, File file, IAuth auth, HttpCommonSettings settings) { - public UploadRequest(string shardUrl, File file, IAuth auth, HttpCommonSettings settings) - { - Request = CreateRequest(shardUrl, auth, file, settings); - } + Request = CreateRequest(shardUrl, auth, file, settings); + } - public HttpWebRequest Request { get; } + public HttpWebRequest Request { get; } - private static HttpWebRequest CreateRequest(string shardUrl, IAuth auth, File file, HttpCommonSettings settings) - { - var url = new Uri($"{shardUrl}?cloud_domain=2&{auth.Login}"); + private static HttpWebRequest CreateRequest(string shardUrl, IAuth auth, File file, HttpCommonSettings settings) + { + var url = new Uri($"{shardUrl}?cloud_domain=2&{auth.Login}"); #pragma warning disable SYSLIB0014 // Type or member is obsolete - var request = (HttpWebRequest)WebRequest.Create(url.OriginalString); + var request = (HttpWebRequest)WebRequest.Create(url.OriginalString); #pragma warning restore SYSLIB0014 // Type or member is obsolete - request.Proxy = settings.Proxy; - request.CookieContainer = auth.Cookies; - request.Method = "PUT"; - request.ContentLength = file.OriginalSize; // + boundary.Start.LongLength + boundary.End.LongLength; - request.Referer = $"{settings.BaseDomain}/home/{Uri.EscapeDataString(file.Path)}"; - request.Headers.Add("Origin", settings.BaseDomain); - request.Host = url.Host; - //request.ContentType = $"multipart/form-data; boundary=----{boundary.Guid}"; - request.Accept = "*/*"; - request.UserAgent = settings.UserAgent; - request.AllowWriteStreamBuffering = false; - return request; - } + request.Proxy = settings.Proxy; + request.CookieContainer = auth.Cookies; + request.Method = "PUT"; + request.ContentLength = file.OriginalSize; // + boundary.Start.LongLength + boundary.End.LongLength; + request.Referer = $"{settings.BaseDomain}/home/{Uri.EscapeDataString(file.Path)}"; + request.Headers.Add("Origin", settings.BaseDomain); + request.Host = url.Host; + //request.ContentType = $"multipart/form-data; boundary=----{boundary.Guid}"; + request.Accept = "*/*"; + request.UserAgent = settings.UserAgent; + request.AllowWriteStreamBuffering = false; + return request; + } - public static implicit operator HttpWebRequest(UploadRequest v) - { - return v.Request; - } + public static implicit operator HttpWebRequest(UploadRequest v) + { + return v.Request; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/WebAuth.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/WebAuth.cs index b200c435..c22b9603 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/WebAuth.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/WebAuth.cs @@ -9,98 +9,97 @@ using YaR.Clouds.Common; using static YaR.Clouds.Cloud; -namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2 +namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2; + +class WebAuth : IAuth { - class WebAuth : IAuth - { - private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(WebAuth)); + private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(WebAuth)); - private readonly SemaphoreSlim _connectionLimiter; - public CookieContainer Cookies { get; } + private readonly SemaphoreSlim _connectionLimiter; + public CookieContainer Cookies { get; } - private readonly HttpCommonSettings _settings; - private readonly IBasicCredentials _creds; + private readonly HttpCommonSettings _settings; + private readonly IBasicCredentials _creds; - public WebAuth(SemaphoreSlim connectionLimiter, HttpCommonSettings settings, - IBasicCredentials credentials, AuthCodeRequiredDelegate onAuthCodeRequired) - { - _connectionLimiter = connectionLimiter; - _settings = settings; - _creds = credentials; - Cookies = new CookieContainer(); + public WebAuth(SemaphoreSlim connectionLimiter, HttpCommonSettings settings, + IBasicCredentials credentials, AuthCodeRequiredDelegate onAuthCodeRequired) + { + _connectionLimiter = connectionLimiter; + _settings = settings; + _creds = credentials; + Cookies = new CookieContainer(); - var logged = MakeLogin(connectionLimiter, onAuthCodeRequired).Result; - if (!logged) - throw new AuthenticationException($"Cannot log in {credentials.Login}"); + var logged = MakeLogin(connectionLimiter, onAuthCodeRequired).Result; + if (!logged) + throw new AuthenticationException($"Cannot log in {credentials.Login}"); - _authToken = new Cached(_ => - { - Logger.Debug("AuthToken expired, refreshing."); - if (credentials.IsAnonymous) - return null; + _authToken = new Cached(_ => + { + Logger.Debug("AuthToken expired, refreshing."); + if (credentials.IsAnonymous) + return null; - var token = Auth(connectionLimiter).Result; - return token; - }, - _ => TimeSpan.FromSeconds(AuthTokenExpiresInSec)); + var token = Auth(connectionLimiter).Result; + return token; + }, + _ => TimeSpan.FromSeconds(AuthTokenExpiresInSec)); - _cachedDownloadToken = - new Cached(_ => new DownloadTokenRequest(_settings, this) - .MakeRequestAsync(_connectionLimiter).Result.ToToken(), - _ => TimeSpan.FromSeconds(DownloadTokenExpiresSec)); + _cachedDownloadToken = + new Cached(_ => new DownloadTokenRequest(_settings, this) + .MakeRequestAsync(_connectionLimiter).Result.ToToken(), + _ => TimeSpan.FromSeconds(DownloadTokenExpiresSec)); - } + } + + public async Task MakeLogin(SemaphoreSlim connectionLimiter, AuthCodeRequiredDelegate onAuthCodeRequired) + { + var loginResult = await new LoginRequest(_settings, this) + .MakeRequestAsync(connectionLimiter); - public async Task MakeLogin(SemaphoreSlim connectionLimiter, AuthCodeRequiredDelegate onAuthCodeRequired) + // 2FA + if (!string.IsNullOrEmpty(loginResult.Csrf)) { - var loginResult = await new LoginRequest(_settings, this) + string authCode = onAuthCodeRequired(_creds.Login, false); + await new SecondStepAuthRequest(_settings, loginResult.Csrf, authCode) .MakeRequestAsync(connectionLimiter); + } - // 2FA - if (!string.IsNullOrEmpty(loginResult.Csrf)) - { - string authCode = onAuthCodeRequired(_creds.Login, false); - await new SecondStepAuthRequest(_settings, loginResult.Csrf, authCode) - .MakeRequestAsync(connectionLimiter); - } - - await new EnsureSdcCookieRequest(_settings, this) - .MakeRequestAsync(connectionLimiter); + await new EnsureSdcCookieRequest(_settings, this) + .MakeRequestAsync(connectionLimiter); - return true; - } + return true; + } - public async Task Auth(SemaphoreSlim connectionLimiter) - { - var req = await new AuthTokenRequest(_settings, this).MakeRequestAsync(connectionLimiter); - var res = req.ToAuthTokenResult(); - return res; - } + public async Task Auth(SemaphoreSlim connectionLimiter) + { + var req = await new AuthTokenRequest(_settings, this).MakeRequestAsync(connectionLimiter); + var res = req.ToAuthTokenResult(); + return res; + } - /// - /// Token for authorization - /// - private readonly Cached _authToken; - private const int AuthTokenExpiresInSec = 23 * 60 * 60; + /// + /// Token for authorization + /// + private readonly Cached _authToken; + private const int AuthTokenExpiresInSec = 23 * 60 * 60; - /// - /// Token for downloading files - /// - private readonly Cached _cachedDownloadToken; - private const int DownloadTokenExpiresSec = 20 * 60; + /// + /// Token for downloading files + /// + private readonly Cached _cachedDownloadToken; + private const int DownloadTokenExpiresSec = 20 * 60; - public bool IsAnonymous => _creds.IsAnonymous; - public string Login => _creds.Login; - public string Password => _creds.Password; + public bool IsAnonymous => _creds.IsAnonymous; + public string Login => _creds.Login; + public string Password => _creds.Password; - public string AccessToken => _authToken.Value?.Token; - public string DownloadToken => _cachedDownloadToken.Value; + public string AccessToken => _authToken.Value?.Token; + public string DownloadToken => _cachedDownloadToken.Value; - public void ExpireDownloadToken() - { - _cachedDownloadToken.Expire(); - } + public void ExpireDownloadToken() + { + _cachedDownloadToken.Expire(); } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/WebV2RequestRepo.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/WebV2RequestRepo.cs index 955fbc8a..edd0bf19 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/WebV2RequestRepo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebV2/WebV2RequestRepo.cs @@ -12,293 +12,292 @@ using YaR.Clouds.Common; using static YaR.Clouds.Cloud; -namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2 +namespace YaR.Clouds.Base.Repos.MailRuCloud.WebV2; + +class WebV2RequestRepo : MailRuBaseRepo, IRequestRepo { - class WebV2RequestRepo: MailRuBaseRepo, IRequestRepo - { - private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(WebV2RequestRepo)); + private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(WebV2RequestRepo)); - private readonly SemaphoreSlim _connectionLimiter; + private readonly SemaphoreSlim _connectionLimiter; - public sealed override HttpCommonSettings HttpSettings { get; } = new() - { - ClientId = string.Empty, - BaseDomain = "https://cloud.mail.ru" - }; + public sealed override HttpCommonSettings HttpSettings { get; } = new() + { + ClientId = string.Empty, + BaseDomain = "https://cloud.mail.ru" + }; - public WebV2RequestRepo(CloudSettings settings, IBasicCredentials credentials, AuthCodeRequiredDelegate onAuthCodeRequired) - : base(credentials) - { - ServicePointManager.DefaultConnectionLimit = int.MaxValue; + public WebV2RequestRepo(CloudSettings settings, IBasicCredentials credentials, AuthCodeRequiredDelegate onAuthCodeRequired) + : base(credentials) + { + ServicePointManager.DefaultConnectionLimit = int.MaxValue; - // required for Windows 7 breaking connection - ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13 | SecurityProtocolType.Tls12; + // required for Windows 7 breaking connection + ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls13 | SecurityProtocolType.Tls12; - _connectionLimiter = new SemaphoreSlim(settings.MaxConnectionCount); - HttpSettings.UserAgent = settings.UserAgent; - HttpSettings.CloudSettings = settings; - HttpSettings.Proxy = settings.Proxy; + _connectionLimiter = new SemaphoreSlim(settings.MaxConnectionCount); + HttpSettings.UserAgent = settings.UserAgent; + HttpSettings.CloudSettings = settings; + HttpSettings.Proxy = settings.Proxy; - Auth = new WebAuth(_connectionLimiter, HttpSettings, credentials, onAuthCodeRequired); + Auth = new WebAuth(_connectionLimiter, HttpSettings, credentials, onAuthCodeRequired); - _bannedShards = new Cached>(_ => [], _ => TimeSpan.FromMinutes(2)); + _bannedShards = new Cached>(_ => [], _ => TimeSpan.FromMinutes(2)); - _cachedShards = new Cached>( - _ => new ShardInfoRequest(HttpSettings, Auth).MakeRequestAsync(_connectionLimiter).Result.ToShardInfo(), - _ => TimeSpan.FromSeconds(ShardsExpiresInSec)); - } + _cachedShards = new Cached>( + _ => new ShardInfoRequest(HttpSettings, Auth).MakeRequestAsync(_connectionLimiter).Result.ToShardInfo(), + _ => TimeSpan.FromSeconds(ShardsExpiresInSec)); + } - private readonly Cached> _cachedShards; - private readonly Cached> _bannedShards; - private const int ShardsExpiresInSec = 30 * 60; + private readonly Cached> _cachedShards; + private readonly Cached> _bannedShards; + private const int ShardsExpiresInSec = 30 * 60; - //public HttpWebRequest UploadRequest(File file, UploadMultipartBoundary boundary) - //{ - // var shard = GetShardInfo(ShardType.Upload).Result; + //public HttpWebRequest UploadRequest(File file, UploadMultipartBoundary boundary) + //{ + // var shard = GetShardInfo(ShardType.Upload).Result; - // var url = new Uri($"{shard.Url}?cloud_domain=2&{Authenticator.Login}"); + // var url = new Uri($"{shard.Url}?cloud_domain=2&{Authenticator.Login}"); - // var result = new UploadRequest(url.OriginalString, file, Authenticator, HttpSettings); - // return result; - //} + // var result = new UploadRequest(url.OriginalString, file, Authenticator, HttpSettings); + // return result; + //} - public Stream GetDownloadStream(File file, long? start = null, long? end = null) + public Stream GetDownloadStream(File file, long? start = null, long? end = null) + { + + CustomDisposable ResponseGenerator(long instart, long inend, File file) { + HttpWebRequest request = new DownloadRequest(file, instart, inend, Auth, HttpSettings, _cachedShards); + var response = (HttpWebResponse)request.GetResponse(); - CustomDisposable ResponseGenerator(long instart, long inend, File file) + return new CustomDisposable { - HttpWebRequest request = new DownloadRequest(file, instart, inend, Auth, HttpSettings, _cachedShards); - var response = (HttpWebResponse)request.GetResponse(); - - return new CustomDisposable + Value = response, + OnDispose = () => { - Value = response, - OnDispose = () => - { - //_shardManager.DownloadServersPending.Free(downServer); - //watch.Stop(); - //Logger.Debug($"HTTP:{request.Method}:{request.RequestUri.AbsoluteUri} ({watch.Elapsed.Milliseconds} ms)"); - } - }; - } - - var stream = new DownloadStream(ResponseGenerator, file, start, end); - return stream; + //_shardManager.DownloadServersPending.Free(downServer); + //watch.Stop(); + //Logger.Debug($"HTTP:{request.Method}:{request.RequestUri.AbsoluteUri} ({watch.Elapsed.Milliseconds} ms)"); + } + }; } - //public HttpWebRequest DownloadRequest(long instart, long inend, File file, ShardInfo shard) - //{ - // string downloadkey = string.Empty; - // if (shard.Type == ShardType.WeblinkGet) - // downloadkey = Authenticator.DownloadToken; - - // string url = shard.Type == ShardType.Get - // ? $"{shard.Url}{Uri.EscapeDataString(file.FullPath)}" - // : $"{shard.Url}{new Uri(ConstSettings.PublishFileLink + file.PublicLink).PathAndQuery.Remove(0, "/public".Length)}?key={downloadkey}"; - - // var request = (HttpWebRequest)WebRequest.Create(url); - - // request.Headers.Add("Accept-Ranges", "bytes"); - // request.AddRange(instart, inend); - // request.Proxy = HttpSettings.Proxy; - // request.CookieContainer = Authenticator.Cookies; - // request.Method = "GET"; - // request.ContentType = MediaTypeNames.Application.Octet; - // request.Accept = "*/*"; - // request.UserAgent = HttpSettings.UserAgent; - // request.AllowReadStreamBuffering = false; - - // request.Timeout = 15 * 1000; - - // return request; - //} - - - //public void BanShardInfo(ShardInfo banShard) - //{ - // if (!_bannedShards.Value.Any(bsh => bsh.Type == banShard.Type && bsh.Url == banShard.Url)) - // { - // Logger.Warn($"Shard {banShard.Url} temporarily banned"); - // _bannedShards.Value.Add(banShard); - // } - //} - - - /// - /// Get shard info that to do post get request. Can be use for anonymous user. - /// - /// Shard type as numeric type. - /// Shard info. - public override async Task GetShardInfo(ShardType shardType) + var stream = new DownloadStream(ResponseGenerator, file, start, end); + return stream; + } + + //public HttpWebRequest DownloadRequest(long instart, long inend, File file, ShardInfo shard) + //{ + // string downloadkey = string.Empty; + // if (shard.Type == ShardType.WeblinkGet) + // downloadkey = Authenticator.DownloadToken; + + // string url = shard.Type == ShardType.Get + // ? $"{shard.Url}{Uri.EscapeDataString(file.FullPath)}" + // : $"{shard.Url}{new Uri(ConstSettings.PublishFileLink + file.PublicLink).PathAndQuery.Remove(0, "/public".Length)}?key={downloadkey}"; + + // var request = (HttpWebRequest)WebRequest.Create(url); + + // request.Headers.Add("Accept-Ranges", "bytes"); + // request.AddRange(instart, inend); + // request.Proxy = HttpSettings.Proxy; + // request.CookieContainer = Authenticator.Cookies; + // request.Method = "GET"; + // request.ContentType = MediaTypeNames.Application.Octet; + // request.Accept = "*/*"; + // request.UserAgent = HttpSettings.UserAgent; + // request.AllowReadStreamBuffering = false; + + // request.Timeout = 15 * 1000; + + // return request; + //} + + + //public void BanShardInfo(ShardInfo banShard) + //{ + // if (!_bannedShards.Value.Any(bsh => bsh.Type == banShard.Type && bsh.Url == banShard.Url)) + // { + // Logger.Warn($"Shard {banShard.Url} temporarily banned"); + // _bannedShards.Value.Add(banShard); + // } + //} + + + /// + /// Get shard info that to do post get request. Can be use for anonymous user. + /// + /// Shard type as numeric type. + /// Shard info. + public override async Task GetShardInfo(ShardType shardType) + { + bool refreshed = false; + for (int i = 0; i < 10; i++) { - bool refreshed = false; - for (int i = 0; i < 10; i++) + await Task.Delay(80 * i); + var ishards = await Task.Run(() => _cachedShards.Value); + var ishard = ishards[shardType]; + var banned = _bannedShards.Value; + if (banned.All(bsh => bsh.Url != ishard.Url)) { - await Task.Delay(80 * i); - var ishards = await Task.Run(() => _cachedShards.Value); - var ishard = ishards[shardType]; - var banned = _bannedShards.Value; - if (banned.All(bsh => bsh.Url != ishard.Url)) - { - if (refreshed) Auth.ExpireDownloadToken(); - return ishard; - } - _cachedShards.Expire(); - refreshed = true; + if (refreshed) Auth.ExpireDownloadToken(); + return ishard; } - - Logger.Error("Cannot get working shard."); - - var shards = await Task.Run(() => _cachedShards.Value); - var shard = shards[shardType]; - return shard; + _cachedShards.Expire(); + refreshed = true; } - public async Task CloneItem(string fromUrl, string toPath) - { - var req = await new CloneItemRequest(HttpSettings, Auth, fromUrl, toPath).MakeRequestAsync(_connectionLimiter); - var res = req.ToCloneItemResult(); - return res; - } + Logger.Error("Cannot get working shard."); - public async Task Copy(string sourceFullPath, string destinationPath, ConflictResolver? conflictResolver = null) - { - var req = await new CopyRequest(HttpSettings, Auth, sourceFullPath, destinationPath, conflictResolver).MakeRequestAsync(_connectionLimiter); - var res = req.ToCopyResult(); - return res; - } + var shards = await Task.Run(() => _cachedShards.Value); + var shard = shards[shardType]; + return shard; + } - public async Task Move(string sourceFullPath, string destinationPath, ConflictResolver? conflictResolver = null) - { - var req = await new MoveRequest(HttpSettings, Auth, sourceFullPath, destinationPath).MakeRequestAsync(_connectionLimiter); - var res = req.ToCopyResult(); - return res; - } + public async Task CloneItem(string fromUrl, string toPath) + { + var req = await new CloneItemRequest(HttpSettings, Auth, fromUrl, toPath).MakeRequestAsync(_connectionLimiter); + var res = req.ToCloneItemResult(); + return res; + } - /// - /// - /// - /// - /// - /// - /// Not applicable here, always = 1 - /// - public async Task FolderInfo(RemotePath path, int offset = 0, int limit = int.MaxValue, int depth = 1) - { + public async Task Copy(string sourceFullPath, string destinationPath, ConflictResolver? conflictResolver = null) + { + var req = await new CopyRequest(HttpSettings, Auth, sourceFullPath, destinationPath, conflictResolver).MakeRequestAsync(_connectionLimiter); + var res = req.ToCopyResult(); + return res; + } - FolderInfoResult dataRes; - try - { - dataRes = await new FolderInfoRequest(HttpSettings, Auth, path, offset, limit) - .MakeRequestAsync(_connectionLimiter); - } - catch (WebException e) when (e.Response is HttpWebResponse { StatusCode: HttpStatusCode.NotFound }) - { - return null; - } + public async Task Move(string sourceFullPath, string destinationPath, ConflictResolver? conflictResolver = null) + { + var req = await new MoveRequest(HttpSettings, Auth, sourceFullPath, destinationPath).MakeRequestAsync(_connectionLimiter); + var res = req.ToCopyResult(); + return res; + } - Cloud.ItemType itemType; - if (null == path.Link || path.Link.ItemType == Cloud.ItemType.Unknown) - itemType = dataRes.Body.Home == path.Path || - WebDavPath.PathEquals("/" + dataRes.Body.Weblink, path.Path) - //datares.body.list.Any(fi => "/" + fi.weblink == path) - ? Cloud.ItemType.Folder - : Cloud.ItemType.File; - else - itemType = path.Link.ItemType; - - - var entry = itemType == Cloud.ItemType.File - ? (IEntry)dataRes.ToFile( - PublicBaseUrlDefault, - home: WebDavPath.Parent(path.Path ?? string.Empty), - ulink: path.Link, - fileName: path.Link == null ? WebDavPath.Name(path.Path) : path.Link.OriginalName, - nameReplacement: path.Link?.IsLinkedToFileSystem ?? true ? WebDavPath.Name(path.Path) : null) - : (IEntry)dataRes.ToFolder(PublicBaseUrlDefault, path.Path, path.Link); - - return entry; - } + /// + /// + /// + /// + /// + /// + /// Not applicable here, always = 1 + /// + public async Task FolderInfo(RemotePath path, int offset = 0, int limit = int.MaxValue, int depth = 1) + { - public async Task ItemInfo(RemotePath path, int offset = 0, int limit = int.MaxValue) + FolderInfoResult dataRes; + try { - var req = await new ItemInfoRequest(HttpSettings, Auth, path, offset, limit) + dataRes = await new FolderInfoRequest(HttpSettings, Auth, path, offset, limit) .MakeRequestAsync(_connectionLimiter); - return req; } - - - public async Task AccountInfo() + catch (WebException e) when (e.Response is HttpWebResponse { StatusCode: HttpStatusCode.NotFound }) { - var req = await new AccountInfoRequest(HttpSettings, Auth).MakeRequestAsync(_connectionLimiter); - var res = req.ToAccountInfo(); - return res; + return null; } - public async Task Publish(string fullPath) - { - var req = await new PublishRequest(HttpSettings, Auth, fullPath).MakeRequestAsync(_connectionLimiter); - var res = req.ToPublishResult(); - return res; - } + Cloud.ItemType itemType; + if (null == path.Link || path.Link.ItemType == Cloud.ItemType.Unknown) + itemType = dataRes.Body.Home == path.Path || + WebDavPath.PathEquals("/" + dataRes.Body.Weblink, path.Path) + //datares.body.list.Any(fi => "/" + fi.weblink == path) + ? Cloud.ItemType.Folder + : Cloud.ItemType.File; + else + itemType = path.Link.ItemType; + + + var entry = itemType == Cloud.ItemType.File + ? (IEntry)dataRes.ToFile( + PublicBaseUrlDefault, + home: WebDavPath.Parent(path.Path ?? string.Empty), + ulink: path.Link, + fileName: path.Link == null ? WebDavPath.Name(path.Path) : path.Link.OriginalName, + nameReplacement: path.Link?.IsLinkedToFileSystem ?? true ? WebDavPath.Name(path.Path) : null) + : (IEntry)dataRes.ToFolder(PublicBaseUrlDefault, path.Path, path.Link); + + return entry; + } - public async Task Unpublish(Uri publicLink, string fullPath = null) - { - var req = await new UnpublishRequest(HttpSettings, Auth, publicLink.OriginalString).MakeRequestAsync(_connectionLimiter); - var res = req.ToUnpublishResult(); - return res; - } + public async Task ItemInfo(RemotePath path, int offset = 0, int limit = int.MaxValue) + { + var req = await new ItemInfoRequest(HttpSettings, Auth, path, offset, limit) + .MakeRequestAsync(_connectionLimiter); + return req; + } - public async Task Remove(string fullPath) - { - var req = await new RemoveRequest(HttpSettings, Auth, fullPath).MakeRequestAsync(_connectionLimiter); - var res = req.ToRemoveResult(); - return res; - } - public async Task Rename(string fullPath, string newName) - { - var req = await new RenameRequest(HttpSettings, Auth, fullPath, newName).MakeRequestAsync(_connectionLimiter); - var res = req.ToRenameResult(); - return res; - } + public async Task AccountInfo() + { + var req = await new AccountInfoRequest(HttpSettings, Auth).MakeRequestAsync(_connectionLimiter); + var res = req.ToAccountInfo(); + return res; + } - public Dictionary GetShardInfo1() - { - return new ShardInfoRequest(HttpSettings, Auth).MakeRequestAsync(_connectionLimiter).Result.ToShardInfo(); - } + public async Task Publish(string fullPath) + { + var req = await new PublishRequest(HttpSettings, Auth, fullPath).MakeRequestAsync(_connectionLimiter); + var res = req.ToPublishResult(); + return res; + } - public IEnumerable GetShareLinks(string fullPath) - { - throw new NotImplementedException("WebV2 GetShareLink not implemented"); - } + public async Task Unpublish(Uri publicLink, string fullPath = null) + { + var req = await new UnpublishRequest(HttpSettings, Auth, publicLink.OriginalString).MakeRequestAsync(_connectionLimiter); + var res = req.ToUnpublishResult(); + return res; + } - public void CleanTrash() - { - throw new NotImplementedException(); - } + public async Task Remove(string fullPath) + { + var req = await new RemoveRequest(HttpSettings, Auth, fullPath).MakeRequestAsync(_connectionLimiter); + var res = req.ToRemoveResult(); + return res; + } - public async Task CreateFolder(string path) - { - return (await new CreateFolderRequest(HttpSettings, Auth, path).MakeRequestAsync(_connectionLimiter)) - .ToCreateFolderResult(); - } + public async Task Rename(string fullPath, string newName) + { + var req = await new RenameRequest(HttpSettings, Auth, fullPath, newName).MakeRequestAsync(_connectionLimiter); + var res = req.ToRenameResult(); + return res; + } - public async Task AddFile(string fileFullPath, IFileHash fileHash, FileSize fileSize, DateTime dateTime, ConflictResolver? conflictResolver) - { - var hash = fileHash.Hash.Value; + public Dictionary GetShardInfo1() + { + return new ShardInfoRequest(HttpSettings, Auth).MakeRequestAsync(_connectionLimiter).Result.ToShardInfo(); + } - var res = await new CreateFileRequest(HttpSettings, Auth, fileFullPath, hash, fileSize, conflictResolver) - .MakeRequestAsync(_connectionLimiter); + public IEnumerable GetShareLinks(string fullPath) + { + throw new NotImplementedException("WebV2 GetShareLink not implemented"); + } - return res.ToAddFileResult(); - } + public void CleanTrash() + { + throw new NotImplementedException(); + } - public async Task DetectOutsideChanges() => await Task.FromResult(null); + public async Task CreateFolder(string path) + { + return (await new CreateFolderRequest(HttpSettings, Auth, path).MakeRequestAsync(_connectionLimiter)) + .ToCreateFolderResult(); } + + public async Task AddFile(string fileFullPath, IFileHash fileHash, FileSize fileSize, DateTime dateTime, ConflictResolver? conflictResolver) + { + var hash = fileHash.Hash.Value; + + var res = await new CreateFileRequest(HttpSettings, Auth, fileFullPath, hash, fileSize, conflictResolver) + .MakeRequestAsync(_connectionLimiter); + + return res.ToAddFileResult(); + } + + public async Task DetectOutsideChanges() => await Task.FromResult(null); } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/RemotePath.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/RemotePath.cs index edf164a1..78a6c9af 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/RemotePath.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/RemotePath.cs @@ -1,30 +1,30 @@ using System.Threading.Tasks; using YaR.Clouds.Links; -namespace YaR.Clouds.Base.Repos +namespace YaR.Clouds.Base.Repos; + +public class RemotePath { - public class RemotePath + private RemotePath() { - private RemotePath() - {} - - public static RemotePath Get(string path) => new() {Path = path}; + } - public static RemotePath Get(Link link) => new() {Link = link}; + public static RemotePath Get(string path) => new() { Path = path }; - public static async Task Get(string path, LinkManager lm) - { - var z = new RemotePath {Path = path}; - if (lm == null) - return z; + public static RemotePath Get(Link link) => new() { Link = link }; - z.Link = await lm.GetItemLink(path); + public static async Task Get(string path, LinkManager lm) + { + var z = new RemotePath { Path = path }; + if (lm == null) return z; - } - public string Path { get; private set; } - public Link Link { get; private set;} - - public bool IsLink => Link != null; + z.Link = await lm.GetItemLink(path); + return z; } + + public string Path { get; private set; } + public Link Link { get; private set; } + + public bool IsLink => Link != null; } diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/RepoFabric.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/RepoFabric.cs index 6a60e270..59988d1e 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/RepoFabric.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/RepoFabric.cs @@ -2,42 +2,41 @@ using YaR.Clouds.Base.Repos.MailRuCloud.WebBin; using YaR.Clouds.Base.Repos.MailRuCloud.WebV2; -namespace YaR.Clouds.Base.Repos +namespace YaR.Clouds.Base.Repos; + +public class RepoFabric { - public class RepoFabric - { - private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(RepoFabric)); + private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(RepoFabric)); - private readonly CloudSettings _settings; - private readonly Credentials _credentials; + private readonly CloudSettings _settings; + private readonly Credentials _credentials; + + public RepoFabric(CloudSettings settings, Credentials credentials) + { + _settings = settings; + _credentials = credentials; + } - public RepoFabric(CloudSettings settings, Credentials credentials) + public IRequestRepo Create() + { + string TwoFaHandler(string login, bool isAutoRelogin) { - _settings = settings; - _credentials = credentials; + Logger.Info($"Waiting 2FA code for {login}"); + var code = _settings.TwoFaHandler?.Get(login, isAutoRelogin); + Logger.Info($"Got 2FA code for {login}"); + return code; } - public IRequestRepo Create() + IRequestRepo repo = _credentials.Protocol switch { - string TwoFaHandler(string login, bool isAutoRelogin) - { - Logger.Info($"Waiting 2FA code for {login}"); - var code = _settings.TwoFaHandler?.Get(login, isAutoRelogin); - Logger.Info($"Got 2FA code for {login}"); - return code; - } + Protocol.YadWeb => _credentials.AuthenticationUsingBrowser + ? new YandexDisk.YadWeb.YadWebRequestRepo2(_settings, _settings.Proxy, _credentials) + : new YandexDisk.YadWeb.YadWebRequestRepo(_settings, _settings.Proxy, _credentials), + Protocol.WebM1Bin => new WebBinRequestRepo(_settings, _credentials, TwoFaHandler), + Protocol.WebV2 => new WebV2RequestRepo(_settings, _credentials, TwoFaHandler), + _ => throw new Exception("Unknown protocol") + }; - IRequestRepo repo = _credentials.Protocol switch - { - Protocol.YadWeb => _credentials.AuthenticationUsingBrowser - ? new YandexDisk.YadWeb.YadWebRequestRepo2(_settings, _settings.Proxy, _credentials) - : new YandexDisk.YadWeb.YadWebRequestRepo(_settings, _settings.Proxy, _credentials), - Protocol.WebM1Bin => new WebBinRequestRepo(_settings, _credentials, TwoFaHandler), - Protocol.WebV2 => new WebV2RequestRepo(_settings, _credentials, TwoFaHandler), - _ => throw new Exception("Unknown protocol") - }; - - return repo; - } + return repo; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs index cecf5d9e..e97d3e0a 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequest.cs @@ -8,291 +8,290 @@ using System.Threading.Tasks; using YaR.Clouds.Base.Repos; -namespace YaR.Clouds.Base.Requests +namespace YaR.Clouds.Base.Requests; + +internal abstract class BaseRequest where T : class { - internal abstract class BaseRequest where T : class - { - private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(BaseRequest)); + private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(BaseRequest)); - protected readonly HttpCommonSettings _settings; - protected readonly IAuth _auth; + protected readonly HttpCommonSettings _settings; + protected readonly IAuth _auth; - protected BaseRequest(HttpCommonSettings settings, IAuth auth) - { - _settings = settings; - _auth = auth; - } + protected BaseRequest(HttpCommonSettings settings, IAuth auth) + { + _settings = settings; + _auth = auth; + } - protected abstract string RelationalUri { get; } + protected abstract string RelationalUri { get; } - protected virtual HttpWebRequest CreateRequest(string baseDomain) - { - string domain = baseDomain; - var uriz = new Uri(new Uri(domain), RelationalUri); + protected virtual HttpWebRequest CreateRequest(string baseDomain) + { + string domain = baseDomain; + var uriz = new Uri(new Uri(domain), RelationalUri); - // suppressing escaping is obsolete and breaks, for example, Chinese names - // url generated for %E2%80%8E and %E2%80%8F seems OK, but mail.ru replies error - // https://stackoverflow.com/questions/20211496/uri-ignore-special-characters - //var udriz = new Uri(new Uri(domain), RelationalUri, true); + // suppressing escaping is obsolete and breaks, for example, Chinese names + // url generated for %E2%80%8E and %E2%80%8F seems OK, but mail.ru replies error + // https://stackoverflow.com/questions/20211496/uri-ignore-special-characters + //var udriz = new Uri(new Uri(domain), RelationalUri, true); #pragma warning disable SYSLIB0014 // Type or member is obsolete - var request = WebRequest.CreateHttp(uriz); + var request = WebRequest.CreateHttp(uriz); #pragma warning restore SYSLIB0014 // Type or member is obsolete - request.Host = uriz.Host; - request.Headers.Add("Origin", $"{uriz.Scheme}://{uriz.Host}"); - request.Referer = $"{uriz.Scheme}://{uriz.Host}"; - request.Headers.Add("sec-ch-ua", _settings.CloudSettings.SecChUa); - request.Proxy = _settings.Proxy; - request.CookieContainer = _auth?.Cookies; - request.Method = "GET"; - // 21.09.2024 Заглушка для проверки, надо переделать - //request.ContentType = "application/x-www-form-urlencoded; charset=UTF-8"; - request.ContentType = RelationalUri.Contains("/models-v2?") - ? "application/json; charset=UTF-8" - : "application/x-www-form-urlencoded; charset=UTF-8"; - request.Accept = "application/json"; - request.UserAgent = _settings.UserAgent; - request.ContinueTimeout = _settings.CloudSettings.Wait100ContinueTimeoutSec * 1000; - request.Timeout = _settings.CloudSettings.WaitResponseTimeoutSec * 1000; - request.ReadWriteTimeout = _settings.CloudSettings.ReadWriteTimeoutSec * 1000; - /* - * NET 4.8: When performing a write operation with AllowWriteStreamBuffering set to false, - * you must either set ContentLength to a non-negative number or set SendChunked to true. - */ - //request.AllowWriteStreamBuffering = false; - /* - * NET 4.8: This operation is not supported. - */ - //request.AllowReadStreamBuffering = true; - request.SendChunked = false; - request.ServicePoint.Expect100Continue = false; - request.KeepAlive = true; + request.Host = uriz.Host; + request.Headers.Add("Origin", $"{uriz.Scheme}://{uriz.Host}"); + request.Referer = $"{uriz.Scheme}://{uriz.Host}"; + request.Headers.Add("sec-ch-ua", _settings.CloudSettings.SecChUa); + request.Proxy = _settings.Proxy; + request.CookieContainer = _auth?.Cookies; + request.Method = "GET"; + // 21.09.2024 Заглушка для проверки, надо переделать + //request.ContentType = "application/x-www-form-urlencoded; charset=UTF-8"; + request.ContentType = RelationalUri.Contains("/models-v2?") + ? "application/json; charset=UTF-8" + : "application/x-www-form-urlencoded; charset=UTF-8"; + request.Accept = "application/json"; + request.UserAgent = _settings.UserAgent; + request.ContinueTimeout = _settings.CloudSettings.Wait100ContinueTimeoutSec * 1000; + request.Timeout = _settings.CloudSettings.WaitResponseTimeoutSec * 1000; + request.ReadWriteTimeout = _settings.CloudSettings.ReadWriteTimeoutSec * 1000; + /* + * NET 4.8: When performing a write operation with AllowWriteStreamBuffering set to false, + * you must either set ContentLength to a non-negative number or set SendChunked to true. + */ + //request.AllowWriteStreamBuffering = false; + /* + * NET 4.8: This operation is not supported. + */ + //request.AllowReadStreamBuffering = true; + request.SendChunked = false; + request.ServicePoint.Expect100Continue = false; + request.KeepAlive = true; #if NET48 - request.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip; + request.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip; #else - request.AutomaticDecompression = DecompressionMethods.All; + request.AutomaticDecompression = DecompressionMethods.All; #endif - //request.AllowReadStreamBuffering = true; + //request.AllowReadStreamBuffering = true; - return request; - } + return request; + } - protected virtual byte[] CreateHttpContent() - { - return null; - } + protected virtual byte[] CreateHttpContent() + { + return null; + } - private const int MaxRetryCount = 10; + private const int MaxRetryCount = 10; - public virtual async Task MakeRequestAsync(SemaphoreSlim serverMaxConnectionLimiter) - { - /* - * С одной стороны есть желание максимально ускорить работу за счет параллельных - * вычислений и обращений к серверу. - * А с другой стороны, сервер может начать ругаться на слишком большое количество - * одновременных обращений. - * Можно экспериментальным путем подобрать близкое к максимальному количеству - * число допустимых одновременных обращений, а затем указать данное число - * в параметре maxconnections. На основе этого параметра создается ограничивающий - * максимальное количество обращений к серверу семафор, - * который передается в параметре метода. - */ + public virtual async Task MakeRequestAsync(SemaphoreSlim serverMaxConnectionLimiter) + { + /* + * С одной стороны есть желание максимально ускорить работу за счет параллельных + * вычислений и обращений к серверу. + * А с другой стороны, сервер может начать ругаться на слишком большое количество + * одновременных обращений. + * Можно экспериментальным путем подобрать близкое к максимальному количеству + * число допустимых одновременных обращений, а затем указать данное число + * в параметре maxconnections. На основе этого параметра создается ограничивающий + * максимальное количество обращений к серверу семафор, + * который передается в параметре метода. + */ - /* - * По всей видимости, при нескольких последовательных обращениях к серверу - * инфраструктура .NET повторно использует подключение после его закрытие по Close. - * По этой причине может получиться так, что поток, который предназначен для чтения - * данных с сервера, читает до того, как данные отправлены на сервер (как вариант), - * или (как вариант) до того, как сервер начал отправку данных клиенту. - * В таком случае поток читает 0 байт и выдает ошибку - * throw new HttpIOException(HttpRequestError.ResponseEnded, SR.net_http_invalid_response_premature_eof); - * см. код сборки .NET здесь: - * https://github.com/dotnet/runtime/blob/139f45e56b85b7e643d7e4f81cb5cdf640cd9021/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnection.cs#L625 - * Судя по сообщениям в интернете, такое поведение многим не нравится, но ситуация не исправляется. - * Учитывая, что рядом с исключением устанавливается _canRetry = true, - * можно предположить, что предполагается повторение обращения к серверу, - * что мы тут и сделаем. - */ + /* + * По всей видимости, при нескольких последовательных обращениях к серверу + * инфраструктура .NET повторно использует подключение после его закрытие по Close. + * По этой причине может получиться так, что поток, который предназначен для чтения + * данных с сервера, читает до того, как данные отправлены на сервер (как вариант), + * или (как вариант) до того, как сервер начал отправку данных клиенту. + * В таком случае поток читает 0 байт и выдает ошибку + * throw new HttpIOException(HttpRequestError.ResponseEnded, SR.net_http_invalid_response_premature_eof); + * см. код сборки .NET здесь: + * https://github.com/dotnet/runtime/blob/139f45e56b85b7e643d7e4f81cb5cdf640cd9021/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/HttpConnection.cs#L625 + * Судя по сообщениям в интернете, такое поведение многим не нравится, но ситуация не исправляется. + * Учитывая, что рядом с исключением устанавливается _canRetry = true, + * можно предположить, что предполагается повторение обращения к серверу, + * что мы тут и сделаем. + */ - await serverMaxConnectionLimiter.WaitAsync(); - try + await serverMaxConnectionLimiter.WaitAsync(); + try + { + Stopwatch totalWatch = Stopwatch.StartNew(); + int retry = MaxRetryCount; + while (true) { - Stopwatch totalWatch = Stopwatch.StartNew(); - int retry = MaxRetryCount; - while (true) + retry--; + bool isRetryState = false; + Stopwatch watch = Stopwatch.StartNew(); + HttpWebRequest httpRequest = null; + + try { - retry--; - bool isRetryState = false; - Stopwatch watch = Stopwatch.StartNew(); - HttpWebRequest httpRequest = null; + httpRequest = CreateRequest(_settings.BaseDomain); - try + var requestContent = CreateHttpContent(); + if (requestContent != null) { - httpRequest = CreateRequest(_settings.BaseDomain); - - var requestContent = CreateHttpContent(); - if (requestContent != null) - { - httpRequest.Method = "POST"; - using Stream requestStream = await httpRequest.GetRequestStreamAsync().ConfigureAwait(false); + httpRequest.Method = "POST"; + using Stream requestStream = await httpRequest.GetRequestStreamAsync().ConfigureAwait(false); - /* - * The debug add the following to a watch list: - * System.Text.Encoding.UTF8.GetString(requestContent) - */ + /* + * The debug add the following to a watch list: + * System.Text.Encoding.UTF8.GetString(requestContent) + */ #if NET48 - await requestStream.WriteAsync(requestContent, 0, requestContent.Length).ConfigureAwait(false); + await requestStream.WriteAsync(requestContent, 0, requestContent.Length).ConfigureAwait(false); #else - await requestStream.WriteAsync(requestContent).ConfigureAwait(false); + await requestStream.WriteAsync(requestContent).ConfigureAwait(false); #endif - await requestStream.FlushAsync().ConfigureAwait(false); - requestStream.Close(); - } + await requestStream.FlushAsync().ConfigureAwait(false); + requestStream.Close(); + } - /* - * Здесь в методе GetResponseAsync() иногда происходит исключение - * throw new HttpIOException(HttpRequestError.ResponseEnded, SR.net_http_invalid_response_premature_eof); - * Мы его отлавливаем и повторяем обращение к серверу. - */ - using var response = (HttpWebResponse)await httpRequest.GetResponseAsync().ConfigureAwait(false); + /* + * Здесь в методе GetResponseAsync() иногда происходит исключение + * throw new HttpIOException(HttpRequestError.ResponseEnded, SR.net_http_invalid_response_premature_eof); + * Мы его отлавливаем и повторяем обращение к серверу. + */ + using var response = (HttpWebResponse)await httpRequest.GetResponseAsync().ConfigureAwait(false); - if ((int)response.StatusCode >= 500) + if ((int)response.StatusCode >= 500) + { + throw new RequestException("Server fault") { - throw new RequestException("Server fault") - { - StatusCode = response.StatusCode - }; - } + StatusCode = response.StatusCode + }; + } - RequestResponse result; - using (var responseStream = response.GetResponseStream()) - { - result = DeserializeMessage(response.Headers, Transport(responseStream)); - responseStream.Close(); - } + RequestResponse result; + using (var responseStream = response.GetResponseStream()) + { + result = DeserializeMessage(response.Headers, Transport(responseStream)); + responseStream.Close(); + } - if (!result.Ok || response.StatusCode != HttpStatusCode.OK) + if (!result.Ok || response.StatusCode != HttpStatusCode.OK) + { + Logger.Debug($"Original request: {System.Text.Encoding.UTF8.GetString(requestContent)}"); + var exceptionMessage = + $"Request failed (status code {(int)response.StatusCode}): {result.Description}"; + throw new RequestException(exceptionMessage) { - Logger.Debug($"Original request: {System.Text.Encoding.UTF8.GetString(requestContent)}"); - var exceptionMessage = - $"Request failed (status code {(int)response.StatusCode}): {result.Description}"; - throw new RequestException(exceptionMessage) - { - StatusCode = response.StatusCode, - ResponseBody = string.Empty, - Description = result.Description, - ErrorCode = result.ErrorCode - }; - } + StatusCode = response.StatusCode, + ResponseBody = string.Empty, + Description = result.Description, + ErrorCode = result.ErrorCode + }; + } - response.Close(); + response.Close(); - return result.Result; - } - catch (WebException iex2) when (iex2?.InnerException is System.Net.Http.HttpRequestException iex1 && - iex1?.InnerException is IOException iex) + return result.Result; + } + catch (WebException iex2) when (iex2?.InnerException is System.Net.Http.HttpRequestException iex1 && + iex1?.InnerException is IOException iex) + { + /* + * Здесь мы ловим ошибку + * throw new HttpIOException(HttpRequestError.ResponseEnded, SR.net_http_invalid_response_premature_eof), + * которая здесь выглядит следующим образом: + * System.AggregateException: One or more errors occurred. (An error occurred while sending the request.) + * ---> System.Net.WebException: An error occurred while sending the request. + * ---> System.Net.Http.HttpRequestException: An error occurred while sending the request. + * ---> System.IO.IOException: The response ended prematurely. + * at System.Net.Http.HttpConnection.SendAsyncCore(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken) + * --- End of inner exception stack trace --- + */ + if (retry <= 0) { - /* - * Здесь мы ловим ошибку - * throw new HttpIOException(HttpRequestError.ResponseEnded, SR.net_http_invalid_response_premature_eof), - * которая здесь выглядит следующим образом: - * System.AggregateException: One or more errors occurred. (An error occurred while sending the request.) - * ---> System.Net.WebException: An error occurred while sending the request. - * ---> System.Net.Http.HttpRequestException: An error occurred while sending the request. - * ---> System.IO.IOException: The response ended prematurely. - * at System.Net.Http.HttpConnection.SendAsyncCore(HttpRequestMessage request, Boolean async, CancellationToken cancellationToken) - * --- End of inner exception stack trace --- - */ - if (retry <= 0) - { - string msg = "The response ended prematurely, retry count completed"; + string msg = "The response ended prematurely, retry count completed"; #if DEBUG - Logger.Warn(msg); + Logger.Warn(msg); #else - Logger.Debug(msg); + Logger.Debug(msg); #endif - throw; - } - else - { - isRetryState = true; - string msg = "The response ended prematurely, retrying..."; + throw; + } + else + { + isRetryState = true; + string msg = "The response ended prematurely, retrying..."; #if DEBUG - Logger.Warn(msg); + Logger.Warn(msg); #else - Logger.Debug(msg); + Logger.Debug(msg); #endif - } } - catch (WebException iex2) when (iex2?.InnerException is System.Net.Http.HttpRequestException iex1 && - iex1?.InnerException is SocketException iex) + } + catch (WebException iex2) when (iex2?.InnerException is System.Net.Http.HttpRequestException iex1 && + iex1?.InnerException is SocketException iex) + { + /* + * Здесь мы ловим ошибку + * SocketException: Попытка установить соединение была безуспешной, + * т.к. от другого компьютера за требуемое время не получен нужный отклик, + * или было разорвано уже установленное соединение из-за неверного отклика + * уже подключенного компьютера. + * + * Возможно превышено максимальное количество подключений к серверу. + * Просто повторяем запрос после небольшого ожидания. + */ + if (retry <= 0) { - /* - * Здесь мы ловим ошибку - * SocketException: Попытка установить соединение была безуспешной, - * т.к. от другого компьютера за требуемое время не получен нужный отклик, - * или было разорвано уже установленное соединение из-за неверного отклика - * уже подключенного компьютера. - * - * Возможно превышено максимальное количество подключений к серверу. - * Просто повторяем запрос после небольшого ожидания. - */ - if (retry <= 0) - { - string msg = "Can not connect to server. Increase timeout in response-timeout-sec parameter."; + string msg = "Can not connect to server. Increase timeout in response-timeout-sec parameter."; #if DEBUG - Logger.Warn(msg); + Logger.Warn(msg); #else - Logger.Debug(msg); + Logger.Debug(msg); #endif - throw; - } - else - { - isRetryState = true; - string msg = watch.ElapsedMilliseconds < 1000 - ? "Connection to server terminated immediately, retrying after couple of seconds..." - : $"Connection lost after {watch.ElapsedMilliseconds / 1000} seconds, retrying after couple of seconds..."; + throw; + } + else + { + isRetryState = true; + string msg = watch.ElapsedMilliseconds < 1000 + ? "Connection to server terminated immediately, retrying after couple of seconds..." + : $"Connection lost after {watch.ElapsedMilliseconds / 1000} seconds, retrying after couple of seconds..."; #if DEBUG - Logger.Warn(msg); + Logger.Warn(msg); #else - Logger.Debug(msg); + Logger.Debug(msg); #endif - Thread.Sleep(TimeSpan.FromSeconds(2)); - } + Thread.Sleep(TimeSpan.FromSeconds(2)); } - // ReSharper disable once RedundantCatchClause + } + // ReSharper disable once RedundantCatchClause #pragma warning disable 168 - catch (Exception ex) + catch (Exception ex) #pragma warning restore 168 + { + throw; + } + finally + { + watch.Stop(); + string totalText = null; + if (!isRetryState && retry < MaxRetryCount - 1) { - throw; - } - finally - { - watch.Stop(); - string totalText = null; - if (!isRetryState && retry < MaxRetryCount - 1) - { - totalWatch.Stop(); - totalText = $"({totalWatch.Elapsed.Milliseconds} ms of {MaxRetryCount - retry} retry laps)"; - } - Logger.Debug($"HTTP:{httpRequest.Method}:{httpRequest.RequestUri.AbsoluteUri} " + - $"({watch.Elapsed.Milliseconds} ms){(isRetryState ? ", retrying" : totalText)}"); + totalWatch.Stop(); + totalText = $"({totalWatch.Elapsed.Milliseconds} ms of {MaxRetryCount - retry} retry laps)"; } + Logger.Debug($"HTTP:{httpRequest.Method}:{httpRequest.RequestUri.AbsoluteUri} " + + $"({watch.Elapsed.Milliseconds} ms){(isRetryState ? ", retrying" : totalText)}"); } } - finally - { - serverMaxConnectionLimiter.Release(); - } } + finally + { + serverMaxConnectionLimiter.Release(); + } + } - protected abstract TConvert Transport(Stream stream); + protected abstract TConvert Transport(Stream stream); - protected abstract RequestResponse DeserializeMessage(NameValueCollection responseHeaders, TConvert data); - } + protected abstract RequestResponse DeserializeMessage(NameValueCollection responseHeaders, TConvert data); } diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequestJson.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequestJson.cs index 53a9e23f..5a01ffa3 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequestJson.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequestJson.cs @@ -3,46 +3,45 @@ using Newtonsoft.Json; using YaR.Clouds.Base.Repos; -namespace YaR.Clouds.Base.Requests +namespace YaR.Clouds.Base.Requests; + +internal abstract class BaseRequestJson : BaseRequest where T : class { - internal abstract class BaseRequestJson : BaseRequest where T : class + protected BaseRequestJson(HttpCommonSettings settings, IAuth auth) : base(settings, auth) { - protected BaseRequestJson(HttpCommonSettings settings, IAuth auth) : base(settings, auth) - { - } + } - protected override Stream Transport(Stream stream) - { - return stream; - } + protected override Stream Transport(Stream stream) + { + return stream; + } - protected override RequestResponse DeserializeMessage(NameValueCollection responseHeaders, Stream stream) - { + protected override RequestResponse DeserializeMessage(NameValueCollection responseHeaders, Stream stream) + { #if DEBUG - using (var sr = new StreamReader(stream)) - { - string text = sr.ReadToEnd(); - - var msg = new RequestResponse - { - Ok = true, - Result = JsonConvert.DeserializeObject(text) - }; - return msg; - - } -#else - var serializer = new JsonSerializer(); - using var sr = new StreamReader(stream); - using var jsonTextReader = new JsonTextReader(sr); + using (var sr = new StreamReader(stream)) + { + string text = sr.ReadToEnd(); var msg = new RequestResponse { Ok = true, - Result = serializer.Deserialize(jsonTextReader) + Result = JsonConvert.DeserializeObject(text) }; return msg; -#endif + } +#else + var serializer = new JsonSerializer(); + using var sr = new StreamReader(stream); + using var jsonTextReader = new JsonTextReader(sr); + + var msg = new RequestResponse + { + Ok = true, + Result = serializer.Deserialize(jsonTextReader) + }; + return msg; +#endif } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequestString.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequestString.cs index 211b8151..4436c52f 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequestString.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequestString.cs @@ -1,23 +1,22 @@ using System.Collections.Specialized; using YaR.Clouds.Base.Repos; -namespace YaR.Clouds.Base.Requests +namespace YaR.Clouds.Base.Requests; + +internal abstract class BaseRequestString : BaseRequestString { - internal abstract class BaseRequestString : BaseRequestString + protected BaseRequestString(HttpCommonSettings settings, IAuth auth) + : base(settings, auth) { - protected BaseRequestString(HttpCommonSettings settings, IAuth auth) - : base(settings, auth) - { - } + } - protected override RequestResponse DeserializeMessage(NameValueCollection responseHeaders, string data) + protected override RequestResponse DeserializeMessage(NameValueCollection responseHeaders, string data) + { + var msg = new RequestResponse { - var msg = new RequestResponse - { - Ok = true, - Result = data - }; - return msg; - } + Ok = true, + Result = data + }; + return msg; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequestStringT.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequestStringT.cs index 303db7d0..86709643 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequestStringT.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/BaseRequestStringT.cs @@ -1,18 +1,17 @@ using System.IO; using YaR.Clouds.Base.Repos; -namespace YaR.Clouds.Base.Requests +namespace YaR.Clouds.Base.Requests; + +internal abstract class BaseRequestString : BaseRequest where T : class { - internal abstract class BaseRequestString : BaseRequest where T : class + protected BaseRequestString(HttpCommonSettings settings, IAuth auth) : base(settings, auth) { - protected BaseRequestString(HttpCommonSettings settings, IAuth auth) : base(settings, auth) - { - } + } - protected override string Transport(Stream stream) - { - using var sr = new StreamReader(stream); - return sr.ReadToEnd(); - } + protected override string Transport(Stream stream) + { + using var sr = new StreamReader(stream); + return sr.ReadToEnd(); } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/HttpCommonSettings.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/HttpCommonSettings.cs index 88a4ff63..3abb77d4 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/HttpCommonSettings.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/HttpCommonSettings.cs @@ -1,13 +1,12 @@ using System.Net; -namespace YaR.Clouds.Base.Requests +namespace YaR.Clouds.Base.Requests; + +public class HttpCommonSettings { - public class HttpCommonSettings - { - public IWebProxy Proxy { get; set; } - public string ClientId { get; set; } - public string UserAgent { get; set; } - public CloudSettings CloudSettings { get; set; } - public string BaseDomain { get; set; } - } + public IWebProxy Proxy { get; set; } + public string ClientId { get; set; } + public string UserAgent { get; set; } + public CloudSettings CloudSettings { get; set; } + public string BaseDomain { get; set; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/RequestException.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/RequestException.cs index 6dc9a145..e3be9345 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/RequestException.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/RequestException.cs @@ -1,36 +1,35 @@ using System; using System.Net; -namespace YaR.Clouds.Base.Requests +namespace YaR.Clouds.Base.Requests; + +[Serializable] +public class RequestException : Exception { - [Serializable] - public class RequestException : Exception - { - public RequestException() - { } - - public RequestException(string message) : base(message) { } - - public RequestException(string message, Exception inner) : base(message, inner) { } - - /// - /// HTTP Status Code returned by server - /// - public HttpStatusCode StatusCode { get; set; } - - /// - /// Response body text - /// - public string ResponseBody { get; set; } - - /// - /// Optional. Human-readable description of the result (by Telegram) - /// - public string Description { get; set; } - - /// - /// Contents are subject to change in the future (by Telegram) - /// - public long? ErrorCode { get; set; } - } + public RequestException() + { } + + public RequestException(string message) : base(message) { } + + public RequestException(string message, Exception inner) : base(message, inner) { } + + /// + /// HTTP Status Code returned by server + /// + public HttpStatusCode StatusCode { get; set; } + + /// + /// Response body text + /// + public string ResponseBody { get; set; } + + /// + /// Optional. Human-readable description of the result (by Telegram) + /// + public string Description { get; set; } + + /// + /// Contents are subject to change in the future (by Telegram) + /// + public long? ErrorCode { get; set; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/RequestResponse.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/RequestResponse.cs index 09423ff9..12afc127 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/RequestResponse.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/RequestResponse.cs @@ -1,13 +1,12 @@ -namespace YaR.Clouds.Base.Requests +namespace YaR.Clouds.Base.Requests; + +public struct RequestResponse { - public struct RequestResponse - { - public bool Ok { get; set; } + public bool Ok { get; set; } - public string Description { get; set; } + public string Description { get; set; } - public T Result { get; set; } + public T Result { get; set; } - public long? ErrorCode { get; set; } - } + public long? ErrorCode { get; set; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/AccountInfo.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/AccountInfo.cs index f5da4644..faeab8ed 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/AccountInfo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/AccountInfo.cs @@ -1,15 +1,14 @@ -namespace YaR.Clouds.Base.Requests.Types -{ - public class AccountInfoResult - { - private long _fileSizeLimit; +namespace YaR.Clouds.Base.Requests.Types; - public long FileSizeLimit - { - get => _fileSizeLimit <= 0 ? long.MaxValue : _fileSizeLimit; - set => _fileSizeLimit = value; - } +public class AccountInfoResult +{ + private long _fileSizeLimit; - public DiskUsage DiskUsage { get; set; } + public long FileSizeLimit + { + get => _fileSizeLimit <= 0 ? long.MaxValue : _fileSizeLimit; + set => _fileSizeLimit = value; } + + public DiskUsage DiskUsage { get; set; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/AccountInfoRequestResult.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/AccountInfoRequestResult.cs index e7b04517..96253280 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/AccountInfoRequestResult.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/AccountInfoRequestResult.cs @@ -1,76 +1,75 @@ using System.Collections.Generic; using Newtonsoft.Json; -namespace YaR.Clouds.Base.Requests.Types +namespace YaR.Clouds.Base.Requests.Types; + +internal class AccountInfoRequestResult : CommonOperationResult { - internal class AccountInfoRequestResult : CommonOperationResult + public class AccountInfoBody { - public class AccountInfoBody + [JsonProperty("cloud")] public CloudInfo Cloud { get; set; } + [JsonProperty("domain")] public string Domain { get; set; } + [JsonProperty("ui")] public UIInfo UI { get; set; } + [JsonProperty("newbie")] public bool Newbie { get; set; } + [JsonProperty("account_type")] public string AccountType { get; set; } + [JsonProperty("login")] public string Login { get; set; } + + public class UIInfo { - [JsonProperty("cloud")] public CloudInfo Cloud { get; set; } - [JsonProperty("domain")] public string Domain { get; set; } - [JsonProperty("ui")] public UIInfo UI { get; set; } - [JsonProperty("newbie")] public bool Newbie { get; set; } - [JsonProperty("account_type")] public string AccountType { get; set; } - [JsonProperty("login")] public string Login { get; set; } + [JsonProperty("sidebar")] public bool Sidebar { get; set; } + [JsonProperty("sort")] public FolderInfoResult.FolderInfoBody.FolderInfoSort Sort { get; set; } + [JsonProperty("kind")] public string Kind { get; set; } + [JsonProperty("thumbs")] public bool Thumbs { get; set; } + [JsonProperty("expand_loader")] public bool ExpandLoader { get; set; } + } - public class UIInfo + public class CloudInfo + { + [JsonProperty("enable")] public EnableInfo Enable { get; set; } + [JsonProperty("beta")] public BetaInfo Beta { get; set; } + [JsonProperty("bonuses")] public BonusesInfo Bonuses { get; set; } + [JsonProperty("file_size_limit")] public long FileSizeLimit { get; set; } + [JsonProperty("space")] public SpaceInfo Space { get; set; } + [JsonProperty("billing")] public BillingInfo Billing { get; set; } + + public class EnableInfo { - [JsonProperty("sidebar")] public bool Sidebar { get; set; } - [JsonProperty("sort")] public FolderInfoResult.FolderInfoBody.FolderInfoSort Sort { get; set; } - [JsonProperty("kind")] public string Kind { get; set; } - [JsonProperty("thumbs")] public bool Thumbs { get; set; } - [JsonProperty("expand_loader")] public bool ExpandLoader { get; set; } + [JsonProperty("sharing")] public bool Sharing { get; set; } } - public class CloudInfo + public class BetaInfo { - [JsonProperty("enable")] public EnableInfo Enable { get; set; } - [JsonProperty("beta")] public BetaInfo Beta { get; set; } - [JsonProperty("bonuses")] public BonusesInfo Bonuses { get; set; } - [JsonProperty("file_size_limit")] public long FileSizeLimit { get; set; } - [JsonProperty("space")] public SpaceInfo Space { get; set; } - [JsonProperty("billing")] public BillingInfo Billing { get; set; } - - public class EnableInfo - { - [JsonProperty("sharing")] public bool Sharing { get; set; } - } - - public class BetaInfo - { - [JsonProperty("allowed")] public bool IsAllowed { get; set; } - [JsonProperty("asked")] public bool Asked { get; set; } - } + [JsonProperty("allowed")] public bool IsAllowed { get; set; } + [JsonProperty("asked")] public bool Asked { get; set; } + } - public class BonusesInfo - { - [JsonProperty("camera_upload")] public bool CameraUpload { get; set; } - [JsonProperty("desktop")] public bool Desktop { get; set; } - [JsonProperty("mobile")] public bool Mobile { get; set; } - [JsonProperty("complete")] public bool Complete { get; set; } - [JsonProperty("registration")] public bool Registration { get; set; } - [JsonProperty("feedback")] public bool Feedback { get; set; } - [JsonProperty("links")] public bool Links { get; set; } - } + public class BonusesInfo + { + [JsonProperty("camera_upload")] public bool CameraUpload { get; set; } + [JsonProperty("desktop")] public bool Desktop { get; set; } + [JsonProperty("mobile")] public bool Mobile { get; set; } + [JsonProperty("complete")] public bool Complete { get; set; } + [JsonProperty("registration")] public bool Registration { get; set; } + [JsonProperty("feedback")] public bool Feedback { get; set; } + [JsonProperty("links")] public bool Links { get; set; } + } - public class SpaceInfo - { - [JsonProperty("bytes_total")] public long BytesTotal { get; set; } - [JsonProperty("overquota")] public bool IsOverquota { get; set; } - [JsonProperty("bytes_used")] public long BytesUsed { get; set; } - } + public class SpaceInfo + { + [JsonProperty("bytes_total")] public long BytesTotal { get; set; } + [JsonProperty("overquota")] public bool IsOverquota { get; set; } + [JsonProperty("bytes_used")] public long BytesUsed { get; set; } + } - public class BillingInfo - { - [JsonProperty("active_cost_id")] public string ActiveCostID { get; set; } - [JsonProperty("active_rate_id")] public string ActiveRateID { get; set; } - [JsonProperty("auto_prolong")] public bool AutoProlong { get; set; } - [JsonProperty("subscription")] public List Subscription { get; set; } - [JsonProperty("prolong")] public bool Prolong { get; set; } - [JsonProperty("enabled")] public bool Enabled { get; set; } - [JsonProperty("expires")] public int Expires { get; set; } - } + public class BillingInfo + { + [JsonProperty("active_cost_id")] public string ActiveCostID { get; set; } + [JsonProperty("active_rate_id")] public string ActiveRateID { get; set; } + [JsonProperty("auto_prolong")] public bool AutoProlong { get; set; } + [JsonProperty("subscription")] public List Subscription { get; set; } + [JsonProperty("prolong")] public bool Prolong { get; set; } + [JsonProperty("enabled")] public bool Enabled { get; set; } + [JsonProperty("expires")] public int Expires { get; set; } } } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/AddFileResult.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/AddFileResult.cs index 1176fb4a..1b114ba0 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/AddFileResult.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/AddFileResult.cs @@ -1,8 +1,7 @@ -namespace YaR.Clouds.Base.Requests.Types +namespace YaR.Clouds.Base.Requests.Types; + +public class AddFileResult { - public class AddFileResult - { - public bool Success { get; set; } - public string Path { get; set; } - } + public bool Success { get; set; } + public string Path { get; set; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/AuthTokenRequestResult.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/AuthTokenRequestResult.cs index 47ba8ee3..2a4d6e12 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/AuthTokenRequestResult.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/AuthTokenRequestResult.cs @@ -1,13 +1,12 @@ using Newtonsoft.Json; -namespace YaR.Clouds.Base.Requests.Types +namespace YaR.Clouds.Base.Requests.Types; + +class AuthTokenRequestResult : CommonOperationResult { - class AuthTokenRequestResult : CommonOperationResult + public class AuthTokenResultBody { - public class AuthTokenResultBody - { - [JsonProperty("token")] - public string Token { get; set; } - } + [JsonProperty("token")] + public string Token { get; set; } } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/AuthTokenResult.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/AuthTokenResult.cs index 2ed5d0f9..ab9d81f8 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/AuthTokenResult.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/AuthTokenResult.cs @@ -1,14 +1,13 @@ using System; -namespace YaR.Clouds.Base.Requests.Types +namespace YaR.Clouds.Base.Requests.Types; + +public class AuthTokenResult { - public class AuthTokenResult - { - public bool IsSuccess { get; set; } - public string Token { get; set; } - public TimeSpan ExpiresIn { get; set; } - public string RefreshToken { get; set; } - public bool IsSecondStepRequired { get; set; } - public string TsaToken { get; set; } - } + public bool IsSuccess { get; set; } + public string Token { get; set; } + public TimeSpan ExpiresIn { get; set; } + public string RefreshToken { get; set; } + public bool IsSecondStepRequired { get; set; } + public string TsaToken { get; set; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/CheckUpInfo.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/CheckUpInfo.cs index 78ccddb7..8f5cfbc7 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/CheckUpInfo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/CheckUpInfo.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Threading; using YaR.Clouds.Base.Repos.YandexDisk.YadWeb.Models; @@ -170,7 +169,7 @@ public void Increment(CounterOperation operation) public void TakeMax(JournalCounters src) { - if(RemoveCounter { - public class CommonOperationResult - { - [JsonProperty("email")] - public string Email { get; set; } - [JsonProperty("body")] - public T Body { get; set; } - [JsonProperty("time")] - public long Time { get; set; } - [JsonProperty("status")] - public int Status { get; set; } - } + [JsonProperty("email")] + public string Email { get; set; } + [JsonProperty("body")] + public T Body { get; set; } + [JsonProperty("time")] + public long Time { get; set; } + [JsonProperty("status")] + public int Status { get; set; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/CopyResult.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/CopyResult.cs index 8b5f014a..9131d272 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/CopyResult.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/CopyResult.cs @@ -1,12 +1,11 @@ using System; -namespace YaR.Clouds.Base.Requests.Types +namespace YaR.Clouds.Base.Requests.Types; + +public class CopyResult { - public class CopyResult - { - public bool IsSuccess { get; set; } - public string NewName { get; set; } - public string OldFullPath { get; set; } - public DateTime DateTime { get; set; } - } + public bool IsSuccess { get; set; } + public string NewName { get; set; } + public string OldFullPath { get; set; } + public DateTime DateTime { get; set; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/CreateFolderResult.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/CreateFolderResult.cs index 0b28db1f..e9465710 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/CreateFolderResult.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/CreateFolderResult.cs @@ -1,8 +1,7 @@ -namespace YaR.Clouds.Base.Requests.Types +namespace YaR.Clouds.Base.Requests.Types; + +public class CreateFolderResult { - public class CreateFolderResult - { - public bool IsSuccess { get; set; } - public string Path{ get; set; } - } + public bool IsSuccess { get; set; } + public string Path{ get; set; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/DiskUsage.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/DiskUsage.cs index e2dad7e2..f1f2b11e 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/DiskUsage.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/DiskUsage.cs @@ -1,25 +1,24 @@ -namespace YaR.Clouds.Base.Requests.Types +namespace YaR.Clouds.Base.Requests.Types; + +/// +/// Get cloud disk usage for current account. +/// +public class DiskUsage { /// - /// Get cloud disk usage for current account. + /// Gets total disk size. /// - public class DiskUsage - { - /// - /// Gets total disk size. - /// - public FileSize Total { get; set; } + public FileSize Total { get; set; } - /// - /// Gets used disk size. - /// - public FileSize Used { get; set; } + /// + /// Gets used disk size. + /// + public FileSize Used { get; set; } - /// - /// Gets free disk size. - /// - public FileSize Free => Total - Used; + /// + /// Gets free disk size. + /// + public FileSize Free => Total - Used; - public bool OverQuota { get; set; } - } + public bool OverQuota { get; set; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/DownloadTokenResult.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/DownloadTokenResult.cs index 9c8fa3ae..21a87c31 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/DownloadTokenResult.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/DownloadTokenResult.cs @@ -1,13 +1,12 @@ using Newtonsoft.Json; -namespace YaR.Clouds.Base.Requests.Types +namespace YaR.Clouds.Base.Requests.Types; + +internal class DownloadTokenResult : CommonOperationResult { - internal class DownloadTokenResult : CommonOperationResult + public class DownloadTokenBody { - public class DownloadTokenBody - { - [JsonProperty("token")] - public string Token { get; set; } - } + [JsonProperty("token")] + public string Token { get; set; } } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/FolderInfoResult.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/FolderInfoResult.cs index 37c97a2e..dfcb9670 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/FolderInfoResult.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/FolderInfoResult.cs @@ -1,12 +1,63 @@ using System.Collections.Generic; using Newtonsoft.Json; -namespace YaR.Clouds.Base.Requests.Types +namespace YaR.Clouds.Base.Requests.Types; + +public class FolderInfoResult : CommonOperationResult { - public class FolderInfoResult : CommonOperationResult + public class FolderInfoBody { - public class FolderInfoBody + [JsonProperty("count")] + public FolderInfoCount Count { get; set; } + + //[JsonProperty("tree")] + //public string Tree { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + + //[JsonProperty("grev")] + //public int Grev { get; set; } + + [JsonProperty("size")] + public long Size { get; set; } + + //[JsonProperty("sort")] + //public FolderInfoSort Sort { get; set; } + + [JsonProperty("kind")] + public string Kind { get; set; } + + //[JsonProperty("rev")] + //public int Rev { get; set; } + + //[JsonProperty("type")] + //public string Type { get; set; } + + [JsonProperty("home")] + public string Home { get; set; } + + [JsonProperty("list")] + public List List { get; set; } + + [JsonProperty("weblink")] + public string Weblink { get; set; } + + #region Преимущественно для выборки отдельного файла или папки вместо списка + + [JsonProperty("mtime")] + public ulong Mtime; + + [JsonProperty("hash")] + public string Hash { get; set; } + + #endregion + + public class FolderInfoProps { + [JsonProperty("mtime")] + public ulong Mtime; + [JsonProperty("count")] public FolderInfoCount Count { get; set; } @@ -22,9 +73,6 @@ public class FolderInfoBody [JsonProperty("size")] public long Size { get; set; } - //[JsonProperty("sort")] - //public FolderInfoSort Sort { get; set; } - [JsonProperty("kind")] public string Kind { get; set; } @@ -37,76 +85,27 @@ public class FolderInfoBody [JsonProperty("home")] public string Home { get; set; } - [JsonProperty("list")] - public List List { get; set; } - [JsonProperty("weblink")] public string Weblink { get; set; } - #region Преимущественно для выборки отдельного файла или папки вместо списка - - [JsonProperty("mtime")] - public ulong Mtime; - [JsonProperty("hash")] public string Hash { get; set; } + } - #endregion - - public class FolderInfoProps - { - [JsonProperty("mtime")] - public ulong Mtime; - - [JsonProperty("count")] - public FolderInfoCount Count { get; set; } - - //[JsonProperty("tree")] - //public string Tree { get; set; } - - [JsonProperty("name")] - public string Name { get; set; } - - //[JsonProperty("grev")] - //public int Grev { get; set; } - - [JsonProperty("size")] - public long Size { get; set; } - - [JsonProperty("kind")] - public string Kind { get; set; } - - //[JsonProperty("rev")] - //public int Rev { get; set; } - - //[JsonProperty("type")] - //public string Type { get; set; } - - [JsonProperty("home")] - public string Home { get; set; } - - [JsonProperty("weblink")] - public string Weblink { get; set; } - - [JsonProperty("hash")] - public string Hash { get; set; } - } - - public class FolderInfoSort - { - [JsonProperty("order")] - public string Order { get; set; } - [JsonProperty("type")] - public string Type { get; set; } - } + public class FolderInfoSort + { + [JsonProperty("order")] + public string Order { get; set; } + [JsonProperty("type")] + public string Type { get; set; } + } - public class FolderInfoCount - { - [JsonProperty("folders")] - public int Folders { get; set; } - [JsonProperty("files")] - public int Files { get; set; } - } + public class FolderInfoCount + { + [JsonProperty("folders")] + public int Folders { get; set; } + [JsonProperty("files")] + public int Files { get; set; } } } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/ItemOperation.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/ItemOperation.cs index a71bcd57..6b9ea396 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/ItemOperation.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/ItemOperation.cs @@ -1,10 +1,9 @@ using System; -namespace YaR.Clouds.Base.Requests.Types +namespace YaR.Clouds.Base.Requests.Types; + +public class ItemOperation { - public class ItemOperation - { - public DateTime DateTime { get; set; } - public string Path { get; set; } - } + public DateTime DateTime { get; set; } + public string Path { get; set; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/LoginResult.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/LoginResult.cs index e0864a31..39a98229 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/LoginResult.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/LoginResult.cs @@ -1,7 +1,6 @@ -namespace YaR.Clouds.Base.Requests.Types +namespace YaR.Clouds.Base.Requests.Types; + +class LoginResult { - class LoginResult - { - public string Csrf { get; set; } - } + public string Csrf { get; set; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/PublishResult.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/PublishResult.cs index 37b9d797..d1471b8c 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/PublishResult.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/PublishResult.cs @@ -1,8 +1,7 @@ -namespace YaR.Clouds.Base.Requests.Types +namespace YaR.Clouds.Base.Requests.Types; + +public class PublishResult { - public class PublishResult - { - public bool IsSuccess { get; set; } - public string Url { get; set; } - } + public bool IsSuccess { get; set; } + public string Url { get; set; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/RemoveResult.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/RemoveResult.cs index cc8ea36c..fc8f023a 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/RemoveResult.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/RemoveResult.cs @@ -1,11 +1,10 @@ using System; -namespace YaR.Clouds.Base.Requests.Types +namespace YaR.Clouds.Base.Requests.Types; + +public class RemoveResult { - public class RemoveResult - { - public bool IsSuccess { get; set; } - public DateTime DateTime { get; set; } - public string Path { get; set; } - } + public bool IsSuccess { get; set; } + public DateTime DateTime { get; set; } + public string Path { get; set; } } \ No newline at end of file diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/RenameResult.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/RenameResult.cs index 3697bded..c21ed218 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/RenameResult.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/RenameResult.cs @@ -1,11 +1,10 @@ using System; -namespace YaR.Clouds.Base.Requests.Types +namespace YaR.Clouds.Base.Requests.Types; + +public class RenameResult { - public class RenameResult - { - public bool IsSuccess{ get; set; } - public DateTime DateTime { get; set; } - public string Path { get; set; } - } + public bool IsSuccess{ get; set; } + public DateTime DateTime { get; set; } + public string Path { get; set; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/ServerRequestResult.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/ServerRequestResult.cs index 57cc08fc..5d2f401b 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/ServerRequestResult.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/ServerRequestResult.cs @@ -1,9 +1,8 @@ -namespace YaR.Clouds.Base.Requests.Types +namespace YaR.Clouds.Base.Requests.Types; + +public class ServerRequestResult { - public class ServerRequestResult - { - public string Url { get; set; } - public string Ip { get; set; } - public int Unknown { get; set; } - } + public string Url { get; set; } + public string Ip { get; set; } + public int Unknown { get; set; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/ShardInfo.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/ShardInfo.cs index 7506cf54..0a3a0f25 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/ShardInfo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/ShardInfo.cs @@ -1,26 +1,25 @@ -namespace YaR.Clouds.Base.Requests.Types +namespace YaR.Clouds.Base.Requests.Types; + +/// +/// Servers info class. +/// +public class ShardInfo { /// - /// Servers info class. + /// Gets or sets shard type. /// - public class ShardInfo - { - /// - /// Gets or sets shard type. - /// - /// Shard type. - public ShardType Type { get; internal set; } + /// Shard type. + public ShardType Type { get; internal set; } - /// - /// Gets or sets number of the shards. - /// - /// Number of the shards. - public int Count { get; internal set; } + /// + /// Gets or sets number of the shards. + /// + /// Number of the shards. + public int Count { get; internal set; } - /// - /// Gets or sets shard link. - /// - /// Shard link. - public string Url { get; internal set; } - } + /// + /// Gets or sets shard link. + /// + /// Shard link. + public string Url { get; internal set; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/ShardInfoRequestResult.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/ShardInfoRequestResult.cs index 2b8e5ef1..d2c4a687 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/ShardInfoRequestResult.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/ShardInfoRequestResult.cs @@ -1,42 +1,41 @@ using System.Collections.Generic; using Newtonsoft.Json; -namespace YaR.Clouds.Base.Requests.Types +namespace YaR.Clouds.Base.Requests.Types; + +internal class ShardInfoRequestResult : CommonOperationResult { - internal class ShardInfoRequestResult : CommonOperationResult + public class ShardInfoResultBody { - public class ShardInfoResultBody - { - [JsonProperty("video")] - public List Video { get; set; } - [JsonProperty("view_direct")] - public List ViewDirect { get; set; } - [JsonProperty("weblink_view")] - public List WeblinkView { get; set; } - [JsonProperty("weblink_video")] - public List WeblinkVideo { get; set; } - [JsonProperty("weblink_get")] - public List WeblinkGet { get; set; } - [JsonProperty("weblink_thumbnails")] - public List WeblinkThumbnails { get; set; } - [JsonProperty("auth")] - public List Auth { get; set; } - [JsonProperty("view")] - public List View { get; set; } - [JsonProperty("get")] - public List Get { get; set; } - [JsonProperty("upload")] - public List Upload { get; set; } - [JsonProperty("thumbnails")] - public List Thumbnails { get; set; } + [JsonProperty("video")] + public List Video { get; set; } + [JsonProperty("view_direct")] + public List ViewDirect { get; set; } + [JsonProperty("weblink_view")] + public List WeblinkView { get; set; } + [JsonProperty("weblink_video")] + public List WeblinkVideo { get; set; } + [JsonProperty("weblink_get")] + public List WeblinkGet { get; set; } + [JsonProperty("weblink_thumbnails")] + public List WeblinkThumbnails { get; set; } + [JsonProperty("auth")] + public List Auth { get; set; } + [JsonProperty("view")] + public List View { get; set; } + [JsonProperty("get")] + public List Get { get; set; } + [JsonProperty("upload")] + public List Upload { get; set; } + [JsonProperty("thumbnails")] + public List Thumbnails { get; set; } - public class ShardSection - { - [JsonProperty("count")] - public string Count { get; set; } - [JsonProperty("url")] - public string Url { get; set; } - } + public class ShardSection + { + [JsonProperty("count")] + public string Count { get; set; } + [JsonProperty("url")] + public string Url { get; set; } } } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/ShardType.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/ShardType.cs index 4a64b338..80fa958e 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/ShardType.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/ShardType.cs @@ -1,76 +1,75 @@ using System.ComponentModel; -namespace YaR.Clouds.Base.Requests.Types +namespace YaR.Clouds.Base.Requests.Types; + +/// +/// Shard types. +/// +public enum ShardType { /// - /// Shard types. + /// Get video shard. /// - public enum ShardType - { - /// - /// Get video shard. - /// - [Description("video")] - Video = 0, + [Description("video")] + Video = 0, - /// - /// Get view direct shard. - /// - [Description("view_direct")] - ViewDirect = 1, + /// + /// Get view direct shard. + /// + [Description("view_direct")] + ViewDirect = 1, - /// - /// Web link view shard. - /// - [Description("weblink_view")] - WeblinkView = 2, + /// + /// Web link view shard. + /// + [Description("weblink_view")] + WeblinkView = 2, - /// - /// Web link view shard. - /// - [Description("weblink_video")] - WeblinkVideo = 3, + /// + /// Web link view shard. + /// + [Description("weblink_video")] + WeblinkVideo = 3, - /// - /// Web link get shard. - /// - [Description("weblink_get")] - WeblinkGet = 4, + /// + /// Web link get shard. + /// + [Description("weblink_get")] + WeblinkGet = 4, - /// - /// Web link get thumbnails shard. - /// - [Description("weblink_thumbnails")] - WeblinkThumbnails = 5, + /// + /// Web link get thumbnails shard. + /// + [Description("weblink_thumbnails")] + WeblinkThumbnails = 5, - /// - /// Authorization shard. - /// - [Description("auth")] - Auth = 6, + /// + /// Authorization shard. + /// + [Description("auth")] + Auth = 6, - /// - /// View shard. - /// - [Description("view")] - View = 7, + /// + /// View shard. + /// + [Description("view")] + View = 7, - /// - /// Get shard. - /// - [Description("get")] - Get = 8, + /// + /// Get shard. + /// + [Description("get")] + Get = 8, - /// - /// Upload items shard. - /// - [Description("upload")] - Upload = 9, + /// + /// Upload items shard. + /// + [Description("upload")] + Upload = 9, - /// - /// Thumbnails shard. - /// - [Description("thumbnails")] - Thumbnails = 10 - } + /// + /// Thumbnails shard. + /// + [Description("thumbnails")] + Thumbnails = 10 } diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/UnpublishResult.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/UnpublishResult.cs index 0b05616f..7fe5c768 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/UnpublishResult.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/UnpublishResult.cs @@ -1,7 +1,6 @@ -namespace YaR.Clouds.Base.Requests.Types +namespace YaR.Clouds.Base.Requests.Types; + +public class UnpublishResult { - public class UnpublishResult - { - public bool IsSuccess { get; set; } - } + public bool IsSuccess { get; set; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/UploadFileResult.cs b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/UploadFileResult.cs index 327ea4b6..8952b095 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Requests/Types/UploadFileResult.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Requests/Types/UploadFileResult.cs @@ -1,13 +1,12 @@ using System.Net; -namespace YaR.Clouds.Base.Requests.Types +namespace YaR.Clouds.Base.Requests.Types; + +public class UploadFileResult { - public class UploadFileResult - { - public IFileHash Hash { get; set; } - public long Size{ get; set; } - public HttpStatusCode HttpStatusCode { get; set; } - public bool HasReturnedData { get; set; } - public bool NeedToAddFile { get; set; } = true; - } + public IFileHash Hash { get; set; } + public long Size{ get; set; } + public HttpStatusCode HttpStatusCode { get; set; } + public bool HasReturnedData { get; set; } + public bool NeedToAddFile { get; set; } = true; } diff --git a/MailRuCloud/MailRuCloudApi/Base/ResolveFileConflictMethod.cs b/MailRuCloud/MailRuCloudApi/Base/ResolveFileConflictMethod.cs index f9e352a5..d823fe4e 100644 --- a/MailRuCloud/MailRuCloudApi/Base/ResolveFileConflictMethod.cs +++ b/MailRuCloud/MailRuCloudApi/Base/ResolveFileConflictMethod.cs @@ -1,47 +1,46 @@ using System; -namespace YaR.Clouds.Base +namespace YaR.Clouds.Base; + +public readonly struct ConflictResolver : IEquatable { - public readonly struct ConflictResolver : IEquatable + private readonly string _value; + + private ConflictResolver(string value) + { + _value = value; + } + + public static ConflictResolver Rename => new("rename"); + public static ConflictResolver Rewrite => new("rewrite"); + + public override bool Equals(object obj) + { + return obj is ConflictResolver resolver && Equals(resolver); + } + + public bool Equals(ConflictResolver other) + { + return _value == other._value; + } + + public override string ToString() + { + return _value; + } + + public static bool operator ==(ConflictResolver resolver1, ConflictResolver resolver2) + { + return resolver1.Equals(resolver2); + } + + public static bool operator !=(ConflictResolver resolver1, ConflictResolver resolver2) + { + return !(resolver1 == resolver2); + } + + public override int GetHashCode() { - private readonly string _value; - - private ConflictResolver(string value) - { - _value = value; - } - - public static ConflictResolver Rename => new("rename"); - public static ConflictResolver Rewrite => new("rewrite"); - - public override bool Equals(object obj) - { - return obj is ConflictResolver resolver && Equals(resolver); - } - - public bool Equals(ConflictResolver other) - { - return _value == other._value; - } - - public override string ToString() - { - return _value; - } - - public static bool operator ==(ConflictResolver resolver1, ConflictResolver resolver2) - { - return resolver1.Equals(resolver2); - } - - public static bool operator !=(ConflictResolver resolver1, ConflictResolver resolver2) - { - return !(resolver1 == resolver2); - } - - public override int GetHashCode() - { - return _value.GetHashCode(); - } + return _value.GetHashCode(); } } diff --git a/MailRuCloud/MailRuCloudApi/Base/SplittedFile.cs b/MailRuCloud/MailRuCloudApi/Base/SplittedFile.cs index 05d1b788..8e5fab06 100644 --- a/MailRuCloud/MailRuCloudApi/Base/SplittedFile.cs +++ b/MailRuCloud/MailRuCloudApi/Base/SplittedFile.cs @@ -2,65 +2,64 @@ using System.Collections.Generic; using System.Linq; -namespace YaR.Clouds.Base +namespace YaR.Clouds.Base; + +public class SplittedFile : File { - public class SplittedFile : File + public SplittedFile(IList files) { - public SplittedFile(IList files) - { - _fileHeader = files.FirstOrDefault(f => !f.ServiceInfo.SplitInfo.IsPart); - Files = files; - Parts = files - .Where(f => f.ServiceInfo.SplitInfo.IsPart) - .OrderBy(f => f.ServiceInfo.SplitInfo.PartNumber) - .ToList(); + _fileHeader = files.FirstOrDefault(f => !f.ServiceInfo.SplitInfo.IsPart); + Files = files; + Parts = files + .Where(f => f.ServiceInfo.SplitInfo.IsPart) + .OrderBy(f => f.ServiceInfo.SplitInfo.PartNumber) + .ToList(); - FullPath = _fileHeader.FullPath; + FullPath = _fileHeader.FullPath; - var cryptofile = files.FirstOrDefault(f => f.ServiceInfo.SplitInfo.IsPart && f.ServiceInfo.CryptInfo != null); - ServiceInfo = new FilenameServiceInfo + var cryptofile = files.FirstOrDefault(f => f.ServiceInfo.SplitInfo.IsPart && f.ServiceInfo.CryptInfo != null); + ServiceInfo = new FilenameServiceInfo + { + CleanName = _fileHeader.Name, + CryptInfo = cryptofile?.ServiceInfo?.CryptInfo, + SplitInfo = new FileSplitInfo { - CleanName = _fileHeader.Name, - CryptInfo = cryptofile?.ServiceInfo?.CryptInfo, - SplitInfo = new FileSplitInfo - { - IsHeader = true - } - }; - } + IsHeader = true + } + }; + } - public override FileSize Size => Parts.Sum(f => f.Size); + public override FileSize Size => Parts.Sum(f => f.Size); - public override FileSize OriginalSize => Parts.Sum(f => f.OriginalSize); + public override FileSize OriginalSize => Parts.Sum(f => f.OriginalSize); - public override IFileHash Hash => FileHeader.Hash; + public override IFileHash Hash => FileHeader.Hash; - public override DateTime CreationTimeUtc => FileHeader.CreationTimeUtc; - public override DateTime LastWriteTimeUtc => FileHeader.LastWriteTimeUtc; - public override DateTime LastAccessTimeUtc => FileHeader.LastAccessTimeUtc; + public override DateTime CreationTimeUtc => FileHeader.CreationTimeUtc; + public override DateTime LastWriteTimeUtc => FileHeader.LastWriteTimeUtc; + public override DateTime LastAccessTimeUtc => FileHeader.LastAccessTimeUtc; - protected override File FileHeader => _fileHeader; - private readonly File _fileHeader; + protected override File FileHeader => _fileHeader; + private readonly File _fileHeader; - /// - /// List of physical files contains data - /// - public override List Parts { get; } + /// + /// List of physical files contains data + /// + public override List Parts { get; } - public override IList Files { get; } + public override IList Files { get; } - public override File New(string newFullPath) - { - string path = WebDavPath.Parent(newFullPath); - - var flist = Files - .Select(f => f.New(WebDavPath.Combine(path, f.Name))) - .ToList(); - var spfile = new SplittedFile(flist); - return spfile; - } + public override File New(string newFullPath) + { + string path = WebDavPath.Parent(newFullPath); + + var flist = Files + .Select(f => f.New(WebDavPath.Combine(path, f.Name))) + .ToList(); + var spfile = new SplittedFile(flist); + return spfile; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/CacheStream.cs b/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/CacheStream.cs index edbb096c..b259c167 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/CacheStream.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/CacheStream.cs @@ -2,60 +2,59 @@ using System.IO; using System.Text.RegularExpressions; -namespace YaR.Clouds.Base.Streams.Cache +namespace YaR.Clouds.Base.Streams.Cache; + +internal class CacheStream : IDisposable { - internal class CacheStream : IDisposable - { - private readonly File _file; - private readonly Stream _sourceStream; - private readonly DeduplicateRulesBag _deduplicateRules; + private readonly File _file; + private readonly Stream _sourceStream; + private readonly DeduplicateRulesBag _deduplicateRules; - public CacheStream(File file, Stream sourceStream, DeduplicateRulesBag deduplicateRules) - { - _file = file; - _sourceStream = sourceStream; - _deduplicateRules = deduplicateRules; - } + public CacheStream(File file, Stream sourceStream, DeduplicateRulesBag deduplicateRules) + { + _file = file; + _sourceStream = sourceStream; + _deduplicateRules = deduplicateRules; + } - public bool Process() - { - _cache = GetCache(); - if (null == _cache) - return false; + public bool Process() + { + _cache = GetCache(); + if (null == _cache) + return false; - _cache.FillFrom(_sourceStream); - return true; - } + _cache.FillFrom(_sourceStream); + return true; + } - public string DataCacheName => _cache?.Name; - private DataCache _cache; + public string DataCacheName => _cache?.Name; + private DataCache _cache; - public Stream Stream => _cache?.OutStream ?? _sourceStream; + public Stream Stream => _cache?.OutStream ?? _sourceStream; - private DataCache GetCache() + private DataCache GetCache() + { + foreach (var rule in _deduplicateRules.Rules) { - foreach (var rule in _deduplicateRules.Rules) + if ( + (rule.MaxSize == 0 || rule.MaxSize > _file.Size) && + _file.Size >= rule.MinSize && + (string.IsNullOrEmpty(rule.Target) || Regex.Match(_file.FullPath, rule.Target).Success)) { - if ( - (rule.MaxSize == 0 || rule.MaxSize > _file.Size) && - _file.Size >= rule.MinSize && - (string.IsNullOrEmpty(rule.Target) || Regex.Match(_file.FullPath, rule.Target).Success) ) + return rule.CacheType switch { - return rule.CacheType switch - { - CacheType.Memory => new MemoryDataCache(), - CacheType.Disk => new DiskDataCache(_deduplicateRules.DiskPath), - _ => throw new NotImplementedException($"DataCache not implemented for {rule.CacheType}") - }; - } + CacheType.Memory => new MemoryDataCache(), + CacheType.Disk => new DiskDataCache(_deduplicateRules.DiskPath), + _ => throw new NotImplementedException($"DataCache not implemented for {rule.CacheType}") + }; } - return null; } + return null; + } - public void Dispose() - { - _cache?.Dispose(); - } + public void Dispose() + { + _cache?.Dispose(); } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/CacheType.cs b/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/CacheType.cs index 25f4fb05..d74fd0c7 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/CacheType.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/CacheType.cs @@ -1,8 +1,7 @@ -namespace YaR.Clouds.Base.Streams.Cache +namespace YaR.Clouds.Base.Streams.Cache; + +public enum CacheType { - public enum CacheType - { - Memory = 0, - Disk = 1 - } + Memory = 0, + Disk = 1 } diff --git a/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/DataCache.cs b/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/DataCache.cs index 16d29007..59b73d17 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/DataCache.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/DataCache.cs @@ -1,16 +1,15 @@ using System; using System.IO; -namespace YaR.Clouds.Base.Streams.Cache +namespace YaR.Clouds.Base.Streams.Cache; + +internal abstract class DataCache : IDisposable { - internal abstract class DataCache : IDisposable - { - public abstract string Name { get; } + public abstract string Name { get; } - public abstract Stream OutStream { get; } + public abstract Stream OutStream { get; } - public abstract void FillFrom(Stream sourceStream); + public abstract void FillFrom(Stream sourceStream); - public abstract void Dispose(); - } + public abstract void Dispose(); } diff --git a/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/DeduplicateRule.cs b/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/DeduplicateRule.cs index ad3c4c39..c8e2c9ed 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/DeduplicateRule.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/DeduplicateRule.cs @@ -1,10 +1,9 @@ -namespace YaR.Clouds.Base.Streams.Cache +namespace YaR.Clouds.Base.Streams.Cache; + +public struct DeduplicateRule { - public struct DeduplicateRule - { - public CacheType CacheType { get; set; } - public string Target { get; set; } - public ulong MinSize { get; set; } - public ulong MaxSize { get; set; } - } + public CacheType CacheType { get; set; } + public string Target { get; set; } + public ulong MinSize { get; set; } + public ulong MaxSize { get; set; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/DiskDataCache.cs b/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/DiskDataCache.cs index a80af874..a3a76e19 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/DiskDataCache.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/DiskDataCache.cs @@ -1,34 +1,33 @@ using System; using System.IO; -namespace YaR.Clouds.Base.Streams.Cache +namespace YaR.Clouds.Base.Streams.Cache; + +internal class DiskDataCache : DataCache { - internal class DiskDataCache : DataCache - { - private readonly string _filename; + private readonly string _filename; - public override string Name => nameof(DiskDataCache); + public override string Name => nameof(DiskDataCache); - public DiskDataCache(string basePath) - { - _filename = Path.Combine(basePath, Guid.NewGuid().ToString()); - } + public DiskDataCache(string basePath) + { + _filename = Path.Combine(basePath, Guid.NewGuid().ToString()); + } - public override Stream OutStream => _outStream ??= new FileStream(_filename, FileMode.Open, FileAccess.Read); - private Stream _outStream; + public override Stream OutStream => _outStream ??= new FileStream(_filename, FileMode.Open, FileAccess.Read); + private Stream _outStream; - public override void FillFrom(Stream sourceStream) - { - using var fs = new FileStream(_filename, FileMode.Create, FileAccess.Write); - sourceStream.CopyTo(fs); - } + public override void FillFrom(Stream sourceStream) + { + using var fs = new FileStream(_filename, FileMode.Create, FileAccess.Write); + sourceStream.CopyTo(fs); + } - public override void Dispose() - { - _outStream?.Dispose(); + public override void Dispose() + { + _outStream?.Dispose(); - if (System.IO.File.Exists(_filename)) - System.IO.File.Delete(_filename); - } + if (System.IO.File.Exists(_filename)) + System.IO.File.Delete(_filename); } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/MemoryDataCache.cs b/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/MemoryDataCache.cs index b8a24a12..7548fd4b 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/MemoryDataCache.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Streams/Cache/MemoryDataCache.cs @@ -1,24 +1,23 @@ using System.IO; -namespace YaR.Clouds.Base.Streams.Cache +namespace YaR.Clouds.Base.Streams.Cache; + +internal class MemoryDataCache : DataCache { - internal class MemoryDataCache : DataCache - { - private readonly MemoryStream _ms = new(); + private readonly MemoryStream _ms = new(); - public override string Name => nameof(MemoryDataCache); + public override string Name => nameof(MemoryDataCache); - public override Stream OutStream => _ms; + public override Stream OutStream => _ms; - public override void FillFrom(Stream sourceStream) - { - sourceStream.CopyTo(_ms); - _ms.Seek(0, SeekOrigin.Begin); - } + public override void FillFrom(Stream sourceStream) + { + sourceStream.CopyTo(_ms); + _ms.Seek(0, SeekOrigin.Begin); + } - public override void Dispose() - { - _ms?.Dispose(); - } + public override void Dispose() + { + _ms?.Dispose(); } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Streams/DownloadStream.cs b/MailRuCloud/MailRuCloudApi/Base/Streams/DownloadStream.cs index 3d216511..abc6f80c 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Streams/DownloadStream.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Streams/DownloadStream.cs @@ -7,151 +7,150 @@ using System.Threading.Tasks; using YaR.Clouds.Common; -namespace YaR.Clouds.Base.Streams -{ - internal class DownloadStream : Stream - { - //private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(DownloadStream)); - - private const int InnerBufferSize = 65536 * 2; - - private readonly Func> _responseGenerator; - private readonly IList _files; - private readonly long? _start; - private readonly long? _end; +namespace YaR.Clouds.Base.Streams; - private RingBufferedStream _innerStream; - private bool _initialized; +internal class DownloadStream : Stream +{ + //private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(DownloadStream)); - public DownloadStream(Func> responseGenerator, - File file, long? start = null, long? end = null) - : this(responseGenerator, file.Parts, start, end) - { - } + private const int InnerBufferSize = 65536 * 2; - private DownloadStream(Func> responseGenerator, IList files, long? start = null, long? end = null) - { - var globalLength = files.Sum(f => f.OriginalSize); + private readonly Func> _responseGenerator; + private readonly IList _files; + private readonly long? _start; + private readonly long? _end; - _responseGenerator = responseGenerator ?? throw new ArgumentNullException(nameof(responseGenerator)); - _files = files; - _start = start; - _end = end >= globalLength ? globalLength - 1 : end; + private RingBufferedStream _innerStream; + private bool _initialized; - Length = _start != null && _end != null - ? _end.Value - _start.Value + 1 - : globalLength; + public DownloadStream(Func> responseGenerator, + File file, long? start = null, long? end = null) + : this(responseGenerator, file.Parts, start, end) + { + } - Open(); - } + private DownloadStream(Func> responseGenerator, IList files, long? start = null, long? end = null) + { + var globalLength = files.Sum(f => f.OriginalSize); - public void Open() - { - _innerStream = new RingBufferedStream(InnerBufferSize) {ReadTimeout = 15 * 1000, WriteTimeout = 15 * 1000}; - _copyTask = GetFileStream(); + _responseGenerator = responseGenerator ?? throw new ArgumentNullException(nameof(responseGenerator)); + _files = files; + _start = start; + _end = end >= globalLength ? globalLength - 1 : end; - _initialized = true; - } + Length = _start != null && _end != null + ? _end.Value - _start.Value + 1 + : globalLength; - private Task _copyTask; - - private async Task GetFileStream() - { - var totalLength = Length; - long glostart = _start ?? 0; - long gloend = _end == null || - _start == _end && _end == 0 ? totalLength : _end.Value + 1; + Open(); + } - long fileStart = 0; - long fileEnd = 0; + public void Open() + { + _innerStream = new RingBufferedStream(InnerBufferSize) { ReadTimeout = 15 * 1000, WriteTimeout = 15 * 1000 }; + _copyTask = GetFileStream(); - foreach (var file in _files) - { - fileEnd += file.OriginalSize; + _initialized = true; + } - if (glostart >= fileEnd || gloend <= fileStart) - { - fileStart += file.OriginalSize; - continue; - } + private Task _copyTask; - long clostart = Math.Max(0, glostart - fileStart); - long cloend = gloend - fileStart - 1; + private async Task GetFileStream() + { + var totalLength = Length; + long glostart = _start ?? 0; + long gloend = _end == null || + _start == _end && _end == 0 ? totalLength : _end.Value + 1; - await Download(clostart, cloend, file, _cancellationTokenSource.Token).ConfigureAwait(false); + long fileStart = 0; + long fileEnd = 0; - //_currentDownload = Download(clostart, cloend, clofile).; - //await _currentDownload.ConfigureAwait(false); + foreach (var file in _files) + { + fileEnd += file.OriginalSize; + if (glostart >= fileEnd || gloend <= fileStart) + { fileStart += file.OriginalSize; + continue; } - _innerStream.Flush(); + long clostart = Math.Max(0, glostart - fileStart); + long cloend = gloend - fileStart - 1; - return _innerStream; - } + await Download(clostart, cloend, file, _cancellationTokenSource.Token).ConfigureAwait(false); - private readonly CancellationTokenSource _cancellationTokenSource = new(); + //_currentDownload = Download(clostart, cloend, clofile).; + //await _currentDownload.ConfigureAwait(false); - private async Task Download(long start, long end, File file, CancellationToken cancellationToken) - { - using var httpweb = _responseGenerator(start, end, file); - using var responseStream = httpweb.Value.GetResponseStream(); - await responseStream.CopyToAsync(_innerStream, - /* 81920 is default buffer size in .NET */ - 16384 /* lets try to align to TCP window */, - cancellationToken: cancellationToken).ConfigureAwait(false); + fileStart += file.OriginalSize; } - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - if (!disposing) return; + _innerStream.Flush(); - _cancellationTokenSource.Cancel(); + return _innerStream; + } - _innerStream?.Flush(); - //_copyTask?.Wait(); + private readonly CancellationTokenSource _cancellationTokenSource = new(); - _innerStream?.Close(); - } + private async Task Download(long start, long end, File file, CancellationToken cancellationToken) + { + using var httpweb = _responseGenerator(start, end, file); + using var responseStream = httpweb.Value.GetResponseStream(); + await responseStream.CopyToAsync(_innerStream, + /* 81920 is default buffer size in .NET */ + 16384 /* lets try to align to TCP window */, + cancellationToken: cancellationToken).ConfigureAwait(false); + } + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + if (!disposing) return; - public override void Flush() - { - throw new NotImplementedException(); - } + _cancellationTokenSource.Cancel(); - public override long Seek(long offset, SeekOrigin origin) - { - return _innerStream.Seek(offset, origin); - } + _innerStream?.Flush(); + //_copyTask?.Wait(); - public override void SetLength(long value) - { - throw new NotImplementedException(); - } + _innerStream?.Close(); + } - public override int Read(byte[] buffer, int offset, int count) - { - if (!_initialized) - Open(); - int readed = _innerStream.Read(buffer, offset, count); - return readed; - } + public override void Flush() + { + throw new NotImplementedException(); + } - public override void Write(byte[] buffer, int offset, int count) - { - throw new NotImplementedException(); - } + public override long Seek(long offset, SeekOrigin origin) + { + return _innerStream.Seek(offset, origin); + } + + public override void SetLength(long value) + { + throw new NotImplementedException(); + } - public override bool CanRead => true; - public override bool CanSeek => true; - public override bool CanWrite => false; + public override int Read(byte[] buffer, int offset, int count) + { + if (!_initialized) + Open(); - public override long Length { get; } + int readed = _innerStream.Read(buffer, offset, count); + return readed; + } - public override long Position { get; set; } + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotImplementedException(); } + + public override bool CanRead => true; + public override bool CanSeek => true; + public override bool CanWrite => false; + + public override long Length { get; } + + public override long Position { get; set; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Streams/PushStreamContent.cs b/MailRuCloud/MailRuCloudApi/Base/Streams/PushStreamContent.cs index d1068071..5a48452d 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Streams/PushStreamContent.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Streams/PushStreamContent.cs @@ -7,203 +7,202 @@ using System.Threading; using System.Threading.Tasks; -namespace YaR.Clouds.Base.Streams +namespace YaR.Clouds.Base.Streams; + +/// +/// Provides an implementation that exposes an output +/// which can be written to directly. The ability to push data to the output stream differs from the +/// where data is pulled and not pushed. +/// +public class PushStreamContent : HttpContent { - /// - /// Provides an implementation that exposes an output - /// which can be written to directly. The ability to push data to the output stream differs from the - /// where data is pulled and not pushed. - /// - public class PushStreamContent : HttpContent + private readonly Func _onStreamAvailable; + + public PushStreamContent(Action onStreamAvailable) + : this(Taskify(onStreamAvailable)) { - private readonly Func _onStreamAvailable; + } - public PushStreamContent(Action onStreamAvailable) - : this(Taskify(onStreamAvailable)) - { - } + public PushStreamContent(Action onStreamAvailable, string mediaType) + : this(Taskify(onStreamAvailable), new MediaTypeHeaderValue(mediaType)) + { + } - public PushStreamContent(Action onStreamAvailable, string mediaType) - : this(Taskify(onStreamAvailable), new MediaTypeHeaderValue(mediaType)) - { - } + public PushStreamContent(Func onStreamAvailable, string mediaType) + : this(onStreamAvailable, new MediaTypeHeaderValue(mediaType)) + { + } - public PushStreamContent(Func onStreamAvailable, string mediaType) - : this(onStreamAvailable, new MediaTypeHeaderValue(mediaType)) - { - } + public PushStreamContent(Action onStreamAvailable, MediaTypeHeaderValue mediaType) + : this(Taskify(onStreamAvailable), mediaType) + { + } - public PushStreamContent(Action onStreamAvailable, MediaTypeHeaderValue mediaType) - : this(Taskify(onStreamAvailable), mediaType) - { - } + public PushStreamContent(Func onStreamAvailable, MediaTypeHeaderValue mediaType = null) + { + _onStreamAvailable = onStreamAvailable ?? throw new ArgumentNullException(nameof(onStreamAvailable)); + Headers.ContentType = mediaType ?? new MediaTypeHeaderValue("application/octet-stream"); + } - public PushStreamContent(Func onStreamAvailable, MediaTypeHeaderValue mediaType = null) + private static Func Taskify(Action onStreamAvailable) + { + if (onStreamAvailable == null) + throw new ArgumentNullException(nameof(onStreamAvailable)); + return (stream, content, transportContext) => { - _onStreamAvailable = onStreamAvailable ?? throw new ArgumentNullException(nameof(onStreamAvailable)); - Headers.ContentType = mediaType ?? new MediaTypeHeaderValue("application/octet-stream"); - } + onStreamAvailable(stream, content, transportContext); + return Completed(); + }; + } - private static Func Taskify(Action onStreamAvailable) - { - if (onStreamAvailable == null) - throw new ArgumentNullException(nameof(onStreamAvailable)); - return (stream, content, transportContext) => - { - onStreamAvailable(stream, content, transportContext); - return Completed(); - }; - } + private static Task Completed() + { + return DefaultCompleted; + } - private static Task Completed() - { - return DefaultCompleted; - } + private static readonly Task DefaultCompleted = Task.FromResult(new AsyncVoid()); + [StructLayout(LayoutKind.Sequential, Size = 1)] + private struct AsyncVoid + { + } - private static readonly Task DefaultCompleted = Task.FromResult(new AsyncVoid()); - [StructLayout(LayoutKind.Sequential, Size = 1)] - private struct AsyncVoid - { - } + /// + /// When this method is called, it calls the action provided in the constructor with the output + /// stream to write to. Once the action has completed its work it closes the stream which will + /// close this content instance and complete the HTTP request or response. + /// + /// The to which to write. + /// The associated . + /// A instance that is asynchronously serializing the object's content. + protected override async Task SerializeToStreamAsync(Stream stream, TransportContext context) + { + TaskCompletionSource serializeToStreamTask = new TaskCompletionSource(); + await _onStreamAvailable(new CompleteTaskOnCloseStream(stream, serializeToStreamTask), this, context); + await serializeToStreamTask.Task; + } - /// - /// When this method is called, it calls the action provided in the constructor with the output - /// stream to write to. Once the action has completed its work it closes the stream which will - /// close this content instance and complete the HTTP request or response. - /// - /// The to which to write. - /// The associated . - /// A instance that is asynchronously serializing the object's content. - protected override async Task SerializeToStreamAsync(Stream stream, TransportContext context) - { - TaskCompletionSource serializeToStreamTask = new TaskCompletionSource(); - await _onStreamAvailable(new CompleteTaskOnCloseStream(stream, serializeToStreamTask), this, context); - await serializeToStreamTask.Task; - } + /// Computes the length of the stream if possible. + /// The computed length of the stream. + /// true if the length has been computed; otherwise false. + protected override bool TryComputeLength(out long length) + { + length = -1L; + return false; + } - /// Computes the length of the stream if possible. - /// The computed length of the stream. - /// true if the length has been computed; otherwise false. - protected override bool TryComputeLength(out long length) + private class CompleteTaskOnCloseStream : DelegatingStream + { + private readonly TaskCompletionSource _serializeToStreamTask; + + public CompleteTaskOnCloseStream(Stream innerStream, TaskCompletionSource serializeToStreamTask) + : base(innerStream) { - length = -1L; - return false; + _serializeToStreamTask = serializeToStreamTask; } - private class CompleteTaskOnCloseStream : DelegatingStream + protected override void Dispose(bool disposing) { - private readonly TaskCompletionSource _serializeToStreamTask; - - public CompleteTaskOnCloseStream(Stream innerStream, TaskCompletionSource serializeToStreamTask) - : base(innerStream) - { - _serializeToStreamTask = serializeToStreamTask; - } - - protected override void Dispose(bool disposing) - { - _serializeToStreamTask.TrySetResult(true); - } + _serializeToStreamTask.TrySetResult(true); } } +} - internal abstract class DelegatingStream : Stream +internal abstract class DelegatingStream : Stream +{ + protected DelegatingStream(Stream innerStream) { - protected DelegatingStream(Stream innerStream) - { - InnerStream = innerStream ?? throw new ArgumentNullException(nameof(innerStream)); - } + InnerStream = innerStream ?? throw new ArgumentNullException(nameof(innerStream)); + } - private Stream InnerStream { get; } + private Stream InnerStream { get; } - public override bool CanRead => InnerStream.CanRead; + public override bool CanRead => InnerStream.CanRead; - public override bool CanSeek => InnerStream.CanSeek; + public override bool CanSeek => InnerStream.CanSeek; - public override bool CanWrite => InnerStream.CanWrite; + public override bool CanWrite => InnerStream.CanWrite; - public override long Length => InnerStream.Length; + public override long Length => InnerStream.Length; - public override long Position - { - get => InnerStream.Position; - set => InnerStream.Position = value; - } + public override long Position + { + get => InnerStream.Position; + set => InnerStream.Position = value; + } - public override int ReadTimeout - { - get => InnerStream.ReadTimeout; - set => InnerStream.ReadTimeout = value; - } + public override int ReadTimeout + { + get => InnerStream.ReadTimeout; + set => InnerStream.ReadTimeout = value; + } - public override bool CanTimeout => InnerStream.CanTimeout; + public override bool CanTimeout => InnerStream.CanTimeout; - public override int WriteTimeout - { - get => InnerStream.WriteTimeout; - set => InnerStream.WriteTimeout = value; - } + public override int WriteTimeout + { + get => InnerStream.WriteTimeout; + set => InnerStream.WriteTimeout = value; + } - protected override void Dispose(bool disposing) - { - if (disposing) - InnerStream.Dispose(); - base.Dispose(disposing); - } + protected override void Dispose(bool disposing) + { + if (disposing) + InnerStream.Dispose(); + base.Dispose(disposing); + } - public override long Seek(long offset, SeekOrigin origin) - { - return InnerStream.Seek(offset, origin); - } + public override long Seek(long offset, SeekOrigin origin) + { + return InnerStream.Seek(offset, origin); + } - public override int Read(byte[] buffer, int offset, int count) - { - return InnerStream.Read(buffer, offset, count); - } + public override int Read(byte[] buffer, int offset, int count) + { + return InnerStream.Read(buffer, offset, count); + } - public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - return InnerStream.ReadAsync(buffer, offset, count, cancellationToken); - } + public override Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + return InnerStream.ReadAsync(buffer, offset, count, cancellationToken); + } - public override int ReadByte() - { - return InnerStream.ReadByte(); - } + public override int ReadByte() + { + return InnerStream.ReadByte(); + } - public override void Flush() - { - InnerStream.Flush(); - } + public override void Flush() + { + InnerStream.Flush(); + } - public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) - { - return InnerStream.CopyToAsync(destination, bufferSize, cancellationToken); - } + public override Task CopyToAsync(Stream destination, int bufferSize, CancellationToken cancellationToken) + { + return InnerStream.CopyToAsync(destination, bufferSize, cancellationToken); + } - public override Task FlushAsync(CancellationToken cancellationToken) - { - return InnerStream.FlushAsync(cancellationToken); - } + public override Task FlushAsync(CancellationToken cancellationToken) + { + return InnerStream.FlushAsync(cancellationToken); + } - public override void SetLength(long value) - { - InnerStream.SetLength(value); - } + public override void SetLength(long value) + { + InnerStream.SetLength(value); + } - public override void Write(byte[] buffer, int offset, int count) - { - InnerStream.Write(buffer, offset, count); - } + public override void Write(byte[] buffer, int offset, int count) + { + InnerStream.Write(buffer, offset, count); + } - public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) - { - return InnerStream.WriteAsync(buffer, offset, count, cancellationToken); - } + public override Task WriteAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) + { + return InnerStream.WriteAsync(buffer, offset, count, cancellationToken); + } - public override void WriteByte(byte value) - { - InnerStream.WriteByte(value); - } + public override void WriteByte(byte value) + { + InnerStream.WriteByte(value); } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Streams/RingBufferedStream.cs b/MailRuCloud/MailRuCloudApi/Base/Streams/RingBufferedStream.cs index f94e8767..9c0a93d7 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Streams/RingBufferedStream.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Streams/RingBufferedStream.cs @@ -4,589 +4,588 @@ using System.Threading.Tasks; using YaR.Clouds.Extensions; -namespace YaR.Clouds.Base.Streams +namespace YaR.Clouds.Base.Streams; + +/// +/// A ring-buffer stream that you can read from and write to from +/// different threads. +/// +public class RingBufferedStream : Stream { - /// - /// A ring-buffer stream that you can read from and write to from - /// different threads. - /// - public class RingBufferedStream : Stream - { - private readonly byte[] _store; + private readonly byte[] _store; - private readonly ManualResetEventAsync _writeAvailable = new(false); + private readonly ManualResetEventAsync _writeAvailable = new(false); - private readonly ManualResetEventAsync _readAvailable = new(false); + private readonly ManualResetEventAsync _readAvailable = new(false); - private readonly ManualResetEvent _flushed = new(false); + private readonly ManualResetEvent _flushed = new(false); - private readonly CancellationTokenSource _cancellationTokenSource = new(); + private readonly CancellationTokenSource _cancellationTokenSource = new(); - private int _readPos; + private int _readPos; - private int _readAvailableByteCount; + private int _readAvailableByteCount; - private int _writePos; + private int _writePos; - private int _writeAvailableByteCount; + private int _writeAvailableByteCount; - private bool _disposed; + private bool _disposed; - /// - /// Initializes a new instance of the - /// class. - /// - /// - /// The maximum number of bytes to buffer. - /// - public RingBufferedStream(int bufferSize) - { - _store = new byte[bufferSize]; - _writeAvailableByteCount = bufferSize; - _readAvailableByteCount = 0; - } + /// + /// Initializes a new instance of the + /// class. + /// + /// + /// The maximum number of bytes to buffer. + /// + public RingBufferedStream(int bufferSize) + { + _store = new byte[bufferSize]; + _writeAvailableByteCount = bufferSize; + _readAvailableByteCount = 0; + } - public override bool CanRead => true; + public override bool CanRead => true; - public override bool CanSeek => false; + public override bool CanSeek => false; - public override bool CanWrite => true; + public override bool CanWrite => true; - public override long Length => throw new NotSupportedException("Cannot get length on RingBufferedStream"); + public override long Length => throw new NotSupportedException("Cannot get length on RingBufferedStream"); - /// - public override int ReadTimeout { get; set; } = Timeout.Infinite; + /// + public override int ReadTimeout { get; set; } = Timeout.Infinite; - /// - public override int WriteTimeout { get; set; } = Timeout.Infinite; + /// + public override int WriteTimeout { get; set; } = Timeout.Infinite; - /// - public override long Position - { - get => throw new NotSupportedException("Cannot set position on RingBufferedStream"); + /// + public override long Position + { + get => throw new NotSupportedException("Cannot set position on RingBufferedStream"); - set => throw new NotSupportedException("Cannot set position on RingBufferedStream"); - } + set => throw new NotSupportedException("Cannot set position on RingBufferedStream"); + } - /// - public override void Flush() - { - if (_disposed) - return; + /// + public override void Flush() + { + if (_disposed) + return; - _flushed.Set(); - _cancellationTokenSource?.Cancel(); - } + _flushed.Set(); + _cancellationTokenSource?.Cancel(); + } - /// - /// Set the length of the current stream. Always throws . - /// - /// - /// The desired length of the current stream in bytes. - /// - public override void SetLength(long value) - { - throw new NotSupportedException( - "Cannot set length on RingBufferedStream"); - } + /// + /// Set the length of the current stream. Always throws . + /// + /// + /// The desired length of the current stream in bytes. + /// + public override void SetLength(long value) + { + throw new NotSupportedException( + "Cannot set length on RingBufferedStream"); + } + + /// + /// Sets the position in the current stream. Always throws . + /// + /// + /// The byte offset to the parameter. + /// + /// + /// A value of type indicating the reference + /// point used to obtain the new position. + /// + /// + /// The new position within the current stream. + /// + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException("Cannot seek on RingBufferedStream"); + } - /// - /// Sets the position in the current stream. Always throws . - /// - /// - /// The byte offset to the parameter. - /// - /// - /// A value of type indicating the reference - /// point used to obtain the new position. - /// - /// - /// The new position within the current stream. - /// - public override long Seek(long offset, SeekOrigin origin) + /// + public override void Write(byte[] buffer, int offset, int count) + { + if (_disposed) { - throw new NotSupportedException("Cannot seek on RingBufferedStream"); + //throw new ObjectDisposedException("RingBufferedStream"); + return; } - /// - public override void Write(byte[] buffer, int offset, int count) + Monitor.Enter(_store); + bool haveLock = true; + try { - if (_disposed) - { - //throw new ObjectDisposedException("RingBufferedStream"); - return; - } - - Monitor.Enter(_store); - bool haveLock = true; - try + while (count > 0) { - while (count > 0) + if (_writeAvailableByteCount == 0) { - if (_writeAvailableByteCount == 0) + _writeAvailable.Reset(); + Monitor.Exit(_store); + haveLock = false; + if (!_writeAvailable.Wait(WriteTimeout, _cancellationTokenSource.Token, out var canceled) || canceled) { - _writeAvailable.Reset(); - Monitor.Exit(_store); - haveLock = false; - if (!_writeAvailable.Wait(WriteTimeout, _cancellationTokenSource.Token, out var canceled) || canceled) - { - break; - } - - Monitor.Enter(_store); - haveLock = true; + break; } - else + + Monitor.Enter(_store); + haveLock = true; + } + else + { + var toWrite = _store.Length - _writePos; + if (toWrite > _writeAvailableByteCount) { - var toWrite = _store.Length - _writePos; - if (toWrite > _writeAvailableByteCount) - { - toWrite = _writeAvailableByteCount; - } - - if (toWrite > count) - toWrite = count; - - Array.Copy(buffer, offset, _store, _writePos, toWrite); - offset += toWrite; - count -= toWrite; - _writeAvailableByteCount -= toWrite; - _readAvailableByteCount += toWrite; - _writePos += toWrite; - if (_writePos == _store.Length) - _writePos = 0; - - _readAvailable.Set(); + toWrite = _writeAvailableByteCount; } + + if (toWrite > count) + toWrite = count; + + Array.Copy(buffer, offset, _store, _writePos, toWrite); + offset += toWrite; + count -= toWrite; + _writeAvailableByteCount -= toWrite; + _readAvailableByteCount += toWrite; + _writePos += toWrite; + if (_writePos == _store.Length) + _writePos = 0; + + _readAvailable.Set(); } } - finally + } + finally + { + if (haveLock) { - if (haveLock) - { - Monitor.Exit(_store); - } + Monitor.Exit(_store); } } + } - /// - public override void WriteByte(byte value) - { - if (_disposed) - throw new ObjectDisposedException("RingBufferedStream"); + /// + public override void WriteByte(byte value) + { + if (_disposed) + throw new ObjectDisposedException("RingBufferedStream"); - Monitor.Enter(_store); - bool haveLock = true; - try + Monitor.Enter(_store); + bool haveLock = true; + try + { + while (true) { - while (true) + if (_writeAvailableByteCount == 0) { - if (_writeAvailableByteCount == 0) - { - _writeAvailable.Reset(); - Monitor.Exit(_store); - haveLock = false; - if (!_writeAvailable.Wait(WriteTimeout, _cancellationTokenSource.Token, out var canceled) || canceled) - break; - - Monitor.Enter(_store); - haveLock = true; - } - else - { - _store[_writePos] = value; - --_writeAvailableByteCount; - ++_readAvailableByteCount; - ++_writePos; - if (_writePos == _store.Length) - _writePos = 0; - - _readAvailable.Set(); + _writeAvailable.Reset(); + Monitor.Exit(_store); + haveLock = false; + if (!_writeAvailable.Wait(WriteTimeout, _cancellationTokenSource.Token, out var canceled) || canceled) break; - } + + Monitor.Enter(_store); + haveLock = true; } - } - finally - { - if (haveLock) + else { - Monitor.Exit(_store); + _store[_writePos] = value; + --_writeAvailableByteCount; + ++_readAvailableByteCount; + ++_writePos; + if (_writePos == _store.Length) + _writePos = 0; + + _readAvailable.Set(); + break; } } } - - /// - public override int Read(byte[] buffer, int offset, int count) + finally { - if (_disposed) - throw new ObjectDisposedException("RingBufferedStream"); - - Monitor.Enter(_store); - int ret = 0; - bool haveLock = true; - try + if (haveLock) { - while (count > 0 ) - { - if (_readAvailableByteCount == 0) - { - _readAvailable.Reset(); - Monitor.Exit(_store); - haveLock = false; - if (!_readAvailable.Wait(ReadTimeout, _cancellationTokenSource.Token, out var canceled) || canceled) - break; - - Monitor.Enter(_store); - haveLock = true; - } - else - { - var toRead = _store.Length - _readPos; - if (toRead > _readAvailableByteCount) - toRead = _readAvailableByteCount; - - if (toRead > count) - toRead = count; - - Array.Copy(_store, _readPos, buffer, offset, toRead); - offset += toRead; - count -= toRead; - _readAvailableByteCount -= toRead; - _writeAvailableByteCount += toRead; - ret += toRead; - _readPos += toRead; - if (_readPos == _store.Length) - _readPos = 0; - - _writeAvailable.Set(); - - if (_flushed.WaitOne(0)) return ret; - } - } + Monitor.Exit(_store); } - finally - { - if (haveLock) - Monitor.Exit(_store); - } - - return ret; } + } - /// - public override int ReadByte() - { - if (_disposed) - throw new ObjectDisposedException("RingBufferedStream"); + /// + public override int Read(byte[] buffer, int offset, int count) + { + if (_disposed) + throw new ObjectDisposedException("RingBufferedStream"); - Monitor.Enter(_store); - int ret = -1; - bool haveLock = true; - try + Monitor.Enter(_store); + int ret = 0; + bool haveLock = true; + try + { + while (count > 0 ) { - while (true) + if (_readAvailableByteCount == 0) { - if (_readAvailableByteCount == 0) - { - _readAvailable.Reset(); - Monitor.Exit(_store); - haveLock = false; - if (!_readAvailable.Wait(ReadTimeout, _cancellationTokenSource.Token, out var canceled) || canceled) - break; - - Monitor.Enter(_store); - haveLock = true; - } - else - { - ret = _store[_readPos]; - ++_writeAvailableByteCount; - --_readAvailableByteCount; - ++_readPos; - if (_readPos == _store.Length) - _readPos = 0; - - _writeAvailable.Set(); + _readAvailable.Reset(); + Monitor.Exit(_store); + haveLock = false; + if (!_readAvailable.Wait(ReadTimeout, _cancellationTokenSource.Token, out var canceled) || canceled) break; - } + + Monitor.Enter(_store); + haveLock = true; + } + else + { + var toRead = _store.Length - _readPos; + if (toRead > _readAvailableByteCount) + toRead = _readAvailableByteCount; + + if (toRead > count) + toRead = count; + + Array.Copy(_store, _readPos, buffer, offset, toRead); + offset += toRead; + count -= toRead; + _readAvailableByteCount -= toRead; + _writeAvailableByteCount += toRead; + ret += toRead; + _readPos += toRead; + if (_readPos == _store.Length) + _readPos = 0; + + _writeAvailable.Set(); + + if (_flushed.WaitOne(0)) return ret; } } - finally - { - if (haveLock) - Monitor.Exit(_store); - } - - return ret; } - - //public override void Close() - //{ - // _cancellationTokenSource.Cancel(); - // base.Close(); - //} - - /// - protected override void Dispose(bool disposing) + finally { - base.Dispose(disposing); - if (!disposing) return; - - _disposed = true; - //_cancellationTokenSource.Cancel(); - //_cancellationTokenSource.Dispose(); + if (haveLock) + Monitor.Exit(_store); } + + return ret; } - public sealed class ManualResetEventAsync + /// + public override int ReadByte() { - /// - /// The task completion source. - /// - private volatile TaskCompletionSource _taskCompletionSource = - new(); - - /// - /// Initializes a new instance of the - /// class with a value indicating whether to set the - /// initial state to signaled. - /// - /// - /// True to set the initial state to signaled; false to set the initial - /// state to non-signaled. - /// - public ManualResetEventAsync(bool initialState) + if (_disposed) + throw new ObjectDisposedException("RingBufferedStream"); + + Monitor.Enter(_store); + int ret = -1; + bool haveLock = true; + try { - if (initialState) + while (true) { - Set(); + if (_readAvailableByteCount == 0) + { + _readAvailable.Reset(); + Monitor.Exit(_store); + haveLock = false; + if (!_readAvailable.Wait(ReadTimeout, _cancellationTokenSource.Token, out var canceled) || canceled) + break; + + Monitor.Enter(_store); + haveLock = true; + } + else + { + ret = _store[_readPos]; + ++_writeAvailableByteCount; + --_readAvailableByteCount; + ++_readPos; + if (_readPos == _store.Length) + _readPos = 0; + + _writeAvailable.Set(); + break; + } } } - - /// - /// Return a task that can be consumed by - /// - /// - /// The asynchronous waiter. - /// - public Task GetWaitTask() + finally { - return _taskCompletionSource.Task; + if (haveLock) + Monitor.Exit(_store); } - /// - /// Mark the event as signaled. - /// - public void Set() + return ret; + } + + //public override void Close() + //{ + // _cancellationTokenSource.Cancel(); + // base.Close(); + //} + + /// + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + if (!disposing) return; + + _disposed = true; + //_cancellationTokenSource.Cancel(); + //_cancellationTokenSource.Dispose(); + } +} + +public sealed class ManualResetEventAsync +{ + /// + /// The task completion source. + /// + private volatile TaskCompletionSource _taskCompletionSource = + new(); + + /// + /// Initializes a new instance of the + /// class with a value indicating whether to set the + /// initial state to signaled. + /// + /// + /// True to set the initial state to signaled; false to set the initial + /// state to non-signaled. + /// + public ManualResetEventAsync(bool initialState) + { + if (initialState) { - var tcs = _taskCompletionSource; - Task.Factory.StartNew( - s => ((TaskCompletionSource)s).TrySetResult(true), - tcs, - CancellationToken.None, - TaskCreationOptions.PreferFairness, - TaskScheduler.Default); - tcs.Task.Wait(); + Set(); } + } + + /// + /// Return a task that can be consumed by + /// + /// + /// The asynchronous waiter. + /// + public Task GetWaitTask() + { + return _taskCompletionSource.Task; + } + + /// + /// Mark the event as signaled. + /// + public void Set() + { + var tcs = _taskCompletionSource; + Task.Factory.StartNew( + s => ((TaskCompletionSource)s).TrySetResult(true), + tcs, + CancellationToken.None, + TaskCreationOptions.PreferFairness, + TaskScheduler.Default); + tcs.Task.Wait(); + } - /// - /// Mark the event as not signaled. - /// - public void Reset() + /// + /// Mark the event as not signaled. + /// + public void Reset() + { + while (true) { - while (true) - { - var tcs = _taskCompletionSource; - if (!tcs.Task.IsCompleted + var tcs = _taskCompletionSource; + if (!tcs.Task.IsCompleted #pragma warning disable 420 - || Interlocked.CompareExchange( - ref _taskCompletionSource, - new TaskCompletionSource(), - tcs) == tcs) + || Interlocked.CompareExchange( + ref _taskCompletionSource, + new TaskCompletionSource(), + tcs) == tcs) #pragma warning restore 420 - { - return; - } + { + return; } } + } - /// - /// Waits for the to be signaled. - /// - /// - /// The waiting - /// was canceled -or- an exception was thrown during the execution - /// of the waiting . - /// - public void Wait() - { - GetWaitTask().Wait(); - } + /// + /// Waits for the to be signaled. + /// + /// + /// The waiting + /// was canceled -or- an exception was thrown during the execution + /// of the waiting . + /// + public void Wait() + { + GetWaitTask().Wait(); + } - /// - /// Waits for the to be signaled. - /// - /// - /// A to observe while waiting for - /// the task to complete. - /// - /// - /// The was canceled. - /// - /// - /// The waiting was - /// canceled -or- an exception was thrown during the execution of the - /// waiting . - /// - public void Wait(CancellationToken cancellationToken) + /// + /// Waits for the to be signaled. + /// + /// + /// A to observe while waiting for + /// the task to complete. + /// + /// + /// The was canceled. + /// + /// + /// The waiting was + /// canceled -or- an exception was thrown during the execution of the + /// waiting . + /// + public void Wait(CancellationToken cancellationToken) + { + GetWaitTask().Wait(cancellationToken); + } + + /// + /// Waits for the to be signaled. + /// + /// + /// A to observe while waiting for + /// the task to complete. + /// + /// + /// Set to true if the wait was canceled via the . + /// + public void Wait(CancellationToken cancellationToken, out bool canceled) + { + try { GetWaitTask().Wait(cancellationToken); + canceled = false; } - - /// - /// Waits for the to be signaled. - /// - /// - /// A to observe while waiting for - /// the task to complete. - /// - /// - /// Set to true if the wait was canceled via the . - /// - public void Wait(CancellationToken cancellationToken, out bool canceled) + catch (Exception ex) when (ex.Contains()) { - try - { - GetWaitTask().Wait(cancellationToken); - canceled = false; - } - catch (Exception ex) when (ex.Contains()) - { - canceled = true; - } + canceled = true; } + } - /// - /// Waits for the to be signaled. - /// - /// - /// A that represents the number of - /// milliseconds to wait, or a that - /// represents -1 milliseconds to wait indefinitely. - /// - /// - /// true if the was signaled within - /// the allotted time; otherwise, false. - /// - /// - /// is a negative number other than -1 - /// milliseconds, which represents an infinite time-out -or- - /// timeout is greater than . - /// - public bool Wait(TimeSpan timeout) - { - return GetWaitTask().Wait(timeout); - } + /// + /// Waits for the to be signaled. + /// + /// + /// A that represents the number of + /// milliseconds to wait, or a that + /// represents -1 milliseconds to wait indefinitely. + /// + /// + /// true if the was signaled within + /// the allotted time; otherwise, false. + /// + /// + /// is a negative number other than -1 + /// milliseconds, which represents an infinite time-out -or- + /// timeout is greater than . + /// + public bool Wait(TimeSpan timeout) + { + return GetWaitTask().Wait(timeout); + } - /// - /// Waits for the to be signaled. - /// - /// - /// The number of milliseconds to wait, or - /// (-1) to wait - /// indefinitely. - /// - /// - /// true if the was signaled within - /// the allotted time; otherwise, false. - /// - /// - /// is a negative number other - /// than -1, which represents an infinite time-out. - /// - public bool Wait(int millisecondsTimeout) - { - return GetWaitTask().Wait(millisecondsTimeout); - } + /// + /// Waits for the to be signaled. + /// + /// + /// The number of milliseconds to wait, or + /// (-1) to wait + /// indefinitely. + /// + /// + /// true if the was signaled within + /// the allotted time; otherwise, false. + /// + /// + /// is a negative number other + /// than -1, which represents an infinite time-out. + /// + public bool Wait(int millisecondsTimeout) + { + return GetWaitTask().Wait(millisecondsTimeout); + } - /// - /// Waits for the to be signaled. - /// - /// - /// The number of milliseconds to wait, or - /// (-1) to wait - /// indefinitely. - /// - /// - /// A to observe while waiting for the - /// to be signaled. - /// - /// - /// true if the was signaled within - /// the allotted time; otherwise, false. - /// - /// - /// The waiting - /// was canceled -or- an exception was thrown during the execution of - /// the waiting . - /// - /// - /// is a negative number other - /// than -1, which represents an infinite time-out. - /// - /// - /// The was canceled. - /// - public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken) + /// + /// Waits for the to be signaled. + /// + /// + /// The number of milliseconds to wait, or + /// (-1) to wait + /// indefinitely. + /// + /// + /// A to observe while waiting for the + /// to be signaled. + /// + /// + /// true if the was signaled within + /// the allotted time; otherwise, false. + /// + /// + /// The waiting + /// was canceled -or- an exception was thrown during the execution of + /// the waiting . + /// + /// + /// is a negative number other + /// than -1, which represents an infinite time-out. + /// + /// + /// The was canceled. + /// + public bool Wait(int millisecondsTimeout, CancellationToken cancellationToken) + { + return GetWaitTask().Wait(millisecondsTimeout, cancellationToken); + } + + /// + /// Waits for the to be signaled. + /// + /// + /// The number of milliseconds to wait, or + /// (-1) to wait + /// indefinitely. + /// + /// + /// A to observe while waiting for the + /// to be signaled. + /// + /// + /// Set to true if the wait was canceled via the . + /// + /// + /// true if the was signaled within + /// the allotted time; otherwise, false. + /// + /// + /// is a negative number other + /// than -1, which represents an infinite time-out. + /// + public bool Wait( + int millisecondsTimeout, + CancellationToken cancellationToken, + out bool canceled) + { + bool ret = false; + try { - return GetWaitTask().Wait(millisecondsTimeout, cancellationToken); + ret = GetWaitTask().Wait(millisecondsTimeout, cancellationToken); + canceled = false; } - - /// - /// Waits for the to be signaled. - /// - /// - /// The number of milliseconds to wait, or - /// (-1) to wait - /// indefinitely. - /// - /// - /// A to observe while waiting for the - /// to be signaled. - /// - /// - /// Set to true if the wait was canceled via the . - /// - /// - /// true if the was signaled within - /// the allotted time; otherwise, false. - /// - /// - /// is a negative number other - /// than -1, which represents an infinite time-out. - /// - public bool Wait( - int millisecondsTimeout, - CancellationToken cancellationToken, - out bool canceled) + catch (Exception ex) when (ex.Contains()) { - bool ret = false; - try - { - ret = GetWaitTask().Wait(millisecondsTimeout, cancellationToken); - canceled = false; - } - catch (Exception ex) when (ex.Contains()) - { - canceled = true; - } - - return ret; + canceled = true; } + + return ret; } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Streams/UploadStream.cs b/MailRuCloud/MailRuCloudApi/Base/Streams/UploadStream.cs index 495af5fd..1ddfda53 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Streams/UploadStream.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Streams/UploadStream.cs @@ -1,9 +1,8 @@ -namespace YaR.Clouds.Base.Streams +namespace YaR.Clouds.Base.Streams; + +internal class UploadStream : UploadStreamHttpClient { - internal class UploadStream : UploadStreamHttpClient + public UploadStream(string destinationPath, Cloud cloud, long size) : base(destinationPath, cloud, size) { - public UploadStream(string destinationPath, Cloud cloud, long size) : base(destinationPath, cloud, size) - { - } } } diff --git a/MailRuCloud/MailRuCloudApi/Base/Streams/UploadStreamHttpClient.cs b/MailRuCloud/MailRuCloudApi/Base/Streams/UploadStreamHttpClient.cs index 0a969201..06f8fa7b 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Streams/UploadStreamHttpClient.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Streams/UploadStreamHttpClient.cs @@ -6,219 +6,218 @@ using YaR.Clouds.Base.Streams.Cache; using YaR.Clouds.Extensions; -namespace YaR.Clouds.Base.Streams +namespace YaR.Clouds.Base.Streams; + +/// +/// Upload stream based on HttpClient +/// +/// Suitable for .NET Core, on .NET desktop POST requests does not return response content. +abstract class UploadStreamHttpClient : Stream { - /// - /// Upload stream based on HttpClient - /// - /// Suitable for .NET Core, on .NET desktop POST requests does not return response content. - abstract class UploadStreamHttpClient : Stream + private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(UploadStreamHttpClient)); + + protected UploadStreamHttpClient(string destinationPath, Cloud cloud, long size) { - private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(UploadStreamHttpClient)); + _cloud = cloud; + _file = new File(destinationPath, size); + _cloudFileHasher = _cloud.RequestRepo.GetHasher(); - protected UploadStreamHttpClient(string destinationPath, Cloud cloud, long size) - { - _cloud = cloud; - _file = new File(destinationPath, size); - _cloudFileHasher = _cloud.RequestRepo.GetHasher(); + Initialize(); + } + + public Action FileStreamSent = null; + private void OnFileStreamSent() => FileStreamSent?.Invoke(); + + public Action ServerFileProcessed = null; + private void OnServerFileProcessed() => ServerFileProcessed?.Invoke(); - Initialize(); + + private void Initialize() + { + _uploadTask = Task.Run(Upload); + } + + private void Upload() + { + try + { + if (_cloud.RequestRepo.SupportsAddSmallFileByHash && _file.OriginalSize <= _cloudFileHasher.Length) // do not send upload request if file content fits to hash + UploadSmall(_ringBuffer); + else if (_cloud.RequestRepo.SupportsDeduplicate && _cloud.Settings.UseDeduplicate) // && !_file.ServiceInfo.IsCrypted) // && !_file.ServiceInfo.SplitInfo.IsPart) + UploadCache(_ringBuffer); + else + UploadFull(_ringBuffer); + } + catch (Exception e) + { + Logger.Error($"Uploading to {_file.FullPath} failed with {e}"); //TODO remove duplicate exception catch? + throw; } + } - public Action FileStreamSent = null; - private void OnFileStreamSent() => FileStreamSent?.Invoke(); + private void UploadSmall(Stream sourceStream) + { + Logger.Debug($"Uploading [small file] {_file.FullPath}"); - public Action ServerFileProcessed = null; - private void OnServerFileProcessed() => ServerFileProcessed?.Invoke(); + sourceStream.CopyTo(Null); + OnFileStreamSent(); + _file.Hash = _cloudFileHasher?.Hash; - private void Initialize() + _cloud.AddFileInCloud(_file, ConflictResolver.Rewrite) + .Result + .ThrowIf(r => !r.Success, _ => new Exception($"Cannot add file {_file.FullPath}")); + } + + private void UploadCache(Stream sourceStream) + { + using var cache = new CacheStream(_file, sourceStream, _cloud.Settings.DeduplicateRules); + + if (cache.Process()) { - _uploadTask = Task.Run(Upload); + Logger.Debug($"Uploading [{cache.DataCacheName}] {_file.FullPath}"); + + OnFileStreamSent(); + + _file.Hash = _cloudFileHasher.Hash; + bool added = _cloud.AddFileInCloud(_file, ConflictResolver.Rewrite) + .Result + .Success; + + if (!added) + UploadFull(cache.Stream, false); + } + else + { + UploadFull(sourceStream); } + } + + private void UploadFull(Stream sourceStream, bool doInvokeFileStreamSent = true) + { + Logger.Debug($"Uploading [direct] {_file.FullPath}"); - private void Upload() + var pushContent = new PushStreamContent((stream, _, _) => { try { - if (_cloud.RequestRepo.SupportsAddSmallFileByHash && _file.OriginalSize <= _cloudFileHasher.Length) // do not send upload request if file content fits to hash - UploadSmall(_ringBuffer); - else if (_cloud.RequestRepo.SupportsDeduplicate && _cloud.Settings.UseDeduplicate) // && !_file.ServiceInfo.IsCrypted) // && !_file.ServiceInfo.SplitInfo.IsPart) - UploadCache(_ringBuffer); - else - UploadFull(_ringBuffer); + sourceStream.CopyTo(stream); + stream.Flush(); + stream.Close(); + if (doInvokeFileStreamSent) + OnFileStreamSent(); } catch (Exception e) { - Logger.Error($"Uploading to {_file.FullPath} failed with {e}"); //TODO remove duplicate exception catch? + Logger.Error($"(inner) Uploading to {_file.FullPath} failed with {e}"); throw; } - } + }); - private void UploadSmall(Stream sourceStream) + var client = HttpClientFabric.Instance[_cloud]; + DateTime timestampBeforeOperation = DateTime.Now; + try { - Logger.Debug($"Uploading [small file] {_file.FullPath}"); + _cloud.OnBeforeUpload(_file.FullPath); - sourceStream.CopyTo(Null); - OnFileStreamSent(); + var uploadFileResult = _cloud.RequestRepo.DoUpload(client, pushContent, _file).Result; - _file.Hash = _cloudFileHasher?.Hash; + if (uploadFileResult.HttpStatusCode != HttpStatusCode.Created && + uploadFileResult.HttpStatusCode != HttpStatusCode.OK) + throw new Exception("Cannot upload file, status " + uploadFileResult.HttpStatusCode); - _cloud.AddFileInCloud(_file, ConflictResolver.Rewrite) - .Result - .ThrowIf(r => !r.Success, _ => new Exception($"Cannot add file {_file.FullPath}")); - } + // 2020-10-26 mail.ru does not return file size now + //if (uploadFileResult.HasReturnedData && _file.OriginalSize != uploadFileResult.Size) + // throw new Exception("Local and remote file size does not match"); - private void UploadCache(Stream sourceStream) - { - using var cache = new CacheStream(_file, sourceStream, _cloud.Settings.DeduplicateRules); - - if (cache.Process()) + _file.Hash = uploadFileResult.HasReturnedData switch { - Logger.Debug($"Uploading [{cache.DataCacheName}] {_file.FullPath}"); - - OnFileStreamSent(); - - _file.Hash = _cloudFileHasher.Hash; - bool added = _cloud.AddFileInCloud(_file, ConflictResolver.Rewrite) + true when CheckHashes && null != uploadFileResult.Hash && _cloudFileHasher != null && + _cloudFileHasher.Hash.Hash.Value != uploadFileResult.Hash.Hash.Value => throw + new HashMatchException(_cloudFileHasher.Hash.ToString(), uploadFileResult.Hash.ToString()), + true => uploadFileResult.Hash, + _ => _file.Hash + }; + + if (uploadFileResult.NeedToAddFile) + _cloud.AddFileInCloud(_file, ConflictResolver.Rewrite) .Result - .Success; - - if (!added) - UploadFull(cache.Stream, false); - } - else - { - UploadFull(sourceStream); - } + .ThrowIf(r => !r.Success, _ => new Exception($"Cannot add file {_file.FullPath}")); } - - private void UploadFull(Stream sourceStream, bool doInvokeFileStreamSent = true) + finally { - Logger.Debug($"Uploading [direct] {_file.FullPath}"); - - var pushContent = new PushStreamContent((stream, _, _) => - { - try - { - sourceStream.CopyTo(stream); - stream.Flush(); - stream.Close(); - if (doInvokeFileStreamSent) - OnFileStreamSent(); - } - catch (Exception e) - { - Logger.Error($"(inner) Uploading to {_file.FullPath} failed with {e}"); - throw; - } - }); - - var client = HttpClientFabric.Instance[_cloud]; - DateTime timestampBeforeOperation = DateTime.Now; - try - { - _cloud.OnBeforeUpload(_file.FullPath); - - var uploadFileResult = _cloud.RequestRepo.DoUpload(client, pushContent, _file).Result; - - if (uploadFileResult.HttpStatusCode != HttpStatusCode.Created && - uploadFileResult.HttpStatusCode != HttpStatusCode.OK) - throw new Exception("Cannot upload file, status " + uploadFileResult.HttpStatusCode); - - // 2020-10-26 mail.ru does not return file size now - //if (uploadFileResult.HasReturnedData && _file.OriginalSize != uploadFileResult.Size) - // throw new Exception("Local and remote file size does not match"); - - _file.Hash = uploadFileResult.HasReturnedData switch - { - true when CheckHashes && null != uploadFileResult.Hash && _cloudFileHasher != null && - _cloudFileHasher.Hash.Hash.Value != uploadFileResult.Hash.Hash.Value => throw - new HashMatchException(_cloudFileHasher.Hash.ToString(), uploadFileResult.Hash.ToString()), - true => uploadFileResult.Hash, - _ => _file.Hash - }; - - if (uploadFileResult.NeedToAddFile) - _cloud.AddFileInCloud(_file, ConflictResolver.Rewrite) - .Result - .ThrowIf(r => !r.Success, _ => new Exception($"Cannot add file {_file.FullPath}")); - } - finally - { - _cloud.OnAfterUpload(_file.FullPath, timestampBeforeOperation); - } + _cloud.OnAfterUpload(_file.FullPath, timestampBeforeOperation); } + } - public bool CheckHashes { get; set; } = true; + public bool CheckHashes { get; set; } = true; - public override void Write(byte[] buffer, int offset, int count) + public override void Write(byte[] buffer, int offset, int count) + { + if (CheckHashes || + (_cloudFileHasher != null && + _cloud.RequestRepo.SupportsAddSmallFileByHash && + _file.OriginalSize <= _cloudFileHasher.Length)) { - if (CheckHashes || - (_cloudFileHasher != null && - _cloud.RequestRepo.SupportsAddSmallFileByHash && - _file.OriginalSize <= _cloudFileHasher.Length)) - { - _cloudFileHasher?.Append(buffer, offset, count); - } - - _ringBuffer.Write(buffer, offset, count); + _cloudFileHasher?.Append(buffer, offset, count); } - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - if (!disposing) return; + _ringBuffer.Write(buffer, offset, count); + } - try - { - _ringBuffer.Flush(); + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + if (!disposing) return; - _uploadTask.GetAwaiter().GetResult(); - OnServerFileProcessed(); + try + { + _ringBuffer.Flush(); + + _uploadTask.GetAwaiter().GetResult(); + OnServerFileProcessed(); - } - finally - { - _ringBuffer?.Dispose(); - _cloudFileHasher?.Dispose(); - } } + finally + { + _ringBuffer?.Dispose(); + _cloudFileHasher?.Dispose(); + } + } - private readonly Cloud _cloud; - private readonly File _file; + private readonly Cloud _cloud; + private readonly File _file; - private readonly ICloudHasher _cloudFileHasher; - private Task _uploadTask; - private readonly RingBufferedStream _ringBuffer = new(65536); + private readonly ICloudHasher _cloudFileHasher; + private Task _uploadTask; + private readonly RingBufferedStream _ringBuffer = new(65536); - //=========================================================================================================================== + //=========================================================================================================================== - public override bool CanRead => true; - public override bool CanSeek => true; - public override bool CanWrite => true; - public override long Length => _file.OriginalSize; - public override long Position { get; set; } + public override bool CanRead => true; + public override bool CanSeek => true; + public override bool CanWrite => true; + public override long Length => _file.OriginalSize; + public override long Position { get; set; } - public override void SetLength(long value) - { - _file.OriginalSize = value; - } + public override void SetLength(long value) + { + _file.OriginalSize = value; + } - public override void Flush() - { - throw new NotImplementedException(); - } + public override void Flush() + { + throw new NotImplementedException(); + } - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotImplementedException(); - } + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotImplementedException(); + } - public override int Read(byte[] buffer, int offset, int count) - { - throw new NotImplementedException(); - } + public override int Read(byte[] buffer, int offset, int count) + { + throw new NotImplementedException(); } } diff --git a/MailRuCloud/MailRuCloudApi/Base/WebDavPath.cs b/MailRuCloud/MailRuCloudApi/Base/WebDavPath.cs index 775e1e0c..947c0a37 100644 --- a/MailRuCloud/MailRuCloudApi/Base/WebDavPath.cs +++ b/MailRuCloud/MailRuCloudApi/Base/WebDavPath.cs @@ -2,244 +2,243 @@ using System.Collections.Generic; using System.Text; -namespace YaR.Clouds.Base +namespace YaR.Clouds.Base; + +public static class WebDavPath { - public static class WebDavPath + public static bool IsFullPath(string path) { - public static bool IsFullPath(string path) - { - return path.StartsWith("/"); - } + return path.StartsWith("/"); + } - public static string Combine(string a, string b) - { - if (a is null) - throw new ArgumentNullException(nameof(a)); + public static string Combine(string a, string b) + { + if (a is null) + throw new ArgumentNullException(nameof(a)); - if (b is null) - return Clean(a, false); + if (b is null) + return Clean(a, false); - StringBuilder right = CleanSb(b, false); + StringBuilder right = CleanSb(b, false); - if (a == "/disk") - { - right.Insert(0, a); - if (right[right.Length - 1] == '/') - right.Remove(right.Length - 1, 1); - return right.ToString(); - } - if (a == Root) - return right.ToString(); + if (a == "/disk") + { + right.Insert(0, a); + if (right[right.Length - 1] == '/') + right.Remove(right.Length - 1, 1); + return right.ToString(); + } + if (a == Root) + return right.ToString(); - StringBuilder left = CleanSb(a, false); + StringBuilder left = CleanSb(a, false); #if NET48 - left.Append(right.ToString()); + left.Append(right.ToString()); #else - left.Append(right); + left.Append(right); #endif - return left.ToString(); + return left.ToString(); - } + } - public static StringBuilder CleanSb(string path, bool doAddFinalSeparator = false) + public static StringBuilder CleanSb(string path, bool doAddFinalSeparator = false) + { + try { - try + if (path == null) + throw new ArgumentNullException(nameof(path)); + + StringBuilder text = new StringBuilder(path); + text.Replace("\\", "/").Replace("//", "/"); + if (doAddFinalSeparator) { - if (path == null) - throw new ArgumentNullException(nameof(path)); - - StringBuilder text = new StringBuilder(path); - text.Replace("\\", "/").Replace("//", "/"); - if (doAddFinalSeparator) - { - if (text.Length == 0 || text[^1] != '/') - text.Append('/'); - } - else - { - if (text.Length > 1 && text[^1] == '/') - text.Remove(text.Length - 1, 1); - } - - if (text.Length == 0 || text[0] != '/') - text.Insert(0, '/'); - - return text; + if (text.Length == 0 || text[^1] != '/') + text.Append('/'); } - catch (Exception e) + else { - Console.WriteLine(e); - throw; + if (text.Length > 1 && text[^1] == '/') + text.Remove(text.Length - 1, 1); } - } - public static string Clean(string path, bool doAddFinalSeparator = false) - { - return CleanSb(path, doAddFinalSeparator).ToString(); - } + if (text.Length == 0 || text[0] != '/') + text.Insert(0, '/'); - public static string Parent(string path, string cmdPrefix = ">>") + return text; + } + catch (Exception e) { - // cause we use >> as a sign of special command - int cmdPos = path.IndexOf(cmdPrefix, StringComparison.Ordinal); - int len = path.EndsWith("/") ? path.Length - 1 : path.Length; - if (len == 0) - return Root; - int slash = path.LastIndexOf("/", len - 1, cmdPos < 0 ? len : len - cmdPos, StringComparison.Ordinal); - - return slash > 0 - ? path.Substring(0, slash) - : Root; + Console.WriteLine(e); + throw; } + } - public static string Name(string path, string cmdPrefix = ">>") - { - // cause we use >> as a sign of special command - int cmdPos = path.IndexOf(cmdPrefix, StringComparison.Ordinal); - int len = path.EndsWith("/") ? path.Length - 1 : path.Length; - if (len == 0) - return ""; - int slash = path.LastIndexOf("/", len - 1, cmdPos < 0 ? len : len - cmdPos, StringComparison.Ordinal); + public static string Clean(string path, bool doAddFinalSeparator = false) + { + return CleanSb(path, doAddFinalSeparator).ToString(); + } - if (slash < 0 && len == path.Length) - return path; + public static string Parent(string path, string cmdPrefix = ">>") + { + // cause we use >> as a sign of special command + int cmdPos = path.IndexOf(cmdPrefix, StringComparison.Ordinal); + int len = path.EndsWith("/") ? path.Length - 1 : path.Length; + if (len == 0) + return Root; + int slash = path.LastIndexOf("/", len - 1, cmdPos < 0 ? len : len - cmdPos, StringComparison.Ordinal); + + return slash > 0 + ? path.Substring(0, slash) + : Root; + } - return path.Substring(slash + 1, len - slash - 1); - } + public static string Name(string path, string cmdPrefix = ">>") + { + // cause we use >> as a sign of special command + int cmdPos = path.IndexOf(cmdPrefix, StringComparison.Ordinal); + int len = path.EndsWith("/") ? path.Length - 1 : path.Length; + if (len == 0) + return ""; + int slash = path.LastIndexOf("/", len - 1, cmdPos < 0 ? len : len - cmdPos, StringComparison.Ordinal); - public static string Root => "/"; - public static string Separator => "/"; + if (slash < 0 && len == path.Length) + return path; - public static bool IsParentOrSame(string parent, string child) - { - return IsParent(parent, child, true); - } + return path.Substring(slash + 1, len - slash - 1); + } - public static bool IsParent(string parent, string child, bool selfTrue = false, bool oneLevelDistanceOnly = false) - { - if (child.Equals(parent, StringComparison.InvariantCultureIgnoreCase)) - return selfTrue; + public static string Root => "/"; + public static string Separator => "/"; - if (parent.Length > child.Length) - return false; + public static bool IsParentOrSame(string parent, string child) + { + return IsParent(parent, child, true); + } - if (parent == Root) - { - // Если родитель=root, то любой путь будет потомком, если не брать только ближайший уровень - if (!oneLevelDistanceOnly) - return true; - // Если уровень только ближайший нужен, - // то после части, совпадающей с родителем, не должно быть /, - // исключение - / в виде последнего символа - int slash = child.IndexOf(Separator, parent.Length, StringComparison.Ordinal); - return slash < 0 || slash == child.Length - 1; - } + public static bool IsParent(string parent, string child, bool selfTrue = false, bool oneLevelDistanceOnly = false) + { + if (child.Equals(parent, StringComparison.InvariantCultureIgnoreCase)) + return selfTrue; - // Части потомка и родителя должны совпадать, и потомок должен быть длиннее - if (parent.Length < child.Length && - child.StartsWith(parent, StringComparison.InvariantCultureIgnoreCase)) - { - int start; - // Если у родителя последний символ был /, то у потомка следующий символ не должен быть / - if (child[parent.Length - 1] == '/' && child[parent.Length] != '/') - start = parent.Length; - else - // Если у родителя последний символ был не /, то у потомка следующий символ должен быть / - if (child[parent.Length - 1] != '/' && child[parent.Length] == '/') - start = parent.Length + 1; - else - { - // Тут какая-то ерунду с символами, это не родитель и потоком - return false; - } - int end = child[child.Length - 1] == '/' ? child.Length - 1 : child.Length; - - // Исключая возможный / в конце и исключая часть, совпадающую с родителем, - // исключая / после родителя, от start, включая, до end, исключая (как для substring), - // будет текст пути потомка. - if (start >= end) - return false; - - // Если уровень потомка не важен, то уже true. - if (!oneLevelDistanceOnly) - return true; - - // Если уровень потомка важен, то в интервале еще и символ / должен находиться. - int slash = child.IndexOf(Separator, start, end - start, StringComparison.Ordinal); - return slash < 0; - } + if (parent.Length > child.Length) return false; - } - public static WebDavPathParts Parts(string path) + if (parent == Root) { - //TODO: refact - var res = new WebDavPathParts - { - Parent = Parent(path), - Name = Name(path) - }; - - return res; + // Если родитель=root, то любой путь будет потомком, если не брать только ближайший уровень + if (!oneLevelDistanceOnly) + return true; + // Если уровень только ближайший нужен, + // то после части, совпадающей с родителем, не должно быть /, + // исключение - / в виде последнего символа + int slash = child.IndexOf(Separator, parent.Length, StringComparison.Ordinal); + return slash < 0 || slash == child.Length - 1; } - public static List GetParents(string path, bool includeSelf = true) + // Части потомка и родителя должны совпадать, и потомок должен быть длиннее + if (parent.Length < child.Length && + child.StartsWith(parent, StringComparison.InvariantCultureIgnoreCase)) { - List result = new List(); - - path = Clean(path); - if (includeSelf) - result.Add(path); - - while (path != Root) + int start; + // Если у родителя последний символ был /, то у потомка следующий символ не должен быть / + if (child[parent.Length - 1] == '/' && child[parent.Length] != '/') + start = parent.Length; + else + // Если у родителя последний символ был не /, то у потомка следующий символ должен быть / + if (child[parent.Length - 1] != '/' && child[parent.Length] == '/') + start = parent.Length + 1; + else { - path = Parent(path); - result.Add(path); + // Тут какая-то ерунду с символами, это не родитель и потоком + return false; } + int end = child[child.Length - 1] == '/' ? child.Length - 1 : child.Length; - return result; - } + // Исключая возможный / в конце и исключая часть, совпадающую с родителем, + // исключая / после родителя, от start, включая, до end, исключая (как для substring), + // будет текст пути потомка. + if (start >= end) + return false; - public static string ModifyParent(string path, string oldParent, string newParent) - { - if (!IsParentOrSame(oldParent, path)) - return path; + // Если уровень потомка не важен, то уже true. + if (!oneLevelDistanceOnly) + return true; - if (path is null) - throw new ArgumentNullException(nameof(path)); - if (oldParent is null) - throw new ArgumentNullException(nameof(oldParent)); - if (newParent is null) - throw new ArgumentNullException(nameof(newParent)); - if (path.Length < oldParent.Length) - throw new ArgumentException($"Value of {nameof(oldParent)} is longer then length of {nameof(path)}"); - - StringBuilder pathTmp = CleanSb(path, true); - StringBuilder oldParentTmp = CleanSb(oldParent, true); - path = pathTmp.Remove(0, oldParentTmp.Length).ToString(); - - return Combine(newParent, path); + // Если уровень потомка важен, то в интервале еще и символ / должен находиться. + int slash = child.IndexOf(Separator, start, end - start, StringComparison.Ordinal); + return slash < 0; } + return false; + } - public static bool PathEquals(string path1, string path2) + public static WebDavPathParts Parts(string path) + { + //TODO: refact + var res = new WebDavPathParts { - return Clean(path1).Equals(Clean(path2), StringComparison.InvariantCultureIgnoreCase); - } + Parent = Parent(path), + Name = Name(path) + }; + + return res; + } + + public static List GetParents(string path, bool includeSelf = true) + { + List result = new List(); + + path = Clean(path); + if (includeSelf) + result.Add(path); - public static string EscapeDataString(string path) + while (path != Root) { - return Uri - .EscapeDataString(path ?? string.Empty) - .Replace("#", "%23"); + path = Parent(path); + result.Add(path); } + + return result; + } + + public static string ModifyParent(string path, string oldParent, string newParent) + { + if (!IsParentOrSame(oldParent, path)) + return path; + + if (path is null) + throw new ArgumentNullException(nameof(path)); + if (oldParent is null) + throw new ArgumentNullException(nameof(oldParent)); + if (newParent is null) + throw new ArgumentNullException(nameof(newParent)); + if (path.Length < oldParent.Length) + throw new ArgumentException($"Value of {nameof(oldParent)} is longer then length of {nameof(path)}"); + + StringBuilder pathTmp = CleanSb(path, true); + StringBuilder oldParentTmp = CleanSb(oldParent, true); + path = pathTmp.Remove(0, oldParentTmp.Length).ToString(); + + return Combine(newParent, path); + } + + public static bool PathEquals(string path1, string path2) + { + return Clean(path1).Equals(Clean(path2), StringComparison.InvariantCultureIgnoreCase); } - public struct WebDavPathParts + public static string EscapeDataString(string path) { - public string Parent { get; set; } - public string Name { get; set; } + return Uri + .EscapeDataString(path ?? string.Empty) + .Replace("#", "%23"); } } + +public struct WebDavPathParts +{ + public string Parent { get; set; } + public string Name { get; set; } +} diff --git a/MailRuCloud/MailRuCloudApi/Cloud.cs b/MailRuCloud/MailRuCloudApi/Cloud.cs index f828d0ea..571552c2 100644 --- a/MailRuCloud/MailRuCloudApi/Cloud.cs +++ b/MailRuCloud/MailRuCloudApi/Cloud.cs @@ -19,1545 +19,1544 @@ using YaR.Clouds.Streams; using File = YaR.Clouds.Base.File; -namespace YaR.Clouds +namespace YaR.Clouds; + +/// +/// Cloud client. +/// +public partial class Cloud : IDisposable { - /// - /// Cloud client. - /// - public partial class Cloud : IDisposable - { - private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(Cloud)); + private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(Cloud)); - public delegate string AuthCodeRequiredDelegate(string login, bool isAutoRelogin); + public delegate string AuthCodeRequiredDelegate(string login, bool isAutoRelogin); - public LinkManager LinkManager { get; } + public LinkManager LinkManager { get; } - /// - /// Async tasks cancelation token. - /// - public readonly CancellationTokenSource CancelToken = new(); + /// + /// Async tasks cancelation token. + /// + public readonly CancellationTokenSource CancelToken = new(); - public CloudSettings Settings { get; } + public CloudSettings Settings { get; } - ///// - ///// Caching files for multiple small reads - ///// - //private readonly ItemCache _itemCache; + ///// + ///// Caching files for multiple small reads + ///// + //private readonly ItemCache _itemCache; + + /// + /// Кеш облачной файловой системы. + /// + private readonly EntryCache _entryCache; - /// - /// Кеш облачной файловой системы. - /// - private readonly EntryCache _entryCache; + internal IRequestRepo RequestRepo { get; } + public Credentials Credentials { get; } + public AccountInfoResult AccountInfo { get; private set; } = null; - internal IRequestRepo RequestRepo{ get; } - public Credentials Credentials { get; } - public AccountInfoResult AccountInfo { get; private set; } = null; + /// + /// Initializes a new instance of the class. + /// + public Cloud(CloudSettings settings, Credentials credentials) + { + Settings = settings; + WebRequest.DefaultWebProxy.Credentials = CredentialCache.DefaultCredentials; + Credentials = credentials; + RequestRepo = new RepoFabric(settings, credentials).Create(); - /// - /// Initializes a new instance of the class. - /// - public Cloud(CloudSettings settings, Credentials credentials) + if (!Credentials.IsAnonymous) { - Settings = settings; - WebRequest.DefaultWebProxy.Credentials = CredentialCache.DefaultCredentials; - Credentials = credentials; - RequestRepo = new RepoFabric(settings, credentials).Create(); - - if (!Credentials.IsAnonymous) + try + { + AccountInfo = RequestRepo.AccountInfo().Result + ?? throw new AuthenticationException("The cloud server rejected the credentials provided"); + } + catch (Exception e) when (!Credentials.AuthenticationUsingBrowser && + !Credentials.AuthenticationUsingBrowserDisabled && + e.Contains() && + e.Contains()) { + Logger.Warn("Cloud server requested for additional authentication for login & password, " + + "browser authentication is not disabled, so going to ask the user to log in using BrowserAuthenticator."); + + try + { + if (!Credentials.Refresh(forceBrowserAuthentication: true)) + { + Logger.Warn("Credentials refreshing is failed"); + throw; + } + } + catch (Exception e2) when (e2.Contains()) + { + Exception ae = e2.FirstOfType(); + Logger.Error("Failed to refresh credentials"); + throw new AuthenticationException( + "The cloud server rejected the credentials provided. Then failed to refresh credentials.", ae); + } + + // Проверка результата try { AccountInfo = RequestRepo.AccountInfo().Result ?? throw new AuthenticationException("The cloud server rejected the credentials provided"); } - catch (Exception e) when (!Credentials.AuthenticationUsingBrowser && - !Credentials.AuthenticationUsingBrowserDisabled && - e.Contains() && - e.Contains()) + catch (Exception e2) when (e2.Contains()) { - Logger.Warn("Cloud server requested for additional authentication for login & password, " + - "browser authentication is not disabled, so going to ask the user to log in using BrowserAuthenticator."); - - try - { - if (!Credentials.Refresh(forceBrowserAuthentication: true)) - { - Logger.Warn("Credentials refreshing is failed"); - throw; - } - } - catch (Exception e2) when (e2.Contains()) - { - Exception ae = e2.FirstOfType(); - Logger.Error("Failed to refresh credentials"); - throw new AuthenticationException( - "The cloud server rejected the credentials provided. Then failed to refresh credentials.", ae); - } + Exception ae = e2.FirstOfType(); + Logger.Error("The server rejected the credentials provided"); + throw new AuthenticationException( + "The cloud server rejected the credentials provided. " + + "Credentials have been updated. " + + "Then the server rejected the credentials again. ", ae); + } + } + catch (Exception e) when (Credentials.AuthenticationUsingBrowser && + e.Contains()) + { + Logger.Warn("Refreshing credentials..."); - // Проверка результата - try - { - AccountInfo = RequestRepo.AccountInfo().Result - ?? throw new AuthenticationException("The cloud server rejected the credentials provided"); - } - catch (Exception e2) when (e2.Contains()) + try + { + if (!Credentials.Refresh()) { - Exception ae = e2.FirstOfType(); - Logger.Error("The server rejected the credentials provided"); - throw new AuthenticationException( - "The cloud server rejected the credentials provided. " + - "Credentials have been updated. " + - "Then the server rejected the credentials again. ", ae); + Logger.Warn("Credentials refreshing is failed"); + throw; } } - catch (Exception e) when (Credentials.AuthenticationUsingBrowser && - e.Contains()) + catch (Exception e2) when (e2.Contains()) { - Logger.Warn("Refreshing credentials..."); - - try - { - if (!Credentials.Refresh()) - { - Logger.Warn("Credentials refreshing is failed"); - throw; - } - } - catch (Exception e2) when (e2.Contains()) - { - Exception ae = e2.FirstOfType(); - Logger.Error("Failed to refresh credentials"); - throw new AuthenticationException( - "The cloud server rejected the credentials provided. Then failed to refresh credentials.", ae); - } + Exception ae = e2.FirstOfType(); + Logger.Error("Failed to refresh credentials"); + throw new AuthenticationException( + "The cloud server rejected the credentials provided. Then failed to refresh credentials.", ae); + } - // Проверка результата - try - { - AccountInfo = RequestRepo.AccountInfo().Result - ?? throw new AuthenticationException("The cloud server rejected the credentials provided"); - } - catch (Exception e2) when (e2.Contains()) - { - Exception ae = e2.FirstOfType(); - Logger.Error("The server rejected the credentials provided"); - throw new AuthenticationException( - "The cloud server rejected the credentials provided. " + - "Credentials have been updated. " + - "Then the server rejected the credentials again. ", ae); - } + // Проверка результата + try + { + AccountInfo = RequestRepo.AccountInfo().Result + ?? throw new AuthenticationException("The cloud server rejected the credentials provided"); + } + catch (Exception e2) when (e2.Contains()) + { + Exception ae = e2.FirstOfType(); + Logger.Error("The server rejected the credentials provided"); + throw new AuthenticationException( + "The cloud server rejected the credentials provided. " + + "Credentials have been updated. " + + "Then the server rejected the credentials again. ", ae); } } + } - _entryCache = new EntryCache(TimeSpan.FromSeconds(settings.CacheListingSec), RequestRepo.DetectOutsideChanges); + _entryCache = new EntryCache(TimeSpan.FromSeconds(settings.CacheListingSec), RequestRepo.DetectOutsideChanges); - ////TODO: wow very dummy linking, refact cache realization globally! - //_itemCache = new ItemCache(TimeSpan.FromSeconds(settings.CacheListingSec)); - ////{ - //// Полагаемся на стандартно заданное время очистки - //// CleanUpPeriod = TimeSpan.FromMinutes(5) - ////}; - LinkManager = settings.DisableLinkManager ? null : new LinkManager(this); - } + ////TODO: wow very dummy linking, refact cache realization globally! + //_itemCache = new ItemCache(TimeSpan.FromSeconds(settings.CacheListingSec)); + ////{ + //// Полагаемся на стандартно заданное время очистки + //// CleanUpPeriod = TimeSpan.FromMinutes(5) + ////}; + LinkManager = settings.DisableLinkManager ? null : new LinkManager(this); + } - public enum ItemType - { - File, - Folder, - Unknown - } + public enum ItemType + { + File, + Folder, + Unknown + } - public virtual async Task GetPublicItemAsync(Uri url, ItemType itemType = ItemType.Unknown) - { - var entry = await RequestRepo.FolderInfo(RemotePath.Get(new Link(url))); + public virtual async Task GetPublicItemAsync(Uri url, ItemType itemType = ItemType.Unknown) + { + var entry = await RequestRepo.FolderInfo(RemotePath.Get(new Link(url))); - return entry; - } + return entry; + } - private readonly ConcurrentDictionary> _getItemDict = - new(StringComparer.InvariantCultureIgnoreCase); + private readonly ConcurrentDictionary> _getItemDict = + new(StringComparer.InvariantCultureIgnoreCase); - private readonly SemaphoreSlim _getItemDictLocker = new SemaphoreSlim(1); + private readonly SemaphoreSlim _getItemDictLocker = new SemaphoreSlim(1); - /// - /// Get list of files and folders from account. - /// - /// Path in the cloud to return the list of the items. - /// Unknown, File/Folder if you know for sure - /// True if you know for sure that's not a linked item - /// True to skip link manager, cache and so on, just get - /// the entry info from cloud as fast as possible. - /// List of the items. - public virtual Task GetItemAsync(string path, ItemType itemType = ItemType.Unknown, - bool resolveLinks = true, bool fastGetFromCloud = false) + /// + /// Get list of files and folders from account. + /// + /// Path in the cloud to return the list of the items. + /// Unknown, File/Folder if you know for sure + /// True if you know for sure that's not a linked item + /// True to skip link manager, cache and so on, just get + /// the entry info from cloud as fast as possible. + /// List of the items. + public virtual Task GetItemAsync(string path, ItemType itemType = ItemType.Unknown, + bool resolveLinks = true, bool fastGetFromCloud = false) + { + /* + * Параметр ItemType сохранен для совместимости со старыми вызовами, + * чтобы не потерять и сохранить информацию когда что надо получить, + * на случай, если понадобится, но сейчас по факту параметр не используется. + */ + + /* + * Клиенты очень часто читают одну и ту же директорию через короткий промежуток времени. + * Часто получается так, что первый запрос чтения папки с сервера еще не завершен, + * а тут уже второй пришел, а поскольку кеш еще не образован результатом первого обращения, + * то уже оба идут к серверу, не взирая на то, что выбирается одна и та же папка. + * Поэтому, составляем словарь, где ключ - FullPath, а значение - Task. + * Первый, кто лезет за содержимым папки, формирует Task, кладет его в словарь, + * по окончании очищает словарь от записи. + * Последующие, обнаружив запись в словаре, используют сохраненный Task, + * точнее его Result. До формирования значения в Result последующие обращения блокируются + * и ждут завершения, затем получают готовое значение и уходят довольные. + * Это снижает нагрузку на канал до сервера, нагрузку на сервер и позволяет для + * последующих обращений получать результат раньше, т.к. результат первого обращения + * точно будет получен раньше, т.к. раньше начался. + */ + + if (_getItemDict.TryGetValue(path, out var oldTask)) + return oldTask; + + _getItemDictLocker.Wait(); + try { - /* - * Параметр ItemType сохранен для совместимости со старыми вызовами, - * чтобы не потерять и сохранить информацию когда что надо получить, - * на случай, если понадобится, но сейчас по факту параметр не используется. - */ - - /* - * Клиенты очень часто читают одну и ту же директорию через короткий промежуток времени. - * Часто получается так, что первый запрос чтения папки с сервера еще не завершен, - * а тут уже второй пришел, а поскольку кеш еще не образован результатом первого обращения, - * то уже оба идут к серверу, не взирая на то, что выбирается одна и та же папка. - * Поэтому, составляем словарь, где ключ - FullPath, а значение - Task. - * Первый, кто лезет за содержимым папки, формирует Task, кладет его в словарь, - * по окончании очищает словарь от записи. - * Последующие, обнаружив запись в словаре, используют сохраненный Task, - * точнее его Result. До формирования значения в Result последующие обращения блокируются - * и ждут завершения, затем получают готовое значение и уходят довольные. - * Это снижает нагрузку на канал до сервера, нагрузку на сервер и позволяет для - * последующих обращений получать результат раньше, т.к. результат первого обращения - * точно будет получен раньше, т.к. раньше начался. - */ - - if (_getItemDict.TryGetValue(path, out var oldTask)) + if (_getItemDict.TryGetValue(path, out oldTask)) return oldTask; - _getItemDictLocker.Wait(); - try - { - if (_getItemDict.TryGetValue(path, out oldTask)) - return oldTask; - - _getItemDict[path] = Task.Run(() => GetItemInternalAsync(path, resolveLinks, fastGetFromCloud).Result); - } - finally - { - _getItemDictLocker.Release(); - } + _getItemDict[path] = Task.Run(() => GetItemInternalAsync(path, resolveLinks, fastGetFromCloud).Result); + } + finally + { + _getItemDictLocker.Release(); + } - try - { - return Task.FromResult(_getItemDict[path].Result); - } - finally - { - _getItemDict.TryRemove(path, out _); - } + try + { + return Task.FromResult(_getItemDict[path].Result); } + finally + { + _getItemDict.TryRemove(path, out _); + } + } - private const string MailRuPublicRegexMask = @"\A/(?https://cloud\.mail\.\w+/public/\S+/\S+(/.*)?)\Z"; + private const string MailRuPublicRegexMask = @"\A/(?https://cloud\.mail\.\w+/public/\S+/\S+(/.*)?)\Z"; #if NET7_0_OR_GREATER - [GeneratedRegex(MailRuPublicRegexMask, RegexOptions.IgnoreCase | RegexOptions.Singleline)] - private static partial Regex MailRuPublicRegex(); - private static readonly Regex _mailRegex = MailRuPublicRegex(); + [GeneratedRegex(MailRuPublicRegexMask, RegexOptions.IgnoreCase | RegexOptions.Singleline)] + private static partial Regex MailRuPublicRegex(); + private static readonly Regex _mailRegex = MailRuPublicRegex(); #else - private static readonly Regex _mailRegex = - new Regex(MailRuPublicRegexMask, RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.Compiled); + private static readonly Regex _mailRegex = + new Regex(MailRuPublicRegexMask, RegexOptions.IgnoreCase | RegexOptions.Singleline | RegexOptions.Compiled); #endif - /// - /// Данный метод запускается для каждого path только в одном потоке. - /// Все остальные потоки ожидают результата от единственного выполняемого. - /// См. комментарий в методе GetItemAsync. - /// - /// - /// - /// - private async Task GetItemInternalAsync(string path, bool resolveLinks, bool fastGetFromCloud = false) + /// + /// Данный метод запускается для каждого path только в одном потоке. + /// Все остальные потоки ожидают результата от единственного выполняемого. + /// См. комментарий в методе GetItemAsync. + /// + /// + /// + /// + private async Task GetItemInternalAsync(string path, bool resolveLinks, bool fastGetFromCloud = false) + { + if (!fastGetFromCloud && + (Settings.Protocol == Protocol.WebM1Bin || Settings.Protocol == Protocol.WebV2)) { - if (!fastGetFromCloud && - (Settings.Protocol == Protocol.WebM1Bin || Settings.Protocol == Protocol.WebV2)) - { - //TODO: вообще, всё плохо стало, всё запуталось, всё надо переписать - var uriMatch = _mailRegex.Match(path); - if (uriMatch.Success) - return await GetPublicItemAsync(new Uri(uriMatch.Groups["uri"].Value, UriKind.Absolute)); - } + //TODO: вообще, всё плохо стало, всё запуталось, всё надо переписать + var uriMatch = _mailRegex.Match(path); + if (uriMatch.Success) + return await GetPublicItemAsync(new Uri(uriMatch.Groups["uri"].Value, UriKind.Absolute)); + } - if (Credentials.IsAnonymous) - return null; + if (Credentials.IsAnonymous) + return null; - path = WebDavPath.Clean(path); - RemotePath remotePath; + path = WebDavPath.Clean(path); + RemotePath remotePath; - if (fastGetFromCloud) - { - remotePath = RemotePath.Get(path); - return await RequestRepo.FolderInfo(remotePath, depth: 1, limit: 2); - } + if (fastGetFromCloud) + { + remotePath = RemotePath.Get(path); + return await RequestRepo.FolderInfo(remotePath, depth: 1, limit: 2); + } - (var cached, var getState) = _entryCache.Get(path); - if (getState == EntryCache.GetState.Entry) - return cached; - if (getState == EntryCache.GetState.NotExists) - return null; + (var cached, var getState) = _entryCache.Get(path); + if (getState == EntryCache.GetState.Entry) + return cached; + if (getState == EntryCache.GetState.NotExists) + return null; - //TODO: subject to refact!!! - Link ulink = resolveLinks && LinkManager is not null ? await LinkManager.GetItemLink(path) : null; + //TODO: subject to refact!!! + Link ulink = resolveLinks && LinkManager is not null ? await LinkManager.GetItemLink(path) : null; - /* - * Если LinkManager на затребованный path выдал ссылку, а ссылка бракованная и не рабочая, - * то нельзя выдать null, показывающий отсутствие файла/папки, иначе удалить такую - * бракованную ссылку будет невозможно. - * Потому формируем специальную заглушку с признаком Bad. - */ - if (ulink is { IsBad: true }) - { - var res = ulink.ToBadEntry(); - _entryCache.Add(res); - return res; - } + /* + * Если LinkManager на затребованный path выдал ссылку, а ссылка бракованная и не рабочая, + * то нельзя выдать null, показывающий отсутствие файла/папки, иначе удалить такую + * бракованную ссылку будет невозможно. + * Потому формируем специальную заглушку с признаком Bad. + */ + if (ulink is { IsBad: true }) + { + var res = ulink.ToBadEntry(); + _entryCache.Add(res); + return res; + } - //if (itemType == ItemType.Unknown && ulink is not null) - // itemType = ulink.ItemType; - - // TODO: cache (parent) folder for file - //if (itemType == ItemType.File) - //{ - // var cachefolder = datares.ToFolder(path, ulink); - // _itemCache.Add(cachefolder.FullPath, cachefolder); - // //_itemCache.Add(cachefolder.Files); - //} - remotePath = ulink is null ? RemotePath.Get(path) : RemotePath.Get(ulink); - DateTime timestamp = DateTime.Now; - var cloudResult = await RequestRepo.FolderInfo(remotePath, depth: Settings.ListDepth); - if (cloudResult is null) - { - // Если с сервера получено состояние, что папки нет, - // а кеш ранее говорил, что папка в кеше есть, но без наполнения, - // то папку удалили и надо безотлагательно очистить кеш. - if (getState == EntryCache.GetState.EntryWithUnknownContent) - { - Logger.Debug("Папка была удалена, делается чистка кеша"); - } - _entryCache.OnRemoveTree(timestamp, remotePath.Path, null); + //if (itemType == ItemType.Unknown && ulink is not null) + // itemType = ulink.ItemType; - return null; + // TODO: cache (parent) folder for file + //if (itemType == ItemType.File) + //{ + // var cachefolder = datares.ToFolder(path, ulink); + // _itemCache.Add(cachefolder.FullPath, cachefolder); + // //_itemCache.Add(cachefolder.Files); + //} + remotePath = ulink is null ? RemotePath.Get(path) : RemotePath.Get(ulink); + DateTime timestamp = DateTime.Now; + var cloudResult = await RequestRepo.FolderInfo(remotePath, depth: Settings.ListDepth); + if (cloudResult is null) + { + // Если с сервера получено состояние, что папки нет, + // а кеш ранее говорил, что папка в кеше есть, но без наполнения, + // то папку удалили и надо безотлагательно очистить кеш. + if (getState == EntryCache.GetState.EntryWithUnknownContent) + { + Logger.Debug("Папка была удалена, делается чистка кеша"); } + _entryCache.OnRemoveTree(timestamp, remotePath.Path, null); + return null; + } - //if (itemType == ItemType.Unknown) - // itemType = cloudResult is Folder - // ? ItemType.Folder - // : ItemType.File; - //if (itemType == ItemType.Folder && cloudResult is Folder folder) // fill folder with links if any - // FillWithULinks(folder); + //if (itemType == ItemType.Unknown) + // itemType = cloudResult is Folder + // ? ItemType.Folder + // : ItemType.File; - if (LinkManager is not null && cloudResult is Folder f) - { - FillWithULinks(f); - } + //if (itemType == ItemType.Folder && cloudResult is Folder folder) // fill folder with links if any + // FillWithULinks(folder); - //if (Settings.CacheListingSec > 0) - // CacheAddEntry(cloudResult); + if (LinkManager is not null && cloudResult is Folder f) + { + FillWithULinks(f); + } - _entryCache.Add(cloudResult); + //if (Settings.CacheListingSec > 0) + // CacheAddEntry(cloudResult); + _entryCache.Add(cloudResult); - /* - * Если запрошен файл или папка, которого нет в кеше, - * при этом не известно, есть ли он на сервере, - * есть вероятность, что скоро будет запрошен соседний файл или папка - * и той же родительской папки, поэтому имеет смысл сделать упреждающее - * чтение содержимого родительской папки с сервера или убедиться, - * что она есть в кеше. - */ - string parentPath = WebDavPath.Parent(path); - if (parentPath != path && _entryCache.IsCacheEnabled) - { - // Здесь не ожидается результата работы, задача отработает в фоне и заполнит кеш. - // Обращение к серверу делается после чтения с сервера результата текущего обращения, - // то есть сначала затребованный результат, а потом фоновый, не наоборот, чтобы не ждать. - _ = GetItemAsync(parentPath).ConfigureAwait(false); - } - return cloudResult; + /* + * Если запрошен файл или папка, которого нет в кеше, + * при этом не известно, есть ли он на сервере, + * есть вероятность, что скоро будет запрошен соседний файл или папка + * и той же родительской папки, поэтому имеет смысл сделать упреждающее + * чтение содержимого родительской папки с сервера или убедиться, + * что она есть в кеше. + */ + string parentPath = WebDavPath.Parent(path); + if (parentPath != path && _entryCache.IsCacheEnabled) + { + // Здесь не ожидается результата работы, задача отработает в фоне и заполнит кеш. + // Обращение к серверу делается после чтения с сервера результата текущего обращения, + // то есть сначала затребованный результат, а потом фоновый, не наоборот, чтобы не ждать. + _ = GetItemAsync(parentPath).ConfigureAwait(false); } - private void FillWithULinks(Folder folder) - { - if (folder == null || !folder.IsChildrenLoaded) - return; + return cloudResult; + } + + private void FillWithULinks(Folder folder) + { + if (folder == null || !folder.IsChildrenLoaded) + return; - string fullPath = folder.FullPath; + string fullPath = folder.FullPath; - var flinks = LinkManager.GetItems(fullPath); - if (flinks is not null) + var flinks = LinkManager.GetItems(fullPath); + if (flinks is not null) + { + var newChildren = new List(); + foreach (var flink in flinks) { - var newChildren = new List(); - foreach (var flink in flinks) - { - string linkpath = WebDavPath.Combine(fullPath, flink.Name); + string linkpath = WebDavPath.Combine(fullPath, flink.Name); - if (flink.IsFile) - { - if (folder.Descendants.Any(entry => entry.FullPath.Equals(linkpath, StringComparison.InvariantCultureIgnoreCase))) - continue; + if (flink.IsFile) + { + if (folder.Descendants.Any(entry => entry.FullPath.Equals(linkpath, StringComparison.InvariantCultureIgnoreCase))) + continue; - var newFile = new File(linkpath, flink.Size, new PublicLinkInfo(flink.Href)); - if (flink.CreationDate is not null) - newFile.LastWriteTimeUtc = flink.CreationDate.Value; - newChildren.Add(newFile); - } - else - { - Folder newFolder = new Folder(0, linkpath) { CreationTimeUtc = flink.CreationDate ?? DateTime.MinValue }; - newChildren.Add(newFolder); - } + var newFile = new File(linkpath, flink.Size, new PublicLinkInfo(flink.Href)); + if (flink.CreationDate is not null) + newFile.LastWriteTimeUtc = flink.CreationDate.Value; + newChildren.Add(newFile); } - if (newChildren.Count > 0) + else { - folder.Descendants = folder.Descendants.AddRange(newChildren); + Folder newFolder = new Folder(0, linkpath) { CreationTimeUtc = flink.CreationDate ?? DateTime.MinValue }; + newChildren.Add(newFolder); } } - - foreach (var child in folder.Descendants) + if (newChildren.Count > 0) { - if (child is Folder f) - FillWithULinks(f); + folder.Descendants = folder.Descendants.AddRange(newChildren); } } - //private void FillWithULinks(Folder folder) - //{ - // if (!folder.IsChildrenLoaded) return; - - // if (LinkManager is not null) - // { - // var flinks = LinkManager.GetItems(folder.FullPath); - // if (flinks is not null && flinks.Any()) - // { - // foreach (var flink in flinks) - // { - // string linkpath = WebDavPath.Combine(folder.FullPath, flink.Name); - - // if (!flink.IsFile) - // { - // Folder item = new Folder(0, linkpath) { CreationTimeUtc = flink.CreationDate ?? DateTime.MinValue }; - // folder.Folders.AddOrUpdate(item.FullPath, item, (_, _) => item); - // } - // else - // { - // if (folder.Files.ContainsKey(linkpath)) - // continue; - - // var newFile = new File(linkpath, flink.Size, new PublicLinkInfo(flink.Href)); - // if (flink.CreationDate is not null) - // newFile.LastWriteTimeUtc = flink.CreationDate.Value; - // folder.Files.AddOrUpdate(newFile.FullPath, newFile, (_, _) => newFile); - // } - // } - // } - // } - - // foreach (var childFolder in folder.Folders.Values) - // FillWithULinks(childFolder); - //} + foreach (var child in folder.Descendants) + { + if (child is Folder f) + FillWithULinks(f); + } + } + //private void FillWithULinks(Folder folder) + //{ + // if (!folder.IsChildrenLoaded) return; + + // if (LinkManager is not null) + // { + // var flinks = LinkManager.GetItems(folder.FullPath); + // if (flinks is not null && flinks.Any()) + // { + // foreach (var flink in flinks) + // { + // string linkpath = WebDavPath.Combine(folder.FullPath, flink.Name); + + // if (!flink.IsFile) + // { + // Folder item = new Folder(0, linkpath) { CreationTimeUtc = flink.CreationDate ?? DateTime.MinValue }; + // folder.Folders.AddOrUpdate(item.FullPath, item, (_, _) => item); + // } + // else + // { + // if (folder.Files.ContainsKey(linkpath)) + // continue; + + // var newFile = new File(linkpath, flink.Size, new PublicLinkInfo(flink.Href)); + // if (flink.CreationDate is not null) + // newFile.LastWriteTimeUtc = flink.CreationDate.Value; + // folder.Files.AddOrUpdate(newFile.FullPath, newFile, (_, _) => newFile); + // } + // } + // } + // } + + // foreach (var childFolder in folder.Folders.Values) + // FillWithULinks(childFolder); + //} + + + //private void CacheAddEntry(IEntry entry) + //{ + // switch (entry) + // { + // case File cfile: + // _itemCache.Add(cfile.FullPath, cfile); + // break; + // case Folder { IsChildrenLoaded: true } cfolder: + // { + // _itemCache.Add(cfolder.FullPath, cfolder); + // _itemCache.Add(cfolder.Files.Select(f => new KeyValuePair(f.Value.FullPath, f.Value))); + + // foreach (var childFolder in cfolder.Entries) + // CacheAddEntry(childFolder); + // break; + // } + // } + //} + + //private IEntry CacheGetEntry(string path) => _itemCache.Get(path); + + //public virtual IEntry GetItem(string path, ItemType itemType = ItemType.Unknown, bool resolveLinks = true) + // => GetItemAsync(path, itemType, resolveLinks).Result; - //private void CacheAddEntry(IEntry entry) - //{ - // switch (entry) - // { - // case File cfile: - // _itemCache.Add(cfile.FullPath, cfile); - // break; - // case Folder { IsChildrenLoaded: true } cfolder: - // { - // _itemCache.Add(cfolder.FullPath, cfolder); - // _itemCache.Add(cfolder.Files.Select(f => new KeyValuePair(f.Value.FullPath, f.Value))); - - // foreach (var childFolder in cfolder.Entries) - // CacheAddEntry(childFolder); - // break; - // } - // } - //} + /// + /// Поиск файла/папки по названию (без пути) в перечисленных папках. + /// Возвращает полный путь к файлу или папке. + /// + /// Название файла/папки без пути. + /// Список полных папок с полными путями. + /// + public string Find(string nameWithoutPathToFind, params string[] folderPaths) + { + if (folderPaths is null || folderPaths.Length == 0 || string.IsNullOrEmpty(nameWithoutPathToFind)) + return null; + + List paths = []; + // Сначала смотрим в кеше, без обращений к серверу + foreach (var folderPath in folderPaths) + { + var path = WebDavPath.Combine(folderPath, nameWithoutPathToFind); + (var cached, var getState) = _entryCache.Get(path); + // Если файл или папка найдены в кеше + if (getState == EntryCache.GetState.Entry) + return cached.FullPath; + // Если файл или папка точно отсутствуют в кеше и на сервере + if (getState == EntryCache.GetState.NotExists) + continue; + // В остальных случаях будем искать дальше + paths.Add(folderPath); + } + if (paths.Count == 0) + return null; + + // В кеше файла не оказалось, читаем все директории и смотрим а них - //private IEntry CacheGetEntry(string path) => _itemCache.Get(path); + var tasks = paths + .AsParallel() + .WithDegreeOfParallelism(paths.Count) + .Select(async path => await GetItemAsync(path, ItemType.Folder, false)); - //public virtual IEntry GetItem(string path, ItemType itemType = ItemType.Unknown, bool resolveLinks = true) - // => GetItemAsync(path, itemType, resolveLinks).Result; + if (tasks is null) + return null; - /// - /// Поиск файла/папки по названию (без пути) в перечисленных папках. - /// Возвращает полный путь к файлу или папке. - /// - /// Название файла/папки без пути. - /// Список полных папок с полными путями. - /// - public string Find(string nameWithoutPathToFind, params string[] folderPaths) + foreach (var task in tasks) { - if (folderPaths is null || folderPaths.Length == 0 || string.IsNullOrEmpty(nameWithoutPathToFind)) - return null; + if (task.Result is null) + continue; + IEntry entry = task.Result; + if (entry.Name.Equals(nameWithoutPathToFind, StringComparison.InvariantCultureIgnoreCase)) + return entry.FullPath; - List paths = []; - // Сначала смотрим в кеше, без обращений к серверу - foreach (var folderPath in folderPaths) + foreach (var child in entry.Descendants) { - var path = WebDavPath.Combine(folderPath, nameWithoutPathToFind); - (var cached, var getState) = _entryCache.Get(path); - // Если файл или папка найдены в кеше - if (getState == EntryCache.GetState.Entry) - return cached.FullPath; - // Если файл или папка точно отсутствуют в кеше и на сервере - if (getState == EntryCache.GetState.NotExists) - continue; - // В остальных случаях будем искать дальше - paths.Add(folderPath); + if (child.Name.Equals(nameWithoutPathToFind, StringComparison.InvariantCultureIgnoreCase)) + return child.FullPath; } - if (paths.Count == 0) - return null; - - // В кеше файла не оказалось, читаем все директории и смотрим а них + } - var tasks = paths - .AsParallel() - .WithDegreeOfParallelism(paths.Count) - .Select(async path => await GetItemAsync(path, ItemType.Folder, false)); + return null; + } - if (tasks is null) - return null; + #region == Publish ========================================================================================================================== - foreach (var task in tasks) - { - if (task.Result is null) - continue; - IEntry entry = task.Result; - if (entry.Name.Equals(nameWithoutPathToFind, StringComparison.InvariantCultureIgnoreCase)) - return entry.FullPath; + private async Task Unpublish(Uri publicLink, string fullPath) + { + //var res = (await new UnpublishRequest(CloudApi, publicLink).MakeRequestAsync(_connectionLimiter)) + var res = (await RequestRepo.Unpublish(publicLink, fullPath)) + .ThrowIf(r => !r.IsSuccess, _ => new Exception($"Unpublish error, link = {publicLink}")); - foreach (var child in entry.Descendants) - { - if (child.Name.Equals(nameWithoutPathToFind, StringComparison.InvariantCultureIgnoreCase)) - return child.FullPath; - } - } + return res.IsSuccess; + } - return null; + public async Task Unpublish(File file) + { + foreach (var innerFile in file.Files) + { + await Unpublish(innerFile.GetPublicLinks(this).FirstOrDefault().Uri, innerFile.FullPath); + innerFile.PublicLinks.Clear(); } + } + + + private async Task Publish(string fullPath) + { + var res = (await RequestRepo.Publish(fullPath)) + .ThrowIf(r => !r.IsSuccess, _ => new Exception($"Publish error, path = {fullPath}")); + + var uri = new Uri(res.Url, UriKind.RelativeOrAbsolute); + if (!uri.IsAbsoluteUri) + uri = new Uri($"{RequestRepo.PublicBaseUrlDefault.TrimEnd('/')}/{res.Url.TrimStart('/')}", UriKind.Absolute); + + return uri; + } - #region == Publish ========================================================================================================================== + public async Task Publish(File file, bool makeShareFile = true, + bool generateDirectVideoLink = false, bool makeM3UFile = false, SharedVideoResolution videoResolution = SharedVideoResolution.All) + { + if (file.Files.Count > 1 && (generateDirectVideoLink || makeM3UFile)) + throw new ArgumentException($"Cannot generate direct video link for splitted file {file.FullPath}"); - private async Task Unpublish(Uri publicLink, string fullPath) + foreach (var innerFile in file.Files) { - //var res = (await new UnpublishRequest(CloudApi, publicLink).MakeRequestAsync(_connectionLimiter)) - var res = (await RequestRepo.Unpublish(publicLink, fullPath)) - .ThrowIf(r => !r.IsSuccess, _ => new Exception($"Unpublish error, link = {publicLink}")); + var url = await Publish(innerFile.FullPath); + innerFile.PublicLinks.Clear(); + innerFile.PublicLinks.TryAdd(url.AbsolutePath, new PublicLinkInfo(url)); + } + var info = file.ToPublishInfo(this, generateDirectVideoLink, videoResolution); - return res.IsSuccess; + if (makeShareFile) + { + string path = string.Concat(file.FullPath, PublishInfo.SharedFilePostfix); + UploadFileJson(path, info) + .ThrowIf(r => !r, _ => new Exception($"Cannot upload JSON file, path = {path}")); } - public async Task Unpublish(File file) + + if (makeM3UFile) { - foreach (var innerFile in file.Files) + string path = string.Concat(file.FullPath, PublishInfo.PlayListFilePostfix); + var content = new StringBuilder(); { - await Unpublish(innerFile.GetPublicLinks(this).FirstOrDefault().Uri, innerFile.FullPath); - innerFile.PublicLinks.Clear(); + content.Append("#EXTM3U\r\n"); + foreach (var item in info.Items) + { + content.Append($"#EXTINF:-1,{WebDavPath.Name(item.Path)}\r\n"); + content.Append($"{item.PlayListUrl}\r\n"); + } } + UploadFile(path, content.ToString()) + .ThrowIf(r => !r, _ => new Exception($"Cannot upload JSON file, path = {path}")); } + return info; + } - private async Task Publish(string fullPath) - { - var res = (await RequestRepo.Publish(fullPath)) - .ThrowIf(r => !r.IsSuccess, _ => new Exception($"Publish error, path = {fullPath}")); + public async Task Publish(Folder folder, bool makeShareFile = true) + { + var url = await Publish(folder.FullPath); + folder.PublicLinks.Clear(); + folder.PublicLinks.TryAdd(url.AbsolutePath, new PublicLinkInfo(url)); + var info = folder.ToPublishInfo(); - var uri = new Uri(res.Url, UriKind.RelativeOrAbsolute); - if (!uri.IsAbsoluteUri) - uri = new Uri($"{RequestRepo.PublicBaseUrlDefault.TrimEnd('/')}/{res.Url.TrimStart('/')}", UriKind.Absolute); + if (!makeShareFile) + return info; - return uri; - } + string path = WebDavPath.Combine(folder.FullPath, PublishInfo.SharedFilePostfix); + UploadFileJson(path, info) + .ThrowIf(r => !r, _ => new Exception($"Cannot upload JSON file, path = {path}")); - public async Task Publish(File file, bool makeShareFile = true, - bool generateDirectVideoLink = false, bool makeM3UFile = false, SharedVideoResolution videoResolution = SharedVideoResolution.All) - { - if (file.Files.Count > 1 && (generateDirectVideoLink || makeM3UFile)) - throw new ArgumentException($"Cannot generate direct video link for splitted file {file.FullPath}"); + return info; + } - foreach (var innerFile in file.Files) - { - var url = await Publish(innerFile.FullPath); - innerFile.PublicLinks.Clear(); - innerFile.PublicLinks.TryAdd(url.AbsolutePath, new PublicLinkInfo(url)); - } - var info = file.ToPublishInfo(this, generateDirectVideoLink, videoResolution); + public async Task Publish(IEntry entry, bool makeShareFile = true, + bool generateDirectVideoLink = false, bool makeM3UFile = false, SharedVideoResolution videoResolution = SharedVideoResolution.All) + { + return entry switch + { + null => throw new ArgumentNullException(nameof(entry)), + File file => await Publish(file, makeShareFile, generateDirectVideoLink, makeM3UFile, videoResolution), + Folder folder => await Publish(folder, makeShareFile), + _ => throw new Exception($"Unknown entry type, type = {entry.GetType()},path = {entry.FullPath}") + }; + } + #endregion == Publish ======================================================================================================================= - if (makeShareFile) - { - string path = string.Concat(file.FullPath, PublishInfo.SharedFilePostfix); - UploadFileJson(path, info) - .ThrowIf(r => !r, _ => new Exception($"Cannot upload JSON file, path = {path}")); - } + #region == Copy ============================================================================================================================= + /// + /// Copy folder. + /// + /// Source folder. + /// Destination path on the server. + /// True or false operation result. + public async Task Copy(Folder folder, string destinationPath) + { + DateTime timestampBeforeOperation = DateTime.Now; + destinationPath = WebDavPath.Clean(destinationPath); - if (makeM3UFile) + // if it linked - just clone + if (LinkManager is not null) + { + var link = await LinkManager.GetItemLink(folder.FullPath, false); + if (link is not null) { - string path = string.Concat(file.FullPath, PublishInfo.PlayListFilePostfix); - var content = new StringBuilder(); - { - content.Append("#EXTM3U\r\n"); - foreach (var item in info.Items) - { - content.Append($"#EXTINF:-1,{WebDavPath.Name(item.Path)}\r\n"); - content.Append($"{item.PlayListUrl}\r\n"); - } - } - UploadFile(path, content.ToString()) - .ThrowIf(r => !r, _ => new Exception($"Cannot upload JSON file, path = {path}")); + var cloneres = await CloneItem(destinationPath, link.Href.OriginalString); + if (!cloneres.IsSuccess || WebDavPath.Name(cloneres.Path) == link.Name) + return cloneres.IsSuccess; + var renRes = await Rename(cloneres.Path, link.Name); + return renRes; } - - return info; } - public async Task Publish(Folder folder, bool makeShareFile = true) + try { - var url = await Publish(folder.FullPath); - folder.PublicLinks.Clear(); - folder.PublicLinks.TryAdd(url.AbsolutePath, new PublicLinkInfo(url)); - var info = folder.ToPublishInfo(); + _entryCache.RegisterOperation(folder.FullPath, CounterOperation.Copy); + _entryCache.RegisterOperation(destinationPath, CounterOperation.None); - if (!makeShareFile) - return info; - - string path = WebDavPath.Combine(folder.FullPath, PublishInfo.SharedFilePostfix); - UploadFileJson(path, info) - .ThrowIf(r => !r, _ => new Exception($"Cannot upload JSON file, path = {path}")); + //var copyRes = await new CopyRequest(CloudApi, folder.FullPath, destinationPath).MakeRequestAsync(_connectionLimiter); + var copyRes = await RequestRepo.Copy(folder.FullPath, destinationPath); + if (!copyRes.IsSuccess) + return false; - return info; + // OnRemove делать до OnCreate + _entryCache.OnRemoveTree(timestampBeforeOperation, + folder.FullPath, GetItemAsync(folder.FullPath, fastGetFromCloud: true)); + _entryCache.OnCreate(timestampBeforeOperation, + destinationPath, GetItemAsync(destinationPath, fastGetFromCloud: true), folder.FullPath); } - - public async Task Publish(IEntry entry, bool makeShareFile = true, - bool generateDirectVideoLink = false, bool makeM3UFile = false, SharedVideoResolution videoResolution = SharedVideoResolution.All) + finally { - return entry switch - { - null => throw new ArgumentNullException(nameof(entry)), - File file => await Publish(file, makeShareFile, generateDirectVideoLink, makeM3UFile, videoResolution), - Folder folder => await Publish(folder, makeShareFile), - _ => throw new Exception($"Unknown entry type, type = {entry.GetType()},path = {entry.FullPath}") - }; + _entryCache.UnregisterOperation(folder.FullPath); + _entryCache.UnregisterOperation(destinationPath); } - #endregion == Publish ======================================================================================================================= - - #region == Copy ============================================================================================================================= - /// - /// Copy folder. - /// - /// Source folder. - /// Destination path on the server. - /// True or false operation result. - public async Task Copy(Folder folder, string destinationPath) + //clone all inner links + if (LinkManager is not null) { - DateTime timestampBeforeOperation = DateTime.Now; - destinationPath = WebDavPath.Clean(destinationPath); - - // if it linked - just clone - if (LinkManager is not null) + var links = LinkManager.GetChildren(folder.FullPath); + if (links is not null) { - var link = await LinkManager.GetItemLink(folder.FullPath, false); - if (link is not null) + foreach (var linka in links) { - var cloneres = await CloneItem(destinationPath, link.Href.OriginalString); - if (!cloneres.IsSuccess || WebDavPath.Name(cloneres.Path) == link.Name) - return cloneres.IsSuccess; - var renRes = await Rename(cloneres.Path, link.Name); - return renRes; - } - } + var linkdest = WebDavPath.ModifyParent(linka.MapPath, WebDavPath.Parent(folder.FullPath), destinationPath); + var cloneres = await CloneItem(linkdest, linka.Href.OriginalString); + if (!cloneres.IsSuccess || WebDavPath.Name(cloneres.Path) == linka.Name) + continue; - try - { - _entryCache.RegisterOperation(folder.FullPath, CounterOperation.Copy); - _entryCache.RegisterOperation(destinationPath, CounterOperation.None); + if (await Rename(cloneres.Path, linka.Name)) + continue; - //var copyRes = await new CopyRequest(CloudApi, folder.FullPath, destinationPath).MakeRequestAsync(_connectionLimiter); - var copyRes = await RequestRepo.Copy(folder.FullPath, destinationPath); - if (!copyRes.IsSuccess) return false; - - // OnRemove делать до OnCreate - _entryCache.OnRemoveTree(timestampBeforeOperation, - folder.FullPath, GetItemAsync(folder.FullPath, fastGetFromCloud: true)); - _entryCache.OnCreate(timestampBeforeOperation, - destinationPath, GetItemAsync(destinationPath, fastGetFromCloud: true), folder.FullPath); - } - finally - { - _entryCache.UnregisterOperation(folder.FullPath); - _entryCache.UnregisterOperation(destinationPath); + } } + } - //clone all inner links - if (LinkManager is not null) - { - var links = LinkManager.GetChildren(folder.FullPath); - if (links is not null) - { - foreach (var linka in links) - { - var linkdest = WebDavPath.ModifyParent(linka.MapPath, WebDavPath.Parent(folder.FullPath), destinationPath); - var cloneres = await CloneItem(linkdest, linka.Href.OriginalString); - if (!cloneres.IsSuccess || WebDavPath.Name(cloneres.Path) == linka.Name) - continue; + return true; + } - if (await Rename(cloneres.Path, linka.Name)) - continue; + /// + /// Copy item. + /// + /// Source item. + /// Destination path on the server. + /// True or false operation result. + public async Task Copy(string sourcePath, string destinationPath) + { + var entry = await GetItemAsync(sourcePath); + if (entry is null) + return false; - return false; - } - } - } + return await Copy(entry, destinationPath); + } - return true; - } + /// + /// Copy item. + /// + /// Source item. + /// Destination path on the server. + /// Rename target item. + /// True or false operation result. + public async Task Copy(IEntry source, string destinationPath, string newName = null) + { + if (source is null) throw new ArgumentNullException(nameof(source)); + if (string.IsNullOrEmpty(destinationPath)) throw new ArgumentNullException(nameof(destinationPath)); - /// - /// Copy item. - /// - /// Source item. - /// Destination path on the server. - /// True or false operation result. - public async Task Copy(string sourcePath, string destinationPath) + return source switch { - var entry = await GetItemAsync(sourcePath); - if (entry is null) - return false; + File file => await Copy(file, destinationPath, string.IsNullOrEmpty(newName) ? file.Name : newName), + Folder folder => await Copy(folder, destinationPath), + _ => throw new ArgumentException("Source is not a file or folder", nameof(source)) + }; + } - return await Copy(entry, destinationPath); - } + /// + /// Copy file to another path. + /// + /// Source file info. + /// Destination path. + /// Rename target file. + /// True or false operation result. + public async Task Copy(File file, string destinationPath, string newName) + { + DateTime timestamp = DateTime.Now; + string destPath = destinationPath; + newName = string.IsNullOrEmpty(newName) ? file.Name : newName; + bool doRename = file.Name != newName; - /// - /// Copy item. - /// - /// Source item. - /// Destination path on the server. - /// Rename target item. - /// True or false operation result. - public async Task Copy(IEntry source, string destinationPath, string newName = null) + if (LinkManager is not null) { - if (source is null) throw new ArgumentNullException(nameof(source)); - if (string.IsNullOrEmpty(destinationPath)) throw new ArgumentNullException(nameof(destinationPath)); - - return source switch + var link = await LinkManager.GetItemLink(file.FullPath, false); + // копируем не саму ссылку, а её содержимое + if (link is not null) { - File file => await Copy(file, destinationPath, string.IsNullOrEmpty(newName) ? file.Name : newName), - Folder folder => await Copy(folder, destinationPath), - _ => throw new ArgumentException("Source is not a file or folder", nameof(source)) - }; - } - /// - /// Copy file to another path. - /// - /// Source file info. - /// Destination path. - /// Rename target file. - /// True or false operation result. - public async Task Copy(File file, string destinationPath, string newName) - { - DateTime timestamp = DateTime.Now; - string destPath = destinationPath; - newName = string.IsNullOrEmpty(newName) ? file.Name : newName; - bool doRename = file.Name != newName; + var cloneRes = await CloneItem(destPath, link.Href.OriginalString); + if (!cloneRes.IsSuccess) + return false; - if (LinkManager is not null) - { - var link = await LinkManager.GetItemLink(file.FullPath, false); - // копируем не саму ссылку, а её содержимое - if (link is not null) + if (doRename || WebDavPath.Name(cloneRes.Path) != newName) { - - var cloneRes = await CloneItem(destPath, link.Href.OriginalString); - if (!cloneRes.IsSuccess) + string newFullPath = WebDavPath.Combine(destPath, WebDavPath.Name(cloneRes.Path)); + var renameRes = await Rename(newFullPath, link.Name); + if (!renameRes) return false; + } - if (doRename || WebDavPath.Name(cloneRes.Path) != newName) + return true; + } + } + + var qry = file + .Files + .AsParallel() + .WithDegreeOfParallelism(file.Files.Count) + .Select(async pfile => + { + try { - string newFullPath = WebDavPath.Combine(destPath, WebDavPath.Name(cloneRes.Path)); - var renameRes = await Rename(newFullPath, link.Name); - if (!renameRes) + _entryCache.RegisterOperation(pfile.FullPath, CounterOperation.Copy); + _entryCache.RegisterOperation(destPath, CounterOperation.None); + + //var copyRes = await new CopyRequest(CloudApi, pfile.FullPath, destPath, ConflictResolver.Rewrite).MakeRequestAsync(_connectionLimiter); + var copyRes = await RequestRepo.Copy(pfile.FullPath, destPath, ConflictResolver.Rewrite); + if (!copyRes.IsSuccess) return false; - } - return true; - } - } + if (!doRename && WebDavPath.Name(copyRes.NewName) == newName) + return true; - var qry = file - .Files - .AsParallel() - .WithDegreeOfParallelism(file.Files.Count) - .Select(async pfile => + string newFullPath = WebDavPath.Combine(destPath, WebDavPath.Name(copyRes.NewName)); + return await Rename(newFullPath, pfile.Name.Replace(file.Name, newName)); + } + finally { - try - { - _entryCache.RegisterOperation(pfile.FullPath, CounterOperation.Copy); - _entryCache.RegisterOperation(destPath, CounterOperation.None); - - //var copyRes = await new CopyRequest(CloudApi, pfile.FullPath, destPath, ConflictResolver.Rewrite).MakeRequestAsync(_connectionLimiter); - var copyRes = await RequestRepo.Copy(pfile.FullPath, destPath, ConflictResolver.Rewrite); - if (!copyRes.IsSuccess) - return false; - - if (!doRename && WebDavPath.Name(copyRes.NewName) == newName) - return true; - - string newFullPath = WebDavPath.Combine(destPath, WebDavPath.Name(copyRes.NewName)); - return await Rename(newFullPath, pfile.Name.Replace(file.Name, newName)); - } - finally - { - _entryCache.UnregisterOperation(pfile.FullPath); - _entryCache.UnregisterOperation(destPath); - } - }); - - bool res = (await Task.WhenAll(qry)) - .All(r => r); + _entryCache.UnregisterOperation(pfile.FullPath); + _entryCache.UnregisterOperation(destPath); + } + }); - return res; - } + bool res = (await Task.WhenAll(qry)) + .All(r => r); - #endregion == Copy ========================================================================================================================== + return res; + } - #region == Rename =========================================================================================================================== + #endregion == Copy ========================================================================================================================== - /// - /// Rename item on the server. - /// - /// Source item info. - /// New item name. - /// True or false operation result. - public async Task Rename(IEntry source, string newName) - { - if (source is null) - throw new ArgumentNullException(nameof(source)); - if (string.IsNullOrEmpty(newName)) - throw new ArgumentNullException(nameof(newName)); + #region == Rename =========================================================================================================================== - return source switch - { - File file => await Rename(file, newName), - Folder folder => await Rename(folder, newName), - _ => throw new ArgumentException("Source item is not a file nor folder", nameof(source)) - }; - } + /// + /// Rename item on the server. + /// + /// Source item info. + /// New item name. + /// True or false operation result. + public async Task Rename(IEntry source, string newName) + { + if (source is null) + throw new ArgumentNullException(nameof(source)); + if (string.IsNullOrEmpty(newName)) + throw new ArgumentNullException(nameof(newName)); - /// - /// Rename folder on the server. - /// - /// Source folder info. - /// New folder name. - /// True or false operation result. - public async Task Rename(Folder folder, string newFileName) - => await Rename(folder.FullPath, newFileName); - - /// - /// Rename file on the server. - /// - /// Source file info. - /// New file name. - /// True or false operation result. - public async Task Rename(File file, string newFileName) + return source switch { - var result = await Rename(file.FullPath, newFileName).ConfigureAwait(false); + File file => await Rename(file, newName), + Folder folder => await Rename(folder, newName), + _ => throw new ArgumentException("Source item is not a file nor folder", nameof(source)) + }; + } - if (file.Files.Count <= 1) - return result; + /// + /// Rename folder on the server. + /// + /// Source folder info. + /// New folder name. + /// True or false operation result. + public async Task Rename(Folder folder, string newFileName) + => await Rename(folder.FullPath, newFileName); - foreach (var splitFile in file.Parts) - { - string newSplitName = newFileName + splitFile.ServiceInfo.ToString(false); - await Rename(splitFile.FullPath, newSplitName).ConfigureAwait(false); - } + /// + /// Rename file on the server. + /// + /// Source file info. + /// New file name. + /// True or false operation result. + public async Task Rename(File file, string newFileName) + { + var result = await Rename(file.FullPath, newFileName).ConfigureAwait(false); + if (file.Files.Count <= 1) return result; - } - /// - /// Rename item on server. - /// - /// Full path of the file or folder. - /// New file or path name. - /// True or false result operation. - private async Task Rename(string fullPath, string newName) + foreach (var splitFile in file.Parts) { - var link = LinkManager is null ? null : await LinkManager.GetItemLink(fullPath, false); + string newSplitName = newFileName + splitFile.ServiceInfo.ToString(false); + await Rename(splitFile.FullPath, newSplitName).ConfigureAwait(false); + } + + return result; + } - //rename item - if (link is null) + /// + /// Rename item on server. + /// + /// Full path of the file or folder. + /// New file or path name. + /// True or false result operation. + private async Task Rename(string fullPath, string newName) + { + var link = LinkManager is null ? null : await LinkManager.GetItemLink(fullPath, false); + + //rename item + if (link is null) + { + string newNamePath = WebDavPath.Combine(WebDavPath.Parent(fullPath), newName); + try { - string newNamePath = WebDavPath.Combine(WebDavPath.Parent(fullPath), newName); - try - { - DateTime timestampBeforeOperation = DateTime.Now; - _entryCache.RegisterOperation(fullPath, CounterOperation.Rename); - _entryCache.RegisterOperation(newNamePath, CounterOperation.None); + DateTime timestampBeforeOperation = DateTime.Now; + _entryCache.RegisterOperation(fullPath, CounterOperation.Rename); + _entryCache.RegisterOperation(newNamePath, CounterOperation.None); - var data = await RequestRepo.Rename(fullPath, newName); + var data = await RequestRepo.Rename(fullPath, newName); - if (!data.IsSuccess) - return data.IsSuccess; + if (!data.IsSuccess) + return data.IsSuccess; - LinkManager?.ProcessRename(fullPath, newName); + LinkManager?.ProcessRename(fullPath, newName); - // OnRemove делать до OnCreate - _entryCache.OnRemoveTree(timestampBeforeOperation, - fullPath, GetItemAsync(fullPath, fastGetFromCloud: true)); - _entryCache.OnCreate(timestampBeforeOperation, - newNamePath, GetItemAsync(newNamePath, fastGetFromCloud: true), fullPath); + // OnRemove делать до OnCreate + _entryCache.OnRemoveTree(timestampBeforeOperation, + fullPath, GetItemAsync(fullPath, fastGetFromCloud: true)); + _entryCache.OnCreate(timestampBeforeOperation, + newNamePath, GetItemAsync(newNamePath, fastGetFromCloud: true), fullPath); - return data.IsSuccess; - } - finally - { - _entryCache.UnregisterOperation(fullPath); - _entryCache.UnregisterOperation(newNamePath); - } + return data.IsSuccess; } - - //rename link - if (LinkManager is not null) + finally { - bool res = LinkManager.RenameLink(link, newName); - return res; + _entryCache.UnregisterOperation(fullPath); + _entryCache.UnregisterOperation(newNamePath); } - return false; } - #endregion == Rename ======================================================================================================================== + //rename link + if (LinkManager is not null) + { + bool res = LinkManager.RenameLink(link, newName); + return res; + } + return false; + } - #region == Move ============================================================================================================================= + #endregion == Rename ======================================================================================================================== - /// - /// Move item. - /// - /// source item info. - /// Destination path on the server. - /// True or false operation result. - public async Task MoveAsync(IEntry source, string destinationPath) - { - if (source is null) throw new ArgumentNullException(nameof(source)); - if (string.IsNullOrEmpty(destinationPath)) throw new ArgumentNullException(nameof(destinationPath)); + #region == Move ============================================================================================================================= - return source switch - { - File file => await MoveAsync(file, destinationPath), - Folder folder => await MoveAsync(folder, destinationPath), - _ => throw new ArgumentException("Source item is not a file nor folder", nameof(source)) - }; - } + /// + /// Move item. + /// + /// source item info. + /// Destination path on the server. + /// True or false operation result. + public async Task MoveAsync(IEntry source, string destinationPath) + { + if (source is null) throw new ArgumentNullException(nameof(source)); + if (string.IsNullOrEmpty(destinationPath)) throw new ArgumentNullException(nameof(destinationPath)); - public async Task MoveAsync(string sourcePath, string destinationPath) + return source switch { - var entry = await GetItemAsync(sourcePath); - if (entry is null) - return false; + File file => await MoveAsync(file, destinationPath), + Folder folder => await MoveAsync(folder, destinationPath), + _ => throw new ArgumentException("Source item is not a file nor folder", nameof(source)) + }; + } - return await MoveAsync(entry, destinationPath); - } + public async Task MoveAsync(string sourcePath, string destinationPath) + { + var entry = await GetItemAsync(sourcePath); + if (entry is null) + return false; - public bool Move(string sourcePath, string destinationPath) + return await MoveAsync(entry, destinationPath); + } + + public bool Move(string sourcePath, string destinationPath) + { + return MoveAsync(sourcePath, destinationPath).Result; + } + + + /// + /// Move folder to another place on the server. + /// + /// Folder to move. + /// Destination path on the server. + /// True or false operation result. + public async Task MoveAsync(Folder folder, string destinationPath) + { + var link = LinkManager is null ? null : await LinkManager.GetItemLink(folder.FullPath, false); + if (link is not null) { - return MoveAsync(sourcePath, destinationPath).Result; + var remapped = await LinkManager.RemapLink(link, destinationPath); + return remapped; } - - /// - /// Move folder to another place on the server. - /// - /// Folder to move. - /// Destination path on the server. - /// True or false operation result. - public async Task MoveAsync(Folder folder, string destinationPath) + try { - var link = LinkManager is null ? null : await LinkManager.GetItemLink(folder.FullPath, false); - if (link is not null) - { - var remapped = await LinkManager.RemapLink(link, destinationPath); - return remapped; - } + DateTime timestampBeforeOperation = DateTime.Now; + _entryCache.RegisterOperation(folder.FullPath, CounterOperation.Move); + _entryCache.RegisterOperation(destinationPath, CounterOperation.None); - try - { - DateTime timestampBeforeOperation = DateTime.Now; - _entryCache.RegisterOperation(folder.FullPath,CounterOperation.Move); - _entryCache.RegisterOperation(destinationPath, CounterOperation.None); + var res = await RequestRepo.Move(folder.FullPath, destinationPath); - var res = await RequestRepo.Move(folder.FullPath, destinationPath); + if (!res.IsSuccess) + return false; - if (!res.IsSuccess) - return false; + // OnRemove делать до OnCreate + _entryCache.OnRemoveTree(timestampBeforeOperation, + folder.FullPath, GetItemAsync(folder.FullPath, fastGetFromCloud: true)); + _entryCache.OnCreate(timestampBeforeOperation, + destinationPath, GetItemAsync(destinationPath, fastGetFromCloud: true), folder.FullPath); + } + finally + { + _entryCache.UnregisterOperation(folder.FullPath); + _entryCache.UnregisterOperation(destinationPath); + } - // OnRemove делать до OnCreate - _entryCache.OnRemoveTree(timestampBeforeOperation, - folder.FullPath, GetItemAsync(folder.FullPath, fastGetFromCloud: true)); - _entryCache.OnCreate(timestampBeforeOperation, - destinationPath, GetItemAsync(destinationPath, fastGetFromCloud: true), folder.FullPath); - } - finally + //clone all inner links + if (LinkManager is not null) + { + var links = LinkManager.GetChildren(folder.FullPath).ToList(); + foreach (var linka in links) { - _entryCache.UnregisterOperation(folder.FullPath); - _entryCache.UnregisterOperation(destinationPath); - } + // некоторые клиенты сначала делают структуру каталогов, а потом по одному переносят файлы + // в таких условиях на каждый файл получится свой собственный линк, + // если делать правильно, т.е. в итоге расплодится миллион линков + // поэтому делаем неправильно - копируем содержимое линков + + var linkdest = WebDavPath.ModifyParent(linka.MapPath, WebDavPath.Parent(folder.FullPath), destinationPath); + var cloneres = await CloneItem(linkdest, linka.Href.OriginalString); + if (!cloneres.IsSuccess) + continue; - //clone all inner links - if (LinkManager is not null) - { - var links = LinkManager.GetChildren(folder.FullPath).ToList(); - foreach (var linka in links) + if (WebDavPath.Name(cloneres.Path) != linka.Name) { - // некоторые клиенты сначала делают структуру каталогов, а потом по одному переносят файлы - // в таких условиях на каждый файл получится свой собственный линк, - // если делать правильно, т.е. в итоге расплодится миллион линков - // поэтому делаем неправильно - копируем содержимое линков - - var linkdest = WebDavPath.ModifyParent(linka.MapPath, WebDavPath.Parent(folder.FullPath), destinationPath); - var cloneres = await CloneItem(linkdest, linka.Href.OriginalString); - if (!cloneres.IsSuccess) - continue; - - if (WebDavPath.Name(cloneres.Path) != linka.Name) - { - var renRes = await Rename(cloneres.Path, linka.Name); - if (!renRes) return false; - } + var renRes = await Rename(cloneres.Path, linka.Name); + if (!renRes) return false; } - if (links.Any()) - LinkManager.Save(); } - - return true; + if (links.Any()) + LinkManager.Save(); } - /// - /// Move file in another space on the server. - /// - /// File info to move. - /// Destination path on the server. - /// True or false operation result. - public async Task MoveAsync(File file, string destinationPath) + return true; + } + + /// + /// Move file in another space on the server. + /// + /// File info to move. + /// Destination path on the server. + /// True or false operation result. + public async Task MoveAsync(File file, string destinationPath) + { + DateTime timestampBeforeOperation = DateTime.Now; + var link = LinkManager is null ? null : await LinkManager.GetItemLink(file.FullPath, false); + if (link is not null) { - DateTime timestampBeforeOperation = DateTime.Now; - var link = LinkManager is null ? null : await LinkManager.GetItemLink(file.FullPath, false); - if (link is not null) - { - var remapped = await LinkManager.RemapLink(link, destinationPath); - return remapped; - } + var remapped = await LinkManager.RemapLink(link, destinationPath); + return remapped; + } - var qry = file.Files - .AsParallel() - .WithDegreeOfParallelism(file.Files.Count) - .Select(async pfile => + var qry = file.Files + .AsParallel() + .WithDegreeOfParallelism(file.Files.Count) + .Select(async pfile => + { + try { - try - { - _entryCache.RegisterOperation(pfile.FullPath, CounterOperation.Move); - _entryCache.RegisterOperation(destinationPath, CounterOperation.None); + _entryCache.RegisterOperation(pfile.FullPath, CounterOperation.Move); + _entryCache.RegisterOperation(destinationPath, CounterOperation.None); - var moveRes = await RequestRepo.Move(pfile.FullPath, destinationPath); + var moveRes = await RequestRepo.Move(pfile.FullPath, destinationPath); - if (!moveRes.IsSuccess) - return moveRes; + if (!moveRes.IsSuccess) + return moveRes; - // OnRemove делать до OnCreate - _entryCache.OnRemoveTree(timestampBeforeOperation, - file.FullPath, GetItemAsync(file.FullPath, fastGetFromCloud: true)); - _entryCache.OnCreate(timestampBeforeOperation, - destinationPath, GetItemAsync(destinationPath, fastGetFromCloud: true), file.FullPath); + // OnRemove делать до OnCreate + _entryCache.OnRemoveTree(timestampBeforeOperation, + file.FullPath, GetItemAsync(file.FullPath, fastGetFromCloud: true)); + _entryCache.OnCreate(timestampBeforeOperation, + destinationPath, GetItemAsync(destinationPath, fastGetFromCloud: true), file.FullPath); - return moveRes; - } - finally - { - _entryCache.UnregisterOperation(pfile.FullPath); - _entryCache.UnregisterOperation(destinationPath); - } - }); + return moveRes; + } + finally + { + _entryCache.UnregisterOperation(pfile.FullPath); + _entryCache.UnregisterOperation(destinationPath); + } + }); - bool res = (await Task.WhenAll(qry)) - .All(r => r.IsSuccess); + bool res = (await Task.WhenAll(qry)) + .All(r => r.IsSuccess); - return res; - } + return res; + } - #endregion == Move ========================================================================================================================== + #endregion == Move ========================================================================================================================== - #region == Remove =========================================================================================================================== + #region == Remove =========================================================================================================================== - /// - /// Remove item on server by path - /// - /// File or folder - /// True or false operation result. - public virtual async Task Remove(IEntry entry) + /// + /// Remove item on server by path + /// + /// File or folder + /// True or false operation result. + public virtual async Task Remove(IEntry entry) + { + return entry switch { - return entry switch + File file => await Remove(file), + Folder folder => await Remove(folder), + _ => false + }; + } + + /// + /// Remove the folder on server. + /// + /// Folder info. + /// True or false operation result. + public async Task Remove(Folder folder) + { + return await Remove(folder.FullPath); + } + + /// + /// Remove the file on server. + /// + /// File info. + /// Also remove share description file (.share.wdmrc) + /// True or false operation result. + public virtual async Task Remove(File file, bool removeShareDescription = true) //, bool doInvalidateCache = true) + { + // remove all parts if file splitted + var qry = file.Files + .AsParallel() + .WithDegreeOfParallelism(file.Files.Count) + .Select(async pfile => { - File file => await Remove(file), - Folder folder => await Remove(folder), - _ => false - }; - } + var removed = await Remove(pfile.FullPath); + return removed; + }); + bool res = (await Task.WhenAll(qry)).All(r => r); - /// - /// Remove the folder on server. - /// - /// Folder info. - /// True or false operation result. - public async Task Remove(Folder folder) + if (res) { - return await Remove(folder.FullPath); - } + if (file.Name.EndsWith(PublishInfo.SharedFilePostfix)) //unshare master item + { + var mpath = WebDavPath.Clean(file.FullPath.Substring(0, file.FullPath.Length - PublishInfo.SharedFilePostfix.Length)); + var entry = await GetItemAsync(mpath); - /// - /// Remove the file on server. - /// - /// File info. - /// Also remove share description file (.share.wdmrc) - /// True or false operation result. - public virtual async Task Remove(File file, bool removeShareDescription = true) //, bool doInvalidateCache = true) - { - // remove all parts if file splitted - var qry = file.Files - .AsParallel() - .WithDegreeOfParallelism(file.Files.Count) - .Select(async pfile => + switch (entry) { - var removed = await Remove(pfile.FullPath); - return removed; - }); - bool res = (await Task.WhenAll(qry)).All(r => r); - - if (res) + case Folder folder: + await Unpublish(folder.GetPublicLinks(this).FirstOrDefault().Uri, folder.FullPath); + break; + case File ifile: + await Unpublish(ifile); + break; + } + } + else { - if (file.Name.EndsWith(PublishInfo.SharedFilePostfix)) //unshare master item + if (removeShareDescription) //remove share description (.wdmrc.share) { - var mpath = WebDavPath.Clean(file.FullPath.Substring(0, file.FullPath.Length - PublishInfo.SharedFilePostfix.Length)); - var entry = await GetItemAsync(mpath); + string fullName = string.Concat(file.FullPath, PublishInfo.SharedFilePostfix); + string path = WebDavPath.Parent(file.FullPath); + string name = WebDavPath.Name(file.FullPath); + string foundFullPath = Find(name, path); - switch (entry) + if (foundFullPath is not null && + await GetItemAsync(foundFullPath) is File sharefile) { - case Folder folder: - await Unpublish(folder.GetPublicLinks(this).FirstOrDefault().Uri, folder.FullPath); - break; - case File ifile: - await Unpublish(ifile); - break; + await Remove(sharefile, false); } } - else - { - if (removeShareDescription) //remove share description (.wdmrc.share) - { - string fullName = string.Concat(file.FullPath, PublishInfo.SharedFilePostfix); - string path = WebDavPath.Parent(file.FullPath); - string name = WebDavPath.Name(file.FullPath); - string foundFullPath = Find(name, path); - - if (foundFullPath is not null && - await GetItemAsync(foundFullPath) is File sharefile) - { - await Remove(sharefile, false); - } - } - } - } - return res; } - /// - /// Remove file or folder. - /// - /// Full file or folder name. - /// True or false result operation. - public async Task Remove(string fullPath) - { - DateTime timestamp = DateTime.Now; - - if (LinkManager is not null) - { - var link = await LinkManager.GetItemLink(fullPath, false); - if (link is not null) - { - // if folder is linked - do not delete inner files/folders - // if client deleting recursively just try to unlink folder - return LinkManager.RemoveLink(fullPath); - } - } - - try - { - _entryCache.RegisterOperation(fullPath, CounterOperation.RemoveToTrash); - - var res = await RequestRepo.Remove(fullPath); - - if (!res.IsSuccess) - return false; + return res; + } - _entryCache.OnRemoveTree(timestamp, fullPath, GetItemAsync(fullPath, fastGetFromCloud: true)); - } - finally - { - _entryCache.UnregisterOperation(fullPath); - } + /// + /// Remove file or folder. + /// + /// Full file or folder name. + /// True or false result operation. + public async Task Remove(string fullPath) + { + DateTime timestamp = DateTime.Now; - // remove inner links - if (LinkManager is not null) + if (LinkManager is not null) + { + var link = await LinkManager.GetItemLink(fullPath, false); + if (link is not null) { - var innerLinks = LinkManager.GetChildren(fullPath); - LinkManager.RemoveLinks(innerLinks); + // if folder is linked - do not delete inner files/folders + // if client deleting recursively just try to unlink folder + return LinkManager.RemoveLink(fullPath); } - - return true; } - #endregion == Remove ======================================================================================================================== - - public IEnumerable GetSharedLinks(string fullPath) + try { - return RequestRepo.GetShareLinks(fullPath); - } + _entryCache.RegisterOperation(fullPath, CounterOperation.RemoveToTrash); - /// - /// Get disk usage for account. - /// - /// Returns Total/Free/Used size. - public async Task GetDiskUsageAsync() - { - var data = await RequestRepo.AccountInfo(); - return data.DiskUsage; - } - public DiskUsage GetDiskUsage() - { - return GetDiskUsageAsync().Result; - } + var res = await RequestRepo.Remove(fullPath); + if (!res.IsSuccess) + return false; - /// - /// Abort all prolonged async operations. - /// - public void AbortAllAsyncThreads() - { - CancelToken.Cancel(false); + _entryCache.OnRemoveTree(timestamp, fullPath, GetItemAsync(fullPath, fastGetFromCloud: true)); } - - /// - /// Create folder on the server. - /// - /// New path name. - /// Destination path. - /// True or false operation result. - public async Task CreateFolderAsync(string name, string basePath) + finally { - return await CreateFolderAsync(WebDavPath.Combine(basePath, name)); + _entryCache.UnregisterOperation(fullPath); } - public bool CreateFolder(string name, string basePath) + // remove inner links + if (LinkManager is not null) { - return CreateFolderAsync(name, basePath).Result; + var innerLinks = LinkManager.GetChildren(fullPath); + LinkManager.RemoveLinks(innerLinks); } - public async Task CreateFolderAsync(string fullPath) - { - try - { - DateTime timestampBeforeOperation = DateTime.Now; + return true; + } - _entryCache.RegisterOperation(fullPath, CounterOperation.NewFolder); + #endregion == Remove ======================================================================================================================== - var res = await RequestRepo.CreateFolder(fullPath); + public IEnumerable GetSharedLinks(string fullPath) + { + return RequestRepo.GetShareLinks(fullPath); + } - if (!res.IsSuccess) - return false; + /// + /// Get disk usage for account. + /// + /// Returns Total/Free/Used size. + public async Task GetDiskUsageAsync() + { + var data = await RequestRepo.AccountInfo(); + return data.DiskUsage; + } + public DiskUsage GetDiskUsage() + { + return GetDiskUsageAsync().Result; + } - _entryCache.OnCreate(timestampBeforeOperation, - fullPath, GetItemAsync(fullPath, fastGetFromCloud: true), null); - } - finally - { - _entryCache.UnregisterOperation(fullPath); - } - return true; - } + /// + /// Abort all prolonged async operations. + /// + public void AbortAllAsyncThreads() + { + CancelToken.Cancel(false); + } - //public bool CreateFolder(string fullPath) - //{ - // return CreateFolderAsync(fullPath).Result; - //} + /// + /// Create folder on the server. + /// + /// New path name. + /// Destination path. + /// True or false operation result. + public async Task CreateFolderAsync(string name, string basePath) + { + return await CreateFolderAsync(WebDavPath.Combine(basePath, name)); + } + public bool CreateFolder(string name, string basePath) + { + return CreateFolderAsync(name, basePath).Result; + } - public async Task CloneItem(string toPath, string fromUrl) + public async Task CreateFolderAsync(string fullPath) + { + try { - try - { - DateTime timestampBeforeOperation = DateTime.Now; - _entryCache.RegisterOperation(toPath, CounterOperation.Copy); - _entryCache.RegisterOperation(fromUrl, CounterOperation.None); + DateTime timestampBeforeOperation = DateTime.Now; - var res = await RequestRepo.CloneItem(fromUrl, toPath); + _entryCache.RegisterOperation(fullPath, CounterOperation.NewFolder); - if (!res.IsSuccess) - return res; + var res = await RequestRepo.CreateFolder(fullPath); - _entryCache.OnCreate(timestampBeforeOperation, - toPath, GetItemAsync(toPath, fastGetFromCloud: true), null); + if (!res.IsSuccess) + return false; - return res; - } - finally - { - _entryCache.UnregisterOperation(toPath); - _entryCache.UnregisterOperation(fromUrl); - } + _entryCache.OnCreate(timestampBeforeOperation, + fullPath, GetItemAsync(fullPath, fastGetFromCloud: true), null); } - - public async Task GetFileDownloadStream(File file, long? start, long? end) + finally { - var result = new DownloadStreamFabric(this).Create(file, start, end); - var task = Task.FromResult(result).ConfigureAwait(false); - Stream stream = await task; - return stream; + _entryCache.UnregisterOperation(fullPath); } + return true; + } - public async Task GetFileUploadStream(string fullFilePath, long size, Action fileStreamSent, Action serverFileProcessed, bool discardEncryption = false) - { - var file = new File(fullFilePath, size); + //public bool CreateFolder(string fullPath) + //{ + // return CreateFolderAsync(fullPath).Result; + //} - var f = new UploadStreamFabric(this) - { - FileStreamSent = fileStreamSent, - ServerFileProcessed = serverFileProcessed - }; - var task = await Task.FromResult(f.Create(file, OnFileUploaded, discardEncryption)).ConfigureAwait(false); - var stream = await task; + public async Task CloneItem(string toPath, string fromUrl) + { + try + { + DateTime timestampBeforeOperation = DateTime.Now; + _entryCache.RegisterOperation(toPath, CounterOperation.Copy); + _entryCache.RegisterOperation(fromUrl, CounterOperation.None); - return stream; - } + var res = await RequestRepo.CloneItem(fromUrl, toPath); - public event FileUploadedDelegate FileUploaded; + if (!res.IsSuccess) + return res; - private void OnFileUploaded(IEnumerable files) - { - var lst = files.ToList(); - FileUploaded?.Invoke(lst); - } + _entryCache.OnCreate(timestampBeforeOperation, + toPath, GetItemAsync(toPath, fastGetFromCloud: true), null); - public void OnBeforeUpload(string path) - { - _entryCache.RegisterOperation(path, CounterOperation.Upload); + return res; } - - public void OnAfterUpload(string path, DateTime timestampBeforeOperation) + finally { - _entryCache.OnCreate(timestampBeforeOperation, path, GetItemAsync(path, fastGetFromCloud: true), null); - _entryCache.UnregisterOperation(path); + _entryCache.UnregisterOperation(toPath); + _entryCache.UnregisterOperation(fromUrl); } + } - public T DownloadFileAsJson(File file) - { - using var stream = RequestRepo.GetDownloadStream(file); - using var reader = new StreamReader(stream); - using var jsonReader = new JsonTextReader(reader); + public async Task GetFileDownloadStream(File file, long? start, long? end) + { + var result = new DownloadStreamFabric(this).Create(file, start, end); + var task = Task.FromResult(result).ConfigureAwait(false); + Stream stream = await task; + return stream; + } - var ser = new JsonSerializer(); - return ser.Deserialize(jsonReader); - } - /// - /// Download content of file - /// - /// - /// file content or null if NotFound - public async Task DownloadFileAsString(string path) + public async Task GetFileUploadStream(string fullFilePath, long size, Action fileStreamSent, Action serverFileProcessed, bool discardEncryption = false) + { + var file = new File(fullFilePath, size); + + var f = new UploadStreamFabric(this) { - try - { - var entry = await GetItemAsync(path); - if (entry is null || entry is not File file) - return null; - { - using var stream = RequestRepo.GetDownloadStream(file); - using var reader = new StreamReader(stream); + FileStreamSent = fileStreamSent, + ServerFileProcessed = serverFileProcessed + }; - string res = await reader.ReadToEndAsync(); - return res; - } - } - catch (Exception e) - when ( // let's check if there really no file or just other network error - e is AggregateException && - e.InnerException is WebException we && - we.Response is HttpWebResponse { StatusCode: HttpStatusCode.NotFound } - || - e is WebException wee && - wee.Response is HttpWebResponse { StatusCode: HttpStatusCode.NotFound } - ) - { - return null; - } - } + var task = await Task.FromResult(f.Create(file, OnFileUploaded, discardEncryption)).ConfigureAwait(false); + var stream = await task; + + return stream; + } + public event FileUploadedDelegate FileUploaded; - public bool UploadFile(string path, byte[] content, bool discardEncryption = false) - { - DateTime timestamp = DateTime.Now; - using var stream = GetFileUploadStream(path, content.Length, null, null, discardEncryption).Result; - stream.Write(content, 0, content.Length); - return true; - } + private void OnFileUploaded(IEnumerable files) + { + var lst = files.ToList(); + FileUploaded?.Invoke(lst); + } + public void OnBeforeUpload(string path) + { + _entryCache.RegisterOperation(path, CounterOperation.Upload); + } - public bool UploadFile(string path, string content, bool discardEncryption = false) - { - var data = Encoding.UTF8.GetBytes(content); - return UploadFile(path, data, discardEncryption); - } + public void OnAfterUpload(string path, DateTime timestampBeforeOperation) + { + _entryCache.OnCreate(timestampBeforeOperation, path, GetItemAsync(path, fastGetFromCloud: true), null); + _entryCache.UnregisterOperation(path); + } - public bool UploadFileJson(string fullFilePath, T data, bool discardEncryption = false) - { - string content = JsonConvert.SerializeObject(data, Formatting.Indented); - UploadFile(fullFilePath, content, discardEncryption); - return true; - } + public T DownloadFileAsJson(File file) + { + using var stream = RequestRepo.GetDownloadStream(file); + using var reader = new StreamReader(stream); + using var jsonReader = new JsonTextReader(reader); - #region IDisposable Support - private bool _disposedValue; + var ser = new JsonSerializer(); + return ser.Deserialize(jsonReader); + } - protected virtual void Dispose(bool disposing) + /// + /// Download content of file + /// + /// + /// file content or null if NotFound + public async Task DownloadFileAsString(string path) + { + try { - if (_disposedValue) return; - if (disposing) + var entry = await GetItemAsync(path); + if (entry is null || entry is not File file) + return null; { - _entryCache?.Dispose(); - CancelToken?.Dispose(); + using var stream = RequestRepo.GetDownloadStream(file); + using var reader = new StreamReader(stream); + + string res = await reader.ReadToEndAsync(); + return res; } - _disposedValue = true; } + catch (Exception e) + when ( // let's check if there really no file or just other network error + e is AggregateException && + e.InnerException is WebException we && + we.Response is HttpWebResponse { StatusCode: HttpStatusCode.NotFound } + || + e is WebException wee && + wee.Response is HttpWebResponse { StatusCode: HttpStatusCode.NotFound } + ) + { + return null; + } + } - public void Dispose() => Dispose(true); - #endregion + public bool UploadFile(string path, byte[] content, bool discardEncryption = false) + { + DateTime timestamp = DateTime.Now; + using var stream = GetFileUploadStream(path, content.Length, null, null, discardEncryption).Result; + stream.Write(content, 0, content.Length); + return true; + } - public async Task LinkItem(Uri url, string path, string name, bool isFile, long size, DateTime? creationDate) - { - if (LinkManager is null) - return false; - DateTime timestampBeforeOperation = DateTime.Now; + public bool UploadFile(string path, string content, bool discardEncryption = false) + { + var data = Encoding.UTF8.GetBytes(content); + return UploadFile(path, data, discardEncryption); + } - var res = await LinkManager.Add(url, path, name, isFile, size, creationDate); - if (res) - { - LinkManager.Save(); - _entryCache.OnCreate(timestampBeforeOperation, path, GetItemAsync(path, fastGetFromCloud: true), null); - } - return res; - } + public bool UploadFileJson(string fullFilePath, T data, bool discardEncryption = false) + { + string content = JsonConvert.SerializeObject(data, Formatting.Indented); + UploadFile(fullFilePath, content, discardEncryption); + return true; + } - public async void RemoveDeadLinks() - { - if (LinkManager is null) - return; + #region IDisposable Support + private bool _disposedValue; - var count = await LinkManager.RemoveDeadLinks(true); - if (count > 0) - _entryCache.Clear(); + protected virtual void Dispose(bool disposing) + { + if (_disposedValue) return; + if (disposing) + { + _entryCache?.Dispose(); + CancelToken?.Dispose(); } + _disposedValue = true; + } - public async Task AddFile(IFileHash hash, string fullFilePath, long size, ConflictResolver? conflict = null) - { - try - { - DateTime timestampBeforeOperation = DateTime.Now; - _entryCache.RegisterOperation(fullFilePath, CounterOperation.Upload); + public void Dispose() => Dispose(true); - var res = await RequestRepo.AddFile(fullFilePath, hash, size, DateTime.Now, conflict); + #endregion - if (!res.Success) - return res; + public async Task LinkItem(Uri url, string path, string name, bool isFile, long size, DateTime? creationDate) + { + if (LinkManager is null) + return false; - _entryCache.OnCreate(timestampBeforeOperation, fullFilePath, GetItemAsync(fullFilePath, fastGetFromCloud: true), null); + DateTime timestampBeforeOperation = DateTime.Now; - return res; - } - finally - { - _entryCache.UnregisterOperation(fullFilePath); - } + var res = await LinkManager.Add(url, path, name, isFile, size, creationDate); + if (res) + { + LinkManager.Save(); + _entryCache.OnCreate(timestampBeforeOperation, path, GetItemAsync(path, fastGetFromCloud: true), null); } + return res; + } - public async Task AddFileInCloud(File fileInfo, ConflictResolver? conflict = null) - { - var res = await AddFile(fileInfo.Hash, fileInfo.FullPath, fileInfo.OriginalSize, conflict); + public async void RemoveDeadLinks() + { + if (LinkManager is null) + return; - return res; - } + var count = await LinkManager.RemoveDeadLinks(true); + if (count > 0) + _entryCache.Clear(); + } - public async Task SetFileDateTime(File file, DateTime dateTime) + public async Task AddFile(IFileHash hash, string fullFilePath, long size, ConflictResolver? conflict = null) + { + try { - if (file.LastWriteTimeUtc == dateTime) - return true; + DateTime timestampBeforeOperation = DateTime.Now; + _entryCache.RegisterOperation(fullFilePath, CounterOperation.Upload); - try - { - DateTime timestampBeforeOperation = DateTime.Now; - _entryCache.RegisterOperation(file.FullPath, CounterOperation.Upload); + var res = await RequestRepo.AddFile(fullFilePath, hash, size, DateTime.Now, conflict); - var res = await RequestRepo.AddFile(file.FullPath, file.Hash, file.Size, dateTime, ConflictResolver.Rename); + if (!res.Success) + return res; - if (!res.Success) - return false; + _entryCache.OnCreate(timestampBeforeOperation, fullFilePath, GetItemAsync(fullFilePath, fastGetFromCloud: true), null); - file.LastWriteTimeUtc = dateTime; + return res; + } + finally + { + _entryCache.UnregisterOperation(fullFilePath); + } + } - _entryCache.OnCreate(timestampBeforeOperation, file.FullPath, GetItemAsync(file.FullPath, fastGetFromCloud: true), null); + public async Task AddFileInCloud(File fileInfo, ConflictResolver? conflict = null) + { + var res = await AddFile(fileInfo.Hash, fileInfo.FullPath, fileInfo.OriginalSize, conflict); - return true; - } - finally - { - _entryCache.UnregisterOperation(file.FullPath); - } - } + return res; + } - /// - /// Создаёт в каталоге признак, что файлы в нём будут шифроваться - /// - /// - /// - public async Task CryptInit(Folder folder) + public async Task SetFileDateTime(File file, DateTime dateTime) + { + if (file.LastWriteTimeUtc == dateTime) + return true; + + try { - // do not allow to crypt root path... don't know for what - if (WebDavPath.PathEquals(folder.FullPath, WebDavPath.Root)) - return false; + DateTime timestampBeforeOperation = DateTime.Now; + _entryCache.RegisterOperation(file.FullPath, CounterOperation.Upload); - string filepath = WebDavPath.Combine(folder.FullPath, CryptFileInfo.FileName); - var file = await GetItemAsync(filepath).ConfigureAwait(false); + var res = await RequestRepo.AddFile(file.FullPath, file.Hash, file.Size, dateTime, ConflictResolver.Rename); - if (file is not null) + if (!res.Success) return false; - var content = new CryptFileInfo - { - Initialized = DateTime.Now - }; + file.LastWriteTimeUtc = dateTime; - var res = UploadFileJson(filepath, content); - return res; - } + _entryCache.OnCreate(timestampBeforeOperation, file.FullPath, GetItemAsync(file.FullPath, fastGetFromCloud: true), null); - public void CleanTrash() + return true; + } + finally { - RequestRepo.CleanTrash(); + _entryCache.UnregisterOperation(file.FullPath); } } + + /// + /// Создаёт в каталоге признак, что файлы в нём будут шифроваться + /// + /// + /// + public async Task CryptInit(Folder folder) + { + // do not allow to crypt root path... don't know for what + if (WebDavPath.PathEquals(folder.FullPath, WebDavPath.Root)) + return false; + + string filepath = WebDavPath.Combine(folder.FullPath, CryptFileInfo.FileName); + var file = await GetItemAsync(filepath).ConfigureAwait(false); + + if (file is not null) + return false; + + var content = new CryptFileInfo + { + Initialized = DateTime.Now + }; + + var res = UploadFileJson(filepath, content); + return res; + } + + public void CleanTrash() + { + RequestRepo.CleanTrash(); + } } diff --git a/MailRuCloud/MailRuCloudApi/CloudSettings.cs b/MailRuCloud/MailRuCloudApi/CloudSettings.cs index 911671a5..08dc39cd 100644 --- a/MailRuCloud/MailRuCloudApi/CloudSettings.cs +++ b/MailRuCloud/MailRuCloudApi/CloudSettings.cs @@ -3,58 +3,57 @@ using YaR.Clouds.Base.Streams.Cache; using YaR.Clouds.Common; -namespace YaR.Clouds +namespace YaR.Clouds; + +public class CloudSettings { - public class CloudSettings - { - public ITwoFaHandler TwoFaHandler { get; set; } + public ITwoFaHandler TwoFaHandler { get; set; } - public string UserAgent { get; set; } - public string SecChUa { get; set; } + public string UserAgent { get; set; } + public string SecChUa { get; set; } - public Protocol Protocol { get; set; } = Protocol.Autodetect; + public Protocol Protocol { get; set; } = Protocol.Autodetect; - public int CacheListingSec { get; set; } = 30; + public int CacheListingSec { get; set; } = 30; - public int MaxConnectionCount { get; set; } = 10; + public int MaxConnectionCount { get; set; } = 10; - public int ListDepth - { - get => CacheListingSec > 0 ? _listDepth : 1; - set => _listDepth = value; - } - private int _listDepth = 1; + public int ListDepth + { + get => CacheListingSec > 0 ? _listDepth : 1; + set => _listDepth = value; + } + private int _listDepth = 1; - public string SpecialCommandPrefix { get; set; } = ">>"; - public string AdditionalSpecialCommandPrefix { get; set; } = ">>"; + public string SpecialCommandPrefix { get; set; } = ">>"; + public string AdditionalSpecialCommandPrefix { get; set; } = ">>"; - public SharedVideoResolution DefaultSharedVideoResolution { get; set; } = SharedVideoResolution.All; - public IWebProxy Proxy { get; set; } - public bool UseLocks { get; set; } + public SharedVideoResolution DefaultSharedVideoResolution { get; set; } = SharedVideoResolution.All; + public IWebProxy Proxy { get; set; } + public bool UseLocks { get; set; } - public bool UseDeduplicate { get; set; } + public bool UseDeduplicate { get; set; } - public DeduplicateRulesBag DeduplicateRules { get; set; } + public DeduplicateRulesBag DeduplicateRules { get; set; } - public bool DisableLinkManager { get; set; } + public bool DisableLinkManager { get; set; } - public int CloudInstanceTimeoutMinutes { get; set; } + public int CloudInstanceTimeoutMinutes { get; set; } - #region Connection timeouts + #region Connection timeouts - public int Wait100ContinueTimeoutSec { get; set; } - public int WaitResponseTimeoutSec { get; set; } - public int ReadWriteTimeoutSec { get; set; } + public int Wait100ContinueTimeoutSec { get; set; } + public int WaitResponseTimeoutSec { get; set; } + public int ReadWriteTimeoutSec { get; set; } - #endregion - #region BrowserAuthenticator + #endregion + #region BrowserAuthenticator - public string BrowserAuthenticatorUrl { get; set; } + public string BrowserAuthenticatorUrl { get; set; } - public string BrowserAuthenticatorPassword { get; set; } + public string BrowserAuthenticatorPassword { get; set; } - public string BrowserAuthenticatorCacheDir { get; set; } + public string BrowserAuthenticatorCacheDir { get; set; } - #endregion - } + #endregion } diff --git a/MailRuCloud/MailRuCloudApi/Common/Cached.cs b/MailRuCloud/MailRuCloudApi/Common/Cached.cs index 1bb732d5..3a439397 100644 --- a/MailRuCloud/MailRuCloudApi/Common/Cached.cs +++ b/MailRuCloud/MailRuCloudApi/Common/Cached.cs @@ -1,75 +1,74 @@ using System; using System.Threading; -namespace YaR.Clouds.Common +namespace YaR.Clouds.Common; + +public class Cached { - public class Cached - { - private readonly Func _duration; - private DateTime _expiration; - private Lazy _value; - private readonly Func _valueFactory; + private readonly Func _duration; + private DateTime _expiration; + private Lazy _value; + private readonly Func _valueFactory; - public T Value + public T Value + { + get { - get - { - RefreshValueIfNeeded(); - return _value.Value; - } + RefreshValueIfNeeded(); + return _value.Value; } + } - public Cached(Func valueFactory, Func duration) - { - _duration = duration; - _valueFactory = valueFactory; + public Cached(Func valueFactory, Func duration) + { + _duration = duration; + _valueFactory = valueFactory; - RefreshValueIfNeeded(); - } + RefreshValueIfNeeded(); + } - private readonly SemaphoreSlim _locker = new SemaphoreSlim(1); + private readonly SemaphoreSlim _locker = new SemaphoreSlim(1); - private void RefreshValueIfNeeded() + private void RefreshValueIfNeeded() + { + if (DateTime.Now < _expiration) + return; + + _locker.Wait(); + try { if (DateTime.Now < _expiration) return; - _locker.Wait(); - try - { - if (DateTime.Now < _expiration) - return; - - T oldValue = _value is { IsValueCreated: true } ? _value.Value : default; - _value = new Lazy(() => _valueFactory(oldValue)); + T oldValue = _value is { IsValueCreated: true } ? _value.Value : default; + _value = new Lazy(() => _valueFactory(oldValue)); - var duration = _duration(_value.Value); - _expiration = duration == TimeSpan.MaxValue - ? DateTime.MaxValue - : DateTime.Now.Add(duration); - } - finally - { - _locker.Release(); - } + var duration = _duration(_value.Value); + _expiration = duration == TimeSpan.MaxValue + ? DateTime.MaxValue + : DateTime.Now.Add(duration); } - - public override string ToString() + finally { - return Value.ToString(); + _locker.Release(); } + } + + public override string ToString() + { + return Value.ToString(); + } - public void Expire() + public void Expire() + { + _locker.Wait(); + try + { + _expiration = DateTime.MinValue; + } + finally { - _locker.Wait(); - try - { - _expiration = DateTime.MinValue; - } - finally - { - _locker.Release(); - } + _locker.Release(); } } } diff --git a/MailRuCloud/MailRuCloudApi/Common/CustomDisposable.cs b/MailRuCloud/MailRuCloudApi/Common/CustomDisposable.cs index ee9c251b..999802e4 100644 --- a/MailRuCloud/MailRuCloudApi/Common/CustomDisposable.cs +++ b/MailRuCloud/MailRuCloudApi/Common/CustomDisposable.cs @@ -1,19 +1,18 @@ using System; -namespace YaR.Clouds.Common +namespace YaR.Clouds.Common; + +class CustomDisposable : IDisposable { - class CustomDisposable : IDisposable - { - public T Value { get; set; } - public Action OnDispose { get; set; } + public T Value { get; set; } + public Action OnDispose { get; set; } - public void Dispose() - { - var value = Value; - if (value is IDisposable disp) - disp.Dispose(); + public void Dispose() + { + var value = Value; + if (value is IDisposable disp) + disp.Dispose(); - OnDispose?.Invoke(); - } + OnDispose?.Invoke(); } } diff --git a/MailRuCloud/MailRuCloudApi/Common/EntryCache.cs b/MailRuCloud/MailRuCloudApi/Common/EntryCache.cs index eba45382..3b1f9eaf 100644 --- a/MailRuCloud/MailRuCloudApi/Common/EntryCache.cs +++ b/MailRuCloud/MailRuCloudApi/Common/EntryCache.cs @@ -9,7 +9,6 @@ using YaR.Clouds.Base.Requests.Types; using System.Linq; using static YaR.Clouds.Extensions.Extensions; -using System.Collections.Immutable; namespace YaR.Clouds.Common; diff --git a/MailRuCloud/MailRuCloudApi/Common/Pending.cs b/MailRuCloud/MailRuCloudApi/Common/Pending.cs index cbace255..9b6329d3 100644 --- a/MailRuCloud/MailRuCloudApi/Common/Pending.cs +++ b/MailRuCloud/MailRuCloudApi/Common/Pending.cs @@ -3,77 +3,76 @@ using System.Linq; using System.Threading; -namespace YaR.Clouds.Common +namespace YaR.Clouds.Common; + +class Pending where T : class { - class Pending where T : class - { - private readonly List> _items = new(); - private readonly int _maxLocks; - private readonly Func _valueFactory; + private readonly List> _items = new(); + private readonly int _maxLocks; + private readonly Func _valueFactory; - public Pending(int maxLocks, Func valueFactory) - { - _maxLocks = maxLocks; - _valueFactory = valueFactory; - } + public Pending(int maxLocks, Func valueFactory) + { + _maxLocks = maxLocks; + _valueFactory = valueFactory; + } - private readonly SemaphoreSlim _locker = new SemaphoreSlim(1); + private readonly SemaphoreSlim _locker = new SemaphoreSlim(1); - public T Next(T current) + public T Next(T current) + { + _locker.Wait(); + try { - _locker.Wait(); - try - { - var item = current is null - ? _items.FirstOrDefault(it => it.LockCount < _maxLocks) - : _items.SkipWhile(it => !it.Equals(current)).Skip(1).FirstOrDefault(it => it.LockCount < _maxLocks); + var item = current is null + ? _items.FirstOrDefault(it => it.LockCount < _maxLocks) + : _items.SkipWhile(it => !it.Equals(current)).Skip(1).FirstOrDefault(it => it.LockCount < _maxLocks); - if (item is null) - _items.Add(item = new PendingItem { Item = _valueFactory(), LockCount = 0 }); + if (item is null) + _items.Add(item = new PendingItem { Item = _valueFactory(), LockCount = 0 }); - item.LockCount++; + item.LockCount++; - return item.Item; - } - finally - { - _locker.Release(); - } + return item.Item; } - - public void Free(T value) + finally { - if (value is null) - return; + _locker.Release(); + } + } + + public void Free(T value) + { + if (value is null) + return; - _locker.Wait(); - try + _locker.Wait(); + try + { + foreach (var item in _items) { - foreach (var item in _items) + if (item.Item.Equals(value)) { - if (item.Item.Equals(value)) + switch (item.LockCount) { - switch (item.LockCount) - { - case <= 0: - throw new Exception("Pending item count <= 0"); - case > 0: - item.LockCount--; - break; - } + case <= 0: + throw new Exception("Pending item count <= 0"); + case > 0: + item.LockCount--; + break; } } } - finally - { - _locker.Release(); - } + } + finally + { + _locker.Release(); } } +} - class PendingItem - { - public T Item { get; set; } - public int LockCount { get; set; } - } +class PendingItem +{ + public T Item { get; set; } + public int LockCount { get; set; } } diff --git a/MailRuCloud/MailRuCloudApi/Common/Retry.cs b/MailRuCloud/MailRuCloudApi/Common/Retry.cs index b34c4b34..74b62875 100644 --- a/MailRuCloud/MailRuCloudApi/Common/Retry.cs +++ b/MailRuCloud/MailRuCloudApi/Common/Retry.cs @@ -3,90 +3,89 @@ using System.Diagnostics; using System.Threading; -namespace YaR.Clouds.Common +namespace YaR.Clouds.Common; + +public static class Retry { - public static class Retry + public static T Do( + Func action, + Func retryIf, + Action onException, + TimeSpan retryInterval, + int maxAttemptCount = 3) { - public static T Do( - Func action, - Func retryIf, - Action onException, - TimeSpan retryInterval, - int maxAttemptCount = 3) - { - var exceptions = new List(); + var exceptions = new List(); - for (int attempted = 0; attempted < maxAttemptCount; attempted++) + for (int attempted = 0; attempted < maxAttemptCount; attempted++) + { + try { - try - { - if (attempted > 0) - Thread.Sleep(retryInterval); + if (attempted > 0) + Thread.Sleep(retryInterval); - return action(); - } - catch (Exception ex) - { - onException?.Invoke(ex); - exceptions.Add(ex); - if (retryIf != null && !retryIf(ex)) - throw; - } + return action(); + } + catch (Exception ex) + { + onException?.Invoke(ex); + exceptions.Add(ex); + if (retryIf != null && !retryIf(ex)) + throw; } - throw new AggregateException(exceptions); } + throw new AggregateException(exceptions); + } - public static T Do( - Func sleepBefore, - Func action, - Func retryIf, - TimeSpan retryInterval, - int maxAttemptCount = 3) - { - var sleep = sleepBefore(); - if (sleep > TimeSpan.Zero) - Thread.Sleep(sleep); - - T res = default; - for (int attempted = 0; attempted < maxAttemptCount; attempted++) - { - if (attempted > 0) - Thread.Sleep(retryInterval); + public static T Do( + Func sleepBefore, + Func action, + Func retryIf, + TimeSpan retryInterval, + int maxAttemptCount = 3) + { + var sleep = sleepBefore(); + if (sleep > TimeSpan.Zero) + Thread.Sleep(sleep); - res = action(); - if (!retryIf(res)) - return res; - } + T res = default; + for (int attempted = 0; attempted < maxAttemptCount; attempted++) + { + if (attempted > 0) + Thread.Sleep(retryInterval); - return res; + res = action(); + if (!retryIf(res)) + return res; } - public static T Do( - Func sleepBefore, - Func action, - Func retryIf, - TimeSpan retryInterval, - TimeSpan? retryTimeout = null) - { - retryTimeout ??= TimeSpan.FromSeconds(15); + return res; + } - var sleep = sleepBefore(); - if (sleep > TimeSpan.Zero) - Thread.Sleep(sleep); + public static T Do( + Func sleepBefore, + Func action, + Func retryIf, + TimeSpan retryInterval, + TimeSpan? retryTimeout = null) + { + retryTimeout ??= TimeSpan.FromSeconds(15); - var watch = Stopwatch.StartNew(); - T res = default; - for (int attempted = 0; watch.Elapsed < retryTimeout; attempted++) - { - if (attempted > 0) - Thread.Sleep(retryInterval); + var sleep = sleepBefore(); + if (sleep > TimeSpan.Zero) + Thread.Sleep(sleep); - res = action(); - if (!retryIf(res)) - return res; - } + var watch = Stopwatch.StartNew(); + T res = default; + for (int attempted = 0; watch.Elapsed < retryTimeout; attempted++) + { + if (attempted > 0) + Thread.Sleep(retryInterval); - return res; + res = action(); + if (!retryIf(res)) + return res; } + + return res; } } diff --git a/MailRuCloud/MailRuCloudApi/Common/SharedVideoResolution.cs b/MailRuCloud/MailRuCloudApi/Common/SharedVideoResolution.cs index fc9ee677..db6a4e29 100644 --- a/MailRuCloud/MailRuCloudApi/Common/SharedVideoResolution.cs +++ b/MailRuCloud/MailRuCloudApi/Common/SharedVideoResolution.cs @@ -1,20 +1,19 @@ using System.Runtime.Serialization; -namespace YaR.Clouds.Common +namespace YaR.Clouds.Common; + +public enum SharedVideoResolution { - public enum SharedVideoResolution - { - [EnumMember(Value = "0p")] - All, - [EnumMember(Value = "240p")] - R240, - [EnumMember(Value = "360p")] - R360, - [EnumMember(Value = "480p")] - R480, - [EnumMember(Value = "720p")] - R720, - [EnumMember(Value = "1080p")] - R1080 - } + [EnumMember(Value = "0p")] + All, + [EnumMember(Value = "240p")] + R240, + [EnumMember(Value = "360p")] + R360, + [EnumMember(Value = "480p")] + R480, + [EnumMember(Value = "720p")] + R720, + [EnumMember(Value = "1080p")] + R1080 } diff --git a/MailRuCloud/MailRuCloudApi/CryptFileInfo.cs b/MailRuCloud/MailRuCloudApi/CryptFileInfo.cs index 1f56afc1..300380a7 100644 --- a/MailRuCloud/MailRuCloudApi/CryptFileInfo.cs +++ b/MailRuCloud/MailRuCloudApi/CryptFileInfo.cs @@ -1,16 +1,15 @@ using System; using System.Reflection; -namespace YaR.Clouds +namespace YaR.Clouds; + +public class CryptFileInfo { - public class CryptFileInfo - { - public const string FileName = ".crypt.wdmrc"; + public const string FileName = ".crypt.wdmrc"; - // ReSharper disable once UnusedMember.Global - public string WDMRCVersion => Assembly.GetExecutingAssembly().GetName().Version?.ToString() - ?? throw new Exception($"{nameof(CryptFileInfo)}.{nameof(WDMRCVersion)} is empty"); + // ReSharper disable once UnusedMember.Global + public string WDMRCVersion => Assembly.GetExecutingAssembly().GetName().Version?.ToString() + ?? throw new Exception($"{nameof(CryptFileInfo)}.{nameof(WDMRCVersion)} is empty"); - public DateTime Initialized { get; set; } - } + public DateTime Initialized { get; set; } } diff --git a/MailRuCloud/MailRuCloudApi/Extensions/EnumExtensions.cs b/MailRuCloud/MailRuCloudApi/Extensions/EnumExtensions.cs index f99c05f9..5dc823fe 100644 --- a/MailRuCloud/MailRuCloudApi/Extensions/EnumExtensions.cs +++ b/MailRuCloud/MailRuCloudApi/Extensions/EnumExtensions.cs @@ -3,43 +3,42 @@ using System.Reflection; using System.Runtime.Serialization; -namespace YaR.Clouds.Extensions +namespace YaR.Clouds.Extensions; + +public static class EnumExtensions { - public static class EnumExtensions + public static T ParseEnumMemberValue(string stringValue, bool ignoreCase = true) + where T : Enum { - public static T ParseEnumMemberValue(string stringValue, bool ignoreCase = true) - where T: Enum + T output = default; + string enumStringValue = null; + + var type = typeof(T); + foreach (FieldInfo fi in type.GetFields()) { - T output = default; - string enumStringValue = null; + if (fi.GetCustomAttributes(typeof(EnumMemberAttribute), false) is EnumMemberAttribute[] { Length: > 0 } attrs) + enumStringValue = attrs[0].Value; - var type = typeof(T); - foreach (FieldInfo fi in type.GetFields()) - { - if (fi.GetCustomAttributes(typeof (EnumMemberAttribute), false) is EnumMemberAttribute[] { Length: > 0 } attrs) - enumStringValue = attrs[0].Value; + if (string.Compare(enumStringValue, stringValue, ignoreCase) != 0) + continue; - if (string.Compare(enumStringValue, stringValue, ignoreCase) != 0) - continue; + output = (T)Enum.Parse(type, fi.Name); + break; + } - output = (T)Enum.Parse(type, fi.Name); - break; - } + return output; + } - return output; - } + public static string ToEnumMemberValue(this Enum @enum) + { + var attr = @enum.GetType() + .GetMember(@enum.ToString()).FirstOrDefault()? + .GetCustomAttributes(false) + .OfType(). + FirstOrDefault(); - public static string ToEnumMemberValue(this Enum @enum) - { - var attr = @enum.GetType() - .GetMember(@enum.ToString()).FirstOrDefault()? - .GetCustomAttributes(false) - .OfType(). - FirstOrDefault(); - - return attr == null - ? @enum.ToString() - : attr.Value; - } + return attr == null + ? @enum.ToString() + : attr.Value; } } diff --git a/MailRuCloud/MailRuCloudApi/FileUploadedDelegate.cs b/MailRuCloud/MailRuCloudApi/FileUploadedDelegate.cs index 3b7f4e2c..9c06a388 100644 --- a/MailRuCloud/MailRuCloudApi/FileUploadedDelegate.cs +++ b/MailRuCloud/MailRuCloudApi/FileUploadedDelegate.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using YaR.Clouds.Base; -namespace YaR.Clouds -{ - public delegate void FileUploadedDelegate(IEnumerable file); -} +namespace YaR.Clouds; + +public delegate void FileUploadedDelegate(IEnumerable file); diff --git a/MailRuCloud/MailRuCloudApi/Links/Dto/ItemLink.cs b/MailRuCloud/MailRuCloudApi/Links/Dto/ItemLink.cs index 8e07f7cd..03df7010 100644 --- a/MailRuCloud/MailRuCloudApi/Links/Dto/ItemLink.cs +++ b/MailRuCloud/MailRuCloudApi/Links/Dto/ItemLink.cs @@ -1,14 +1,13 @@ using System; -namespace YaR.Clouds.Links.Dto +namespace YaR.Clouds.Links.Dto; + +public class ItemLink { - public class ItemLink - { - public Uri Href { get; set; } - public string MapTo { get; set; } - public string Name { get; set; } - public bool IsFile { get; set; } - public long Size { get; set; } - public DateTime? CreationDate { get; set; } - } + public Uri Href { get; set; } + public string MapTo { get; set; } + public string Name { get; set; } + public bool IsFile { get; set; } + public long Size { get; set; } + public DateTime? CreationDate { get; set; } } diff --git a/MailRuCloud/MailRuCloudApi/Links/Dto/ItemList.cs b/MailRuCloud/MailRuCloudApi/Links/Dto/ItemList.cs index b579353e..d1623d69 100644 --- a/MailRuCloud/MailRuCloudApi/Links/Dto/ItemList.cs +++ b/MailRuCloud/MailRuCloudApi/Links/Dto/ItemList.cs @@ -1,9 +1,8 @@ using System.Collections.Generic; -namespace YaR.Clouds.Links.Dto +namespace YaR.Clouds.Links.Dto; + +public class ItemList { - public class ItemList - { - public List Items { get; } = new(); - } + public List Items { get; } = []; } diff --git a/MailRuCloud/MailRuCloudApi/Links/Link.cs b/MailRuCloud/MailRuCloudApi/Links/Link.cs index d2b98223..28fc12b6 100644 --- a/MailRuCloud/MailRuCloudApi/Links/Link.cs +++ b/MailRuCloud/MailRuCloudApi/Links/Link.cs @@ -6,88 +6,87 @@ using YaR.Clouds.Links.Dto; using File = YaR.Clouds.Base.File; -namespace YaR.Clouds.Links +namespace YaR.Clouds.Links; + +public class Link : IEntry { - public class Link : IEntry + public Link(Uri href, Cloud.ItemType itemType = Cloud.ItemType.Unknown) { - public Link(Uri href, Cloud.ItemType itemType = Cloud.ItemType.Unknown) - { - Href = href.IsAbsoluteUri ? href : throw new ArgumentException("Absolute URI required"); - IsLinkedToFileSystem = false; - ItemType = itemType; - } + Href = href.IsAbsoluteUri ? href : throw new ArgumentException("Absolute URI required"); + IsLinkedToFileSystem = false; + ItemType = itemType; + } - public Link(ItemLink rootLink, string fullPath, Uri href) : this(href) - { - _rootLink = rootLink; - FullPath = fullPath; - Name = WebDavPath.Name(fullPath); + public Link(ItemLink rootLink, string fullPath, Uri href) : this(href) + { + _rootLink = rootLink; + FullPath = fullPath; + Name = WebDavPath.Name(fullPath); - IsRoot = WebDavPath.PathEquals(WebDavPath.Parent(FullPath), _rootLink.MapTo); + IsRoot = WebDavPath.PathEquals(WebDavPath.Parent(FullPath), _rootLink.MapTo); - ItemType = IsRoot - ? rootLink.IsFile ? Cloud.ItemType.File : Cloud.ItemType.Folder - : Cloud.ItemType.Unknown; + ItemType = IsRoot + ? rootLink.IsFile ? Cloud.ItemType.File : Cloud.ItemType.Folder + : Cloud.ItemType.Unknown; - Size = IsRoot - ? rootLink.Size - : 0; + Size = IsRoot + ? rootLink.Size + : 0; - CreationTimeUtc = rootLink.CreationDate ?? DateTime.Now; - } + CreationTimeUtc = rootLink.CreationDate ?? DateTime.Now; + } - public bool IsLinkedToFileSystem { get; } + public bool IsLinkedToFileSystem { get; } - private readonly ItemLink _rootLink; + private readonly ItemLink _rootLink; - public string OriginalName { get; set; } + public string OriginalName { get; set; } - public string Name { get; } + public string Name { get; } - public Cloud.ItemType ItemType { get; set; } + public Cloud.ItemType ItemType { get; set; } - public bool IsFile => ItemType == Cloud.ItemType.File; + public bool IsFile => ItemType == Cloud.ItemType.File; - public bool IsBad { get; set; } + public bool IsBad { get; set; } - public bool IsResolved { get; set; } + public bool IsResolved { get; set; } - /// - /// Filesystem full path from root - /// - public string FullPath { get; } + /// + /// Filesystem full path from root + /// + public string FullPath { get; } - public string MapPath => _rootLink.MapTo; + public string MapPath => _rootLink.MapTo; - public bool IsRoot { get; } + public bool IsRoot { get; } - public IEntry ToBadEntry() - { - var res = ItemType == Cloud.ItemType.File - ? (IEntry)new File(FullPath, Size) - : new Folder(Size, FullPath); + public IEntry ToBadEntry() + { + var res = ItemType == Cloud.ItemType.File + ? (IEntry)new File(FullPath, Size) + : new Folder(Size, FullPath); - return res; - } + return res; + } - public Uri Href { get; } - //public List PublicLinks => new() {new PublicLinkInfo("linked", Href) }; - public ConcurrentDictionary PublicLinks + public Uri Href { get; } + //public List PublicLinks => new() {new PublicLinkInfo("linked", Href) }; + public ConcurrentDictionary PublicLinks + { + get { - get - { - ConcurrentDictionary result = new(StringComparer.InvariantCultureIgnoreCase); - result.TryAdd(Href.AbsoluteUri, new PublicLinkInfo("linked", Href)); - return result; - } + ConcurrentDictionary result = new(StringComparer.InvariantCultureIgnoreCase); + result.TryAdd(Href.AbsoluteUri, new PublicLinkInfo("linked", Href)); + return result; } + } - public ImmutableList Descendants => ImmutableList.Empty; + public ImmutableList Descendants => ImmutableList.Empty; - public FileAttributes Attributes => FileAttributes.Normal; //TODO: dunno what to do + public FileAttributes Attributes => FileAttributes.Normal; //TODO: dunno what to do - public FileSize Size { get; set; } - public DateTime CreationTimeUtc { get; set; } - } + public FileSize Size { get; set; } + public DateTime CreationTimeUtc { get; set; } } diff --git a/MailRuCloud/MailRuCloudApi/Links/LinkManager.cs b/MailRuCloud/MailRuCloudApi/Links/LinkManager.cs index e35da1d9..543d5f54 100644 --- a/MailRuCloud/MailRuCloudApi/Links/LinkManager.cs +++ b/MailRuCloud/MailRuCloudApi/Links/LinkManager.cs @@ -10,494 +10,493 @@ using YaR.Clouds.Common; using YaR.Clouds.Links.Dto; -namespace YaR.Clouds.Links +namespace YaR.Clouds.Links; + +/// +/// Управление ссылками, привязанными к облаку +/// +public class LinkManager { - /// - /// Управление ссылками, привязанными к облаку - /// - public class LinkManager - { - private readonly TimeSpan SaveAndLoadTimeout = TimeSpan.FromMinutes(5); + private readonly TimeSpan SaveAndLoadTimeout = TimeSpan.FromMinutes(5); - private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(LinkManager)); + private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(LinkManager)); - private const string LinkContainerName = "item.links.wdmrc"; - private const string HistoryContainerName = "item.links.history.wdmrc"; - private readonly Cloud _cloud; - private ItemList _itemList = new(); - private readonly EntryCache _linkCache; + private const string LinkContainerName = "item.links.wdmrc"; + private const string HistoryContainerName = "item.links.history.wdmrc"; + private readonly Cloud _cloud; + private ItemList _itemList = new(); + private readonly EntryCache _linkCache; - private readonly SemaphoreSlim _locker; + private readonly SemaphoreSlim _locker; - public LinkManager(Cloud cloud) - { - _locker = new SemaphoreSlim(1); - _cloud = cloud; - _linkCache = new EntryCache(TimeSpan.FromSeconds(60), null); - //{ - // Полагаемся на стандартно заданное время очистки - // CleanUpPeriod = TimeSpan.FromMinutes(5) - //}; + public LinkManager(Cloud cloud) + { + _locker = new SemaphoreSlim(1); + _cloud = cloud; + _linkCache = new EntryCache(TimeSpan.FromSeconds(60), null); + //{ + // Полагаемся на стандартно заданное время очистки + // CleanUpPeriod = TimeSpan.FromMinutes(5) + //}; + + cloud.FileUploaded += OnFileUploaded; + + Load(); + } - cloud.FileUploaded += OnFileUploaded; + private void OnFileUploaded(IEnumerable files) + { + var file = files?.FirstOrDefault(); + if (file is null) return; + if (file.Path == WebDavPath.Root && + file.Name == LinkContainerName && + file.Size > 3) + { Load(); } + } - private void OnFileUploaded(IEnumerable files) - { - var file = files?.FirstOrDefault(); - if (file is null) return; + /// + /// Сохранить в файл в облаке список ссылок + /// + public async void Save() + { + Logger.Info($"Saving links to {LinkContainerName}"); - if (file.Path == WebDavPath.Root && - file.Name == LinkContainerName && - file.Size > 3) + if (await _locker.WaitAsync(SaveAndLoadTimeout)) + { + try { - Load(); + string content = JsonConvert.SerializeObject(_itemList, Formatting.Indented); + string path = WebDavPath.Combine(WebDavPath.Root, LinkContainerName); + _cloud.FileUploaded -= OnFileUploaded; + _cloud.UploadFile(path, content); } + finally + { + _cloud.FileUploaded += OnFileUploaded; + _locker.Release(); + } + } + else + { + Logger.Info($"Timeout while saving links to {LinkContainerName}"); } + } - /// - /// Сохранить в файл в облаке список ссылок - /// - public async void Save() + /// + /// Загрузить из файла в облаке список ссылок + /// + public async void Load() + { + if (!_cloud.Credentials.IsAnonymous) { - Logger.Info($"Saving links to {LinkContainerName}"); + Logger.Info($"Loading links from {LinkContainerName}"); if (await _locker.WaitAsync(SaveAndLoadTimeout)) { try { - string content = JsonConvert.SerializeObject(_itemList, Formatting.Indented); - string path = WebDavPath.Combine(WebDavPath.Root, LinkContainerName); - _cloud.FileUploaded -= OnFileUploaded; - _cloud.UploadFile(path, content); + string filepath = WebDavPath.Combine(WebDavPath.Root, LinkContainerName); + //var file = (File)_cloud.GetItem(filepath, Cloud.ItemType.File, false); + var entry = _cloud.GetItemAsync(filepath, Cloud.ItemType.File, false).Result; + if (entry is not null && entry is File file) + { + // If the file is not empty. + // An empty file in UTF-8 with BOM is exactly 3 bytes long. + if (file is not null && file.Size > 3) + { + _itemList = _cloud.DownloadFileAsJson(file); + } + + _itemList ??= new ItemList(); + + foreach (var f in _itemList.Items) + { + f.MapTo = WebDavPath.Clean(f.MapTo); + if (!f.Href.IsAbsoluteUri) + f.Href = new Uri(_cloud.RequestRepo.PublicBaseUrlDefault + f.Href); + } + } + } + catch (Exception e) + { + Logger.Warn("Cannot load links", e); } finally { - _cloud.FileUploaded += OnFileUploaded; _locker.Release(); } } else { - Logger.Info($"Timeout while saving links to {LinkContainerName}"); + Logger.Info($"Timeout while loading links from {LinkContainerName}"); } } - /// - /// Загрузить из файла в облаке список ссылок - /// - public async void Load() - { - if (!_cloud.Credentials.IsAnonymous) - { - Logger.Info($"Loading links from {LinkContainerName}"); + _itemList ??= new ItemList(); + } - if (await _locker.WaitAsync(SaveAndLoadTimeout)) - { - try - { - string filepath = WebDavPath.Combine(WebDavPath.Root, LinkContainerName); - //var file = (File)_cloud.GetItem(filepath, Cloud.ItemType.File, false); - var entry = _cloud.GetItemAsync(filepath, Cloud.ItemType.File, false).Result; - if (entry is not null && entry is File file) - { - // If the file is not empty. - // An empty file in UTF-8 with BOM is exactly 3 bytes long. - if (file is not null && file.Size > 3) - { - _itemList = _cloud.DownloadFileAsJson(file); - } - - _itemList ??= new ItemList(); - - foreach (var f in _itemList.Items) - { - f.MapTo = WebDavPath.Clean(f.MapTo); - if (!f.Href.IsAbsoluteUri) - f.Href = new Uri(_cloud.RequestRepo.PublicBaseUrlDefault + f.Href); - } - } - } - catch (Exception e) - { - Logger.Warn("Cannot load links", e); - } - finally - { - _locker.Release(); - } - } - else - { - Logger.Info($"Timeout while loading links from {LinkContainerName}"); - } - } + /// + /// Получить список ссылок, привязанных к указанному пути в облаке + /// + /// Путь к каталогу в облаке + /// + public List GetItems(string path) + { + var z = _itemList.Items + .Where(f => f.MapTo == path) + .ToList(); - _itemList ??= new ItemList(); - } + return z; + } - /// - /// Получить список ссылок, привязанных к указанному пути в облаке - /// - /// Путь к каталогу в облаке - /// - public List GetItems(string path) - { - var z = _itemList.Items - .Where(f => f.MapTo == path) - .ToList(); + /// + /// Убрать ссылку + /// + /// + /// Save container after removing + public bool RemoveLink(string path, bool doSave = true) + { + var name = WebDavPath.Name(path); + var parent = WebDavPath.Parent(path); - return z; - } + var z = _itemList.Items.FirstOrDefault(f => f.MapTo == parent && f.Name == name); - /// - /// Убрать ссылку - /// - /// - /// Save container after removing - public bool RemoveLink(string path, bool doSave = true) - { - var name = WebDavPath.Name(path); - var parent = WebDavPath.Parent(path); + if (z is null) + return false; - var z = _itemList.Items.FirstOrDefault(f => f.MapTo == parent && f.Name == name); + _itemList.Items.Remove(z); + _linkCache.OnRemoveTree(DateTime.MaxValue, path, null); - if (z is null) - return false; + if (doSave) + Save(); - _itemList.Items.Remove(z); - _linkCache.OnRemoveTree(DateTime.MaxValue, path, null); + return true; + } - if (doSave) - Save(); + public void RemoveLinks(IEnumerable innerLinks, bool doSave = true) + { + bool removed = false; + var lst = innerLinks.ToList(); - return true; + foreach (var link in lst) + { + var res = RemoveLink(link.FullPath, false); + if (res) removed = true; } - public void RemoveLinks(IEnumerable innerLinks, bool doSave = true) - { - bool removed = false; - var lst = innerLinks.ToList(); + if (doSave && removed) + Save(); + } + + + /// + /// Убрать все привязки на мёртвые ссылки + /// + /// + public async Task RemoveDeadLinks(bool doWriteHistory) + { + var removes = _itemList.Items + .AsParallel() + .WithDegreeOfParallelism(5) + .Select(it => GetItemLink(WebDavPath.Combine(it.MapTo, it.Name)).Result) + .Where(itl => + itl.IsBad || + _cloud.GetItemAsync(itl.MapPath, Cloud.ItemType.Folder, false).Result is null) + .ToList(); - foreach (var link in lst) + if (removes.Count == 0) + return 0; + + _itemList.Items.RemoveAll(it => removes.Any(rem => WebDavPath.PathEquals(rem.MapPath, it.MapTo) && rem.Name == it.Name)); + + if (removes.Count == 0) + return 0; + + if (doWriteHistory) + { + foreach (var link in removes) { - var res = RemoveLink(link.FullPath, false); - if (res) removed = true; + _linkCache.RemoveTree(link.FullPath); } - if (doSave && removed) - Save(); + string path = WebDavPath.Combine(WebDavPath.Root, HistoryContainerName); + string res = await _cloud.DownloadFileAsString(path); + var history = new StringBuilder(res ?? string.Empty); + foreach (var link in removes) + { + history.Append($"{DateTime.Now} REMOVE: {link.Href} {link.Name}\r\n"); + } + _cloud.UploadFile(path, history.ToString()); } + Save(); + return removes.Count; + } + ///// + ///// Проверка доступности ссылки + ///// + ///// + ///// + //private bool IsLinkAlive(ItemLink link) + //{ + // string path = WebDavPath.Combine(link.MapTo, link.Name); + // try + // { + // var entry = _cloud.GetItem(path).Result; + // return entry is not null; + // } + // catch (AggregateException e) + // when ( // let's check if there really no file or just other network error + // e.InnerException is WebException we && + // (we.Response as HttpWebResponse)?.StatusCode == HttpStatusCode.NotFound + // ) + // { + // return false; + // } + //} - /// - /// Убрать все привязки на мёртвые ссылки - /// - /// - public async Task RemoveDeadLinks(bool doWriteHistory) + /// + /// + /// + /// + /// Resolving file/folder type requires addition request to cloud + /// + public async Task GetItemLink(string path, bool doResolveType = true) + { + (var cached, var getState) = _linkCache.Get(path); + if (cached is not null) + return (Link)cached; + + //TODO: subject to refact + string parent = path; + ItemLink wp; + string right = string.Empty; + do { - var removes = _itemList.Items - .AsParallel() - .WithDegreeOfParallelism(5) - .Select(it => GetItemLink(WebDavPath.Combine(it.MapTo, it.Name)).Result) - .Where(itl => - itl.IsBad || - _cloud.GetItemAsync(itl.MapPath, Cloud.ItemType.Folder, false).Result is null) - .ToList(); + string name = WebDavPath.Name(parent); + parent = WebDavPath.Parent(parent); + wp = _itemList.Items.FirstOrDefault(ip => parent == ip.MapTo && name == ip.Name); + if (wp is null) + right = WebDavPath.Combine(name, right); - if (removes.Count == 0) - return 0; + } while (parent != WebDavPath.Root && wp is null); - _itemList.Items.RemoveAll(it => removes.Any(rem => WebDavPath.PathEquals(rem.MapPath, it.MapTo) && rem.Name == it.Name)); + if (wp is null) + return null; - if (removes.Count == 0) - return 0; + string addhref = string.IsNullOrEmpty(right) + ? string.Empty + : '/' + Uri.EscapeDataString(right.TrimStart('/')); + var link = new Link(wp, path, new Uri(wp.Href.OriginalString + addhref, UriKind.Absolute)); - if (doWriteHistory) - { - foreach (var link in removes) - { - _linkCache.RemoveTree(link.FullPath); - } + //resolve additional link properties, e.g. OriginalName, ItemType, Size + if (doResolveType) + await ResolveLink(link); - string path = WebDavPath.Combine(WebDavPath.Root, HistoryContainerName); - string res = await _cloud.DownloadFileAsString(path); - var history = new StringBuilder(res ?? string.Empty); - foreach (var link in removes) - { - history.Append($"{DateTime.Now} REMOVE: {link.Href} {link.Name}\r\n"); - } - _cloud.UploadFile(path, history.ToString()); - } - Save(); - return removes.Count; - } + _linkCache.Add(link); + return link; + } - ///// - ///// Проверка доступности ссылки - ///// - ///// - ///// - //private bool IsLinkAlive(ItemLink link) - //{ - // string path = WebDavPath.Combine(link.MapTo, link.Name); - // try - // { - // var entry = _cloud.GetItem(path).Result; - // return entry is not null; - // } - // catch (AggregateException e) - // when ( // let's check if there really no file or just other network error - // e.InnerException is WebException we && - // (we.Response as HttpWebResponse)?.StatusCode == HttpStatusCode.NotFound - // ) - // { - // return false; - // } - //} - - /// - /// - /// - /// - /// Resolving file/folder type requires addition request to cloud - /// - public async Task GetItemLink(string path, bool doResolveType = true) + private async Task ResolveLink(Link link) + { + try { - (var cached, var getState) = _linkCache.Get(path); - if (cached is not null) - return (Link)cached; - - //TODO: subject to refact - string parent = path; - ItemLink wp; - string right = string.Empty; - do - { - string name = WebDavPath.Name(parent); - parent = WebDavPath.Parent(parent); - wp = _itemList.Items.FirstOrDefault(ip => parent == ip.MapTo && name == ip.Name); - if (wp is null) - right = WebDavPath.Combine(name, right); + //var relahref = link.Href.IsAbsoluteUri + // ? link.Href.OriginalString.Remove(0, _cloud.Repo.PublicBaseUrlDefault.Length + 1) + // : link.Href.OriginalString; + + //var infores = await new ItemInfoRequest(_cloud.CloudApi, link.Href, true).MakeRequestAsync(_connectionLimiter); + var infores = await _cloud.RequestRepo.ItemInfo(RemotePath.Get(link)); + link.ItemType = infores.Body.Kind == "file" + ? Cloud.ItemType.File + : Cloud.ItemType.Folder; + link.OriginalName = infores.Body.Name; + link.Size = infores.Body.Size; + + link.IsResolved = true; + } + catch (Exception) //TODO check 404 etc. + { + //this means a bad link + // don't know what to do + link.IsBad = true; + } + } - } while (parent != WebDavPath.Root && wp is null); + //public IEnumerable GetChildren(string folderFullPath, bool doResolveType) + //{ + // var lst = _itemList.Items + // .Where(it => + // WebDavPath.IsParentOrSame(folderFullPath, it.MapTo)); - if (wp is null) - return null; + // return lst; + //} - string addhref = string.IsNullOrEmpty(right) - ? string.Empty - : '/' + Uri.EscapeDataString(right.TrimStart('/')); - var link = new Link(wp, path, new Uri(wp.Href.OriginalString + addhref, UriKind.Absolute)); + public IEnumerable GetChildren(string folderFullPath) + { + var lst = _itemList.Items + .Where(it => WebDavPath.IsParentOrSame(folderFullPath, it.MapTo)) + .Select(it => GetItemLink(WebDavPath.Combine(it.MapTo, it.Name), false).Result); - //resolve additional link properties, e.g. OriginalName, ItemType, Size - if (doResolveType) - await ResolveLink(link); + return lst; + } - _linkCache.Add(link); - return link; - } - private async Task ResolveLink(Link link) - { - try - { - //var relahref = link.Href.IsAbsoluteUri - // ? link.Href.OriginalString.Remove(0, _cloud.Repo.PublicBaseUrlDefault.Length + 1) - // : link.Href.OriginalString; - - //var infores = await new ItemInfoRequest(_cloud.CloudApi, link.Href, true).MakeRequestAsync(_connectionLimiter); - var infores = await _cloud.RequestRepo.ItemInfo(RemotePath.Get(link)); - link.ItemType = infores.Body.Kind == "file" - ? Cloud.ItemType.File - : Cloud.ItemType.Folder; - link.OriginalName = infores.Body.Name; - link.Size = infores.Body.Size; - - link.IsResolved = true; - } - catch (Exception) //TODO check 404 etc. - { - //this means a bad link - // don't know what to do - link.IsBad = true; - } - } + /// + /// Привязать ссылку к облаку + /// + /// Ссылка + /// Путь в облаке, в который поместить ссылку + /// Имя для ссылки + /// Признак, что ссылка ведёт на файл, иначе - на папку + /// Размер данных по ссылке + /// Дата создания + public async Task Add(Uri url, string path, string name, bool isFile, long size, DateTime? creationDate) + { + path = WebDavPath.Clean(path); - //public IEnumerable GetChildren(string folderFullPath, bool doResolveType) - //{ - // var lst = _itemList.Items - // .Where(it => - // WebDavPath.IsParentOrSame(folderFullPath, it.MapTo)); + var entry = await _cloud.GetItemAsync(path); + if (entry is not null && entry.Descendants.Any(entry => entry.Name == name)) + return false; - // return lst; - //} + path = WebDavPath.Clean(path); + if (_itemList.Items.Any(it => WebDavPath.PathEquals(it.MapTo, path) && it.Name == name)) + return false; - public IEnumerable GetChildren(string folderFullPath) + _itemList.Items.Add(new ItemLink { - var lst = _itemList.Items - .Where(it => WebDavPath.IsParentOrSame(folderFullPath, it.MapTo)) - .Select(it => GetItemLink(WebDavPath.Combine(it.MapTo, it.Name), false).Result); + Href = url, + MapTo = path, + Name = name, + IsFile = isFile, + Size = size, + CreationDate = creationDate + }); + + _linkCache.RemoveOne(path); + return true; + } - return lst; - } - /// - /// Привязать ссылку к облаку - /// - /// Ссылка - /// Путь в облаке, в который поместить ссылку - /// Имя для ссылки - /// Признак, что ссылка ведёт на файл, иначе - на папку - /// Размер данных по ссылке - /// Дата создания - public async Task Add(Uri url, string path, string name, bool isFile, long size, DateTime? creationDate) - { - path = WebDavPath.Clean(path); - var entry = await _cloud.GetItemAsync(path); - if (entry is not null && entry.Descendants.Any(entry => entry.Name == name)) - return false; - path = WebDavPath.Clean(path); - if (_itemList.Items.Any(it => WebDavPath.PathEquals(it.MapTo, path) && it.Name == name)) - return false; + //private const string PublicBaseLink = "https://cloud.mail.ru/public"; + //private const string PublicBaseLink1 = "https:/cloud.mail.ru/public"; - _itemList.Items.Add(new ItemLink - { - Href = url, - MapTo = path, - Name = name, - IsFile = isFile, - Size = size, - CreationDate = creationDate - }); - - _linkCache.RemoveOne(path); - return true; - } + //private string GetRelaLink(Uri url) + //{ + // foreach (string pbu in _cloud.RequestRepo.PublicBaseUrls) + // { + // if (!string.IsNullOrEmpty(pbu)) + // if (url.StartsWith(pbu)) + // return url.Remove(pbu.Length); + // } + // return url; + //} + public void ProcessRename(string fullPath, string newName) + { + string newPath = WebDavPath.Combine(WebDavPath.Parent(fullPath), newName); + bool changed = false; + foreach (var link in _itemList.Items) + { + if (!WebDavPath.IsParentOrSame(fullPath, link.MapTo)) + continue; + link.MapTo = WebDavPath.ModifyParent(link.MapTo, fullPath, newPath); + changed = true; + } + if (!changed) + return; - //private const string PublicBaseLink = "https://cloud.mail.ru/public"; - //private const string PublicBaseLink1 = "https:/cloud.mail.ru/public"; + _linkCache.RemoveTree(fullPath); + _linkCache.RemoveOne(newPath); + Save(); + } - //private string GetRelaLink(Uri url) - //{ - // foreach (string pbu in _cloud.RequestRepo.PublicBaseUrls) - // { - // if (!string.IsNullOrEmpty(pbu)) - // if (url.StartsWith(pbu)) - // return url.Remove(pbu.Length); - // } - // return url; - //} - - public void ProcessRename(string fullPath, string newName) - { - string newPath = WebDavPath.Combine(WebDavPath.Parent(fullPath), newName); + public bool RenameLink(Link link, string newName) + { + // can't rename items within linked folder + if (!link.IsRoot) return false; - bool changed = false; - foreach (var link in _itemList.Items) - { - if (!WebDavPath.IsParentOrSame(fullPath, link.MapTo)) - continue; + var ilink = _itemList.Items.FirstOrDefault(it => WebDavPath.PathEquals(it.MapTo, link.MapPath) && it.Name == link.Name); + if (ilink is null) return false; + if (ilink.Name == newName) return true; - link.MapTo = WebDavPath.ModifyParent(link.MapTo, fullPath, newPath); - changed = true; - } + ilink.Name = newName; + Save(); + _linkCache.RemoveOne(link.FullPath); + return true; + } - if (!changed) - return; - _linkCache.RemoveTree(fullPath); - _linkCache.RemoveOne(newPath); - Save(); - } + /// + /// Перемещение ссылки из одного каталога в другой + /// + /// + /// + /// Сохранить изменения в файл в облаке + /// + /// + /// Корневую ссылку просто перенесем + /// + /// Если это вложенная ссылка, то перенести ее нельзя, а можно + /// 1. сделать новую ссылку на эту вложенность + /// 2. скопировать содержимое + /// если следовать логике, что при копировании мы копируем содержимое ссылок, а при перемещении - перемещаем ссылки, то надо делать новую ссылку + /// + /// Логика хороша, но + /// некоторые клиенты сначала делают структуру каталогов, а потом по одному переносят файлы, например, TotalCommander c плагином WebDAV v.2.9 + /// в таких условиях на каждый файл получится свой собственный линк, если делать правильно, т.е. в итоге расплодится миллион линков + /// поэтому делаем неправильно - копируем содержимое линков + /// + public async Task RemapLink(Link link, string destinationPath, bool doSave = true) + { + if (WebDavPath.PathEquals(link.MapPath, destinationPath)) + return true; - public bool RenameLink(Link link, string newName) + if (link.IsRoot) { - // can't rename items within linked folder - if (!link.IsRoot) return false; - - var ilink = _itemList.Items.FirstOrDefault(it => WebDavPath.PathEquals(it.MapTo, link.MapPath) && it.Name == link.Name); - if (ilink is null) return false; - if (ilink.Name == newName) return true; + var rootlink = _itemList.Items.FirstOrDefault(it => WebDavPath.PathEquals(it.MapTo, link.MapPath) && it.Name == link.Name); + if (rootlink is null) + return false; - ilink.Name = newName; + string oldmap = rootlink.MapTo; + rootlink.MapTo = destinationPath; Save(); + _linkCache.RemoveTree(oldmap); + _linkCache.RemoveOne(destinationPath); _linkCache.RemoveOne(link.FullPath); return true; } + // it's a link on inner item of root link, creating new link + if (!link.IsResolved) + await ResolveLink(link); - /// - /// Перемещение ссылки из одного каталога в другой - /// - /// - /// - /// Сохранить изменения в файл в облаке - /// - /// - /// Корневую ссылку просто перенесем - /// - /// Если это вложенная ссылка, то перенести ее нельзя, а можно - /// 1. сделать новую ссылку на эту вложенность - /// 2. скопировать содержимое - /// если следовать логике, что при копировании мы копируем содержимое ссылок, а при перемещении - перемещаем ссылки, то надо делать новую ссылку - /// - /// Логика хороша, но - /// некоторые клиенты сначала делают структуру каталогов, а потом по одному переносят файлы, например, TotalCommander c плагином WebDAV v.2.9 - /// в таких условиях на каждый файл получится свой собственный линк, если делать правильно, т.е. в итоге расплодится миллион линков - /// поэтому делаем неправильно - копируем содержимое линков - /// - public async Task RemapLink(Link link, string destinationPath, bool doSave = true) - { - if (WebDavPath.PathEquals(link.MapPath, destinationPath)) - return true; + var res = await Add( + link.Href, + destinationPath, + link.Name, + link.ItemType == Cloud.ItemType.File, + link.Size, + DateTime.Now); - if (link.IsRoot) - { - var rootlink = _itemList.Items.FirstOrDefault(it => WebDavPath.PathEquals(it.MapTo, link.MapPath) && it.Name == link.Name); - if (rootlink is null) - return false; - - string oldmap = rootlink.MapTo; - rootlink.MapTo = destinationPath; - Save(); - _linkCache.RemoveTree(oldmap); - _linkCache.RemoveOne(destinationPath); - _linkCache.RemoveOne(link.FullPath); - return true; - } - - // it's a link on inner item of root link, creating new link - if (!link.IsResolved) - await ResolveLink(link); - - var res = await Add( - link.Href, - destinationPath, - link.Name, - link.ItemType == Cloud.ItemType.File, - link.Size, - DateTime.Now); - - if (!res) - return false; + if (!res) + return false; - if (doSave) - Save(); + if (doSave) + Save(); - _linkCache.RemoveOne(destinationPath); + _linkCache.RemoveOne(destinationPath); - return true; - } + return true; } } diff --git a/MailRuCloud/MailRuCloudApi/PublishInfo.cs b/MailRuCloud/MailRuCloudApi/PublishInfo.cs index 35776d8b..f41003f7 100644 --- a/MailRuCloud/MailRuCloudApi/PublishInfo.cs +++ b/MailRuCloud/MailRuCloudApi/PublishInfo.cs @@ -1,21 +1,20 @@ using System; using System.Collections.Generic; -namespace YaR.Clouds +namespace YaR.Clouds; + +public class PublishInfo { - public class PublishInfo - { - public const string SharedFilePostfix = ".share.wdmrc"; - public const string PlayListFilePostfix = ".m3u8"; + public const string SharedFilePostfix = ".share.wdmrc"; + public const string PlayListFilePostfix = ".m3u8"; - public List Items { get; } = []; - public DateTime DateTime { get; set; } = DateTime.Now; - } + public List Items { get; } = []; + public DateTime DateTime { get; set; } = DateTime.Now; +} - public class PublishInfoItem - { - public string Path { get; set; } - public List Urls { get; set; } - public string PlayListUrl { get; set; } - } +public class PublishInfoItem +{ + public string Path { get; set; } + public List Urls { get; set; } + public string PlayListUrl { get; set; } } diff --git a/MailRuCloud/MailRuCloudApi/SmtpAsyncAppender.cs b/MailRuCloud/MailRuCloudApi/SmtpAsyncAppender.cs index 99864c9a..ba4e57cd 100644 --- a/MailRuCloud/MailRuCloudApi/SmtpAsyncAppender.cs +++ b/MailRuCloud/MailRuCloudApi/SmtpAsyncAppender.cs @@ -2,25 +2,24 @@ using System.Threading.Tasks; using log4net.Appender; -namespace YaR.Clouds +namespace YaR.Clouds; + +// ReSharper disable once UnusedType.Global +public class SmtpAsyncAppender : SmtpAppender { - // ReSharper disable once UnusedType.Global - public class SmtpAsyncAppender : SmtpAppender + protected override void SendEmail(string messageBody) { - protected override void SendEmail(string messageBody) + Task.Run(() => { - Task.Run(() => + try + { + base.SendEmail(messageBody); + } + catch (Exception e) { - try - { - base.SendEmail(messageBody); - } - catch (Exception e) - { - Console.WriteLine(e); - throw; - } - }); - } + Console.WriteLine(e); + throw; + } + }); } } diff --git a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/CleanTrashCommand.cs b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/CleanTrashCommand.cs index fb9e6f85..ec3fadca 100644 --- a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/CleanTrashCommand.cs +++ b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/CleanTrashCommand.cs @@ -1,24 +1,24 @@ using System.Collections.Generic; using System.Threading.Tasks; -namespace YaR.Clouds.SpecialCommands.Commands +namespace YaR.Clouds.SpecialCommands.Commands; + +/// +/// +/// +public class CleanTrashCommand : SpecialCommand { - /// - /// - /// - public class CleanTrashCommand : SpecialCommand + public CleanTrashCommand(Cloud cloud, string path, IList parameters) + : base(cloud, path, parameters) { - public CleanTrashCommand(Cloud cloud, string path, IList parameters) : base(cloud, path, parameters) - { - } + } - protected override MinMax MinMaxParamsCount { get; } = new(0); + protected override MinMax MinMaxParamsCount { get; } = new(0); - public override async Task Execute() - { - _cloud.CleanTrash(); + public override async Task Execute() + { + _cloud.CleanTrash(); - return await Task.FromResult(SpecialCommandResult.Success); - } + return await Task.FromResult(SpecialCommandResult.Success); } } diff --git a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/CopyCommand.cs b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/CopyCommand.cs index a479e6ff..f3bd926f 100644 --- a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/CopyCommand.cs +++ b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/CopyCommand.cs @@ -2,24 +2,23 @@ using System.Threading.Tasks; using YaR.Clouds.Base; -namespace YaR.Clouds.SpecialCommands.Commands +namespace YaR.Clouds.SpecialCommands.Commands; + +public class CopyCommand : SpecialCommand { - public class CopyCommand : SpecialCommand + public CopyCommand(Cloud cloud, string path, IList parameters) : base(cloud, path, parameters) { - public CopyCommand(Cloud cloud, string path, IList parameters) : base(cloud, path, parameters) - { - } + } - protected override MinMax MinMaxParamsCount { get; } = new(1, 2); + protected override MinMax MinMaxParamsCount { get; } = new(1, 2); - public override async Task Execute() - { - string source = WebDavPath.Clean(_parameters.Count == 1 ? _path : _parameters[0]); - string target = WebDavPath.Clean(_parameters.Count == 1 ? _parameters[0] : _parameters[1]); + public override async Task Execute() + { + string source = WebDavPath.Clean(_parameters.Count == 1 ? _path : _parameters[0]); + string target = WebDavPath.Clean(_parameters.Count == 1 ? _parameters[0] : _parameters[1]); - var res = await _cloud.Copy(source, target); - return new SpecialCommandResult(res); + var res = await _cloud.Copy(source, target); + return new SpecialCommandResult(res); - } } } diff --git a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/CryptInitCommand.cs b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/CryptInitCommand.cs index 7b1bdec5..0604b701 100644 --- a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/CryptInitCommand.cs +++ b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/CryptInitCommand.cs @@ -2,38 +2,37 @@ using System.Threading.Tasks; using YaR.Clouds.Base; -namespace YaR.Clouds.SpecialCommands.Commands +namespace YaR.Clouds.SpecialCommands.Commands; + +/// +/// , +/// +public class CryptInitCommand : SpecialCommand { - /// - /// , - /// - public class CryptInitCommand : SpecialCommand + public CryptInitCommand(Cloud cloud, string path, IList parameters) + : base(cloud, path, parameters) { - public CryptInitCommand(Cloud cloud, string path, IList parameters) - : base(cloud, path, parameters) - { - } + } - protected override MinMax MinMaxParamsCount { get; } = new(0, 1); + protected override MinMax MinMaxParamsCount { get; } = new(0, 1); - public override async Task Execute() - { - string path; - string param = _parameters.Count == 0 ? string.Empty : _parameters[0].Replace("\\", WebDavPath.Separator); + public override async Task Execute() + { + string path; + string param = _parameters.Count == 0 ? string.Empty : _parameters[0].Replace("\\", WebDavPath.Separator); - if (_parameters.Count == 0) - path = _path; - else if (param.StartsWith(WebDavPath.Separator)) - path = param; - else - path = WebDavPath.Combine(_path, param); + if (_parameters.Count == 0) + path = _path; + else if (param.StartsWith(WebDavPath.Separator)) + path = param; + else + path = WebDavPath.Combine(_path, param); - var entry = await _cloud.GetItemAsync(path); - if (entry is null || entry.IsFile) - return SpecialCommandResult.Fail; + var entry = await _cloud.GetItemAsync(path); + if (entry is null || entry.IsFile) + return SpecialCommandResult.Fail; - var res = await _cloud.CryptInit((Folder)entry); - return new SpecialCommandResult(res); - } + var res = await _cloud.CryptInit((Folder)entry); + return new SpecialCommandResult(res); } } diff --git a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/CryptPasswdCommand.cs b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/CryptPasswdCommand.cs index 0d1c13c6..1fe596c8 100644 --- a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/CryptPasswdCommand.cs +++ b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/CryptPasswdCommand.cs @@ -1,28 +1,28 @@ using System.Collections.Generic; using System.Threading.Tasks; -namespace YaR.Clouds.SpecialCommands.Commands +namespace YaR.Clouds.SpecialCommands.Commands; + +/// +/// () +/// +public class CryptPasswdCommand : SpecialCommand { - /// - /// () - /// - public class CryptPasswdCommand : SpecialCommand + public CryptPasswdCommand(Cloud cloud, string path, IList parameters) + : base(cloud, path, parameters) { - public CryptPasswdCommand(Cloud cloud, string path, IList parameters) : base(cloud, path, parameters) - { - } + } - protected override MinMax MinMaxParamsCount { get; } = new(1); + protected override MinMax MinMaxParamsCount { get; } = new(1); - public override async Task Execute() - { - var newPasswd = _parameters[0]; - if (string.IsNullOrEmpty(newPasswd)) - return await Task.FromResult(new SpecialCommandResult(false, "Crypt password is empty")); + public override async Task Execute() + { + var newPasswd = _parameters[0]; + if (string.IsNullOrEmpty(newPasswd)) + return await Task.FromResult(new SpecialCommandResult(false, "Crypt password is empty")); - _cloud.Credentials.PasswordCrypt = newPasswd; + _cloud.Credentials.PasswordCrypt = newPasswd; - return await Task.FromResult(SpecialCommandResult.Success); - } + return await Task.FromResult(SpecialCommandResult.Success); } } diff --git a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/DeleteCommand.cs b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/DeleteCommand.cs index 8a0344f5..4c8adda2 100644 --- a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/DeleteCommand.cs +++ b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/DeleteCommand.cs @@ -2,35 +2,34 @@ using System.Threading.Tasks; using YaR.Clouds.Base; -namespace YaR.Clouds.SpecialCommands.Commands +namespace YaR.Clouds.SpecialCommands.Commands; + +public class DeleteCommand : SpecialCommand { - public class DeleteCommand : SpecialCommand + public DeleteCommand(Cloud cloud, string path, IList parameters) + : base(cloud, path, parameters) { - public DeleteCommand(Cloud cloud, string path, IList parameters) - : base(cloud, path, parameters) - { - } + } - protected override MinMax MinMaxParamsCount { get; } = new(0, 1); + protected override MinMax MinMaxParamsCount { get; } = new(0, 1); - public override async Task Execute() - { - string path; - string param = _parameters.Count == 0 ? string.Empty : _parameters[0].Replace("\\", WebDavPath.Separator); + public override async Task Execute() + { + string path; + string param = _parameters.Count == 0 ? string.Empty : _parameters[0].Replace("\\", WebDavPath.Separator); - if (_parameters.Count == 0) - path = _path; - else if (param.StartsWith(WebDavPath.Separator)) - path = param; - else - path = WebDavPath.Combine(_path, param); + if (_parameters.Count == 0) + path = _path; + else if (param.StartsWith(WebDavPath.Separator)) + path = param; + else + path = WebDavPath.Combine(_path, param); - var entry = await _cloud.GetItemAsync(path); - if (entry is null) - return SpecialCommandResult.Fail; + var entry = await _cloud.GetItemAsync(path); + if (entry is null) + return SpecialCommandResult.Fail; - var res = await _cloud.Remove(entry); - return new SpecialCommandResult(res); - } + var res = await _cloud.Remove(entry); + return new SpecialCommandResult(res); } } diff --git a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/FishCommand.cs b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/FishCommand.cs index cff49b91..d706fd60 100644 --- a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/FishCommand.cs +++ b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/FishCommand.cs @@ -6,79 +6,78 @@ using YaR.Clouds.Base; using YaR.Clouds.Base.Repos.MailRuCloud; -namespace YaR.Clouds.SpecialCommands.Commands +namespace YaR.Clouds.SpecialCommands.Commands; + +/// +/// Join random file from cloud. If you got it - you are biggest f@kn lucker of Universe! +/// +public class FishCommand : SpecialCommand { - /// - /// Join random file from cloud. If you got it - you are biggest f@kn lucker of Universe! - /// - public class FishCommand : SpecialCommand + private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(FishCommand)); + + public FishCommand(Cloud cloud, string path, IList parameters) : base(cloud, path, parameters) { - private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(FishCommand)); + } - public FishCommand(Cloud cloud, string path, IList parameters) : base(cloud, path, parameters) - { - } + protected override MinMax MinMaxParamsCount { get; } = new(0); - protected override MinMax MinMaxParamsCount { get; } = new(0); + private static readonly Random Random = new(); - private static readonly Random Random = new(); + public override async Task Execute() + { + const string name = "FISHA.YEA"; + string target = WebDavPath.Combine(_path, name); - public override async Task Execute() - { - const string name = "FISHA.YEA"; - string target = WebDavPath.Combine(_path, name); + var randomHash = new byte[20]; + Random.NextBytes(randomHash); - var randomHash = new byte[20]; - Random.NextBytes(randomHash); + long randomSize = Random.Next(21, int.MaxValue); - long randomSize = Random.Next(21, int.MaxValue); + try + { + //var res = await new CreateFileRequest(Cloud.CloudApi, target, strRandomHash, randomSize, ConflictResolver.Rename).MakeRequestAsync(_connectionLimiter); + var hash = new FileHashMrc(randomHash); + var res = await _cloud.RequestRepo.AddFile(target, hash, randomSize, DateTime.Now, ConflictResolver.Rename); + if (res.Success) + { + Logger.Warn("╔╗╔╗╔╦══╦╗╔╗╔╗╔╦╦╗"); + Logger.Warn("║║║║║║╔╗║║║║║║║║║║"); + Logger.Warn("║║║║║║║║║║║║║║║║║║"); + Logger.Warn("║║║║║║║║║║║║║║╚╩╩╝"); + Logger.Warn("║╚╝╚╝║╚╝║╚╝╚╝║╔╦╦╗"); + Logger.Warn("╚═╝╚═╩══╩═╝╚═╝╚╩╩╝"); + Logger.Warn(""); + Logger.Warn("¦̵̱ ̵̱ ̵̱ ̵̱ ̵̱(̢ ̡͇̅└͇̅┘͇̅ (▤8כ−◦"); + } + } + catch (Exception) + { + string content = string.Empty; try { - //var res = await new CreateFileRequest(Cloud.CloudApi, target, strRandomHash, randomSize, ConflictResolver.Rename).MakeRequestAsync(_connectionLimiter); - var hash = new FileHashMrc(randomHash); - var res = await _cloud.RequestRepo.AddFile(target, hash, randomSize, DateTime.Now, ConflictResolver.Rename); - if (res.Success) - { - Logger.Warn("╔╗╔╗╔╦══╦╗╔╗╔╗╔╦╦╗"); - Logger.Warn("║║║║║║╔╗║║║║║║║║║║"); - Logger.Warn("║║║║║║║║║║║║║║║║║║"); - Logger.Warn("║║║║║║║║║║║║║║╚╩╩╝"); - Logger.Warn("║╚╝╚╝║╚╝║╚╝╚╝║╔╦╦╗"); - Logger.Warn("╚═╝╚═╩══╩═╝╚═╝╚╩╩╝"); - Logger.Warn(""); - Logger.Warn("¦̵̱ ̵̱ ̵̱ ̵̱ ̵̱(̢ ̡͇̅└͇̅┘͇̅ (▤8כ−◦"); - - } + // Replace obsolete methods + //using WebClient client = new WebClient(); + //string htmlCode = client.DownloadString("http://www.smartphrase.com/cgi-bin/randomphrase.cgi?spanish&humorous&normal&15&2&12&16&1&5"); + using HttpClient client = new HttpClient(); + HttpResponseMessage response = await client.GetAsync("http://www.smartphrase.com/cgi-bin/randomphrase.cgi?spanish&humorous&normal&15&2&12&16&1&5"); + response.EnsureSuccessStatusCode(); + string htmlCode = await response.Content.ReadAsStringAsync(); + content = Regex.Match(htmlCode, + @"\s*\s*\s*(?.*?)

\s*(?.*?)\s*

") + .Groups["phraseeng"].Value; } catch (Exception) { - string content = string.Empty; - try - { - // Replace obsolete methods - //using WebClient client = new WebClient(); - //string htmlCode = client.DownloadString("http://www.smartphrase.com/cgi-bin/randomphrase.cgi?spanish&humorous&normal&15&2&12&16&1&5"); - using HttpClient client = new HttpClient(); - HttpResponseMessage response = await client.GetAsync("http://www.smartphrase.com/cgi-bin/randomphrase.cgi?spanish&humorous&normal&15&2&12&16&1&5"); - response.EnsureSuccessStatusCode(); - string htmlCode = await response.Content.ReadAsStringAsync(); - content = Regex.Match(htmlCode, - @"\s*\s*\s*(?.*?)

\s*(?.*?)\s*

") - .Groups["phraseeng"].Value; - } - catch (Exception) - { - // ignored - } - if (string.IsNullOrEmpty(content)) - content = @"Maybe next time ¯\_(ツ)_/¯"; - - _cloud.UploadFile(WebDavPath.Combine(_path, $"{DateTime.Now:yyyy-MM-dd hh-mm-ss} Not today, dude.txt"), content); + // ignored } + if (string.IsNullOrEmpty(content)) + content = @"Maybe next time ¯\_(ツ)_/¯"; - return SpecialCommandResult.Success; - + _cloud.UploadFile(WebDavPath.Combine(_path, $"{DateTime.Now:yyyy-MM-dd hh-mm-ss} Not today, dude.txt"), content); } + + return SpecialCommandResult.Success; + } } diff --git a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/JoinCommand.cs b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/JoinCommand.cs index f927fbdf..a921dbd6 100644 --- a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/JoinCommand.cs +++ b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/JoinCommand.cs @@ -5,72 +5,71 @@ using YaR.Clouds.Base; using YaR.Clouds.Base.Repos.MailRuCloud; -namespace YaR.Clouds.SpecialCommands.Commands +namespace YaR.Clouds.SpecialCommands.Commands; + +public partial class JoinCommand : SpecialCommand { - public partial class JoinCommand: SpecialCommand - { - private const string CommandRegexMask = @"(?snx-) (https://?cloud.mail.ru/public)?(?/\w*/?\w*)/?\s*"; - private const string HashRegexMask = @"#(?\w+)"; - private const string SizeRegexMask = @"(?\w+)"; + private const string CommandRegexMask = @"(?snx-) (https://?cloud.mail.ru/public)?(?/\w*/?\w*)/?\s*"; + private const string HashRegexMask = @"#(?\w+)"; + private const string SizeRegexMask = @"(?\w+)"; #if NET7_0_OR_GREATER - [GeneratedRegex(CommandRegexMask)] - private static partial Regex CommandRegex(); - private static readonly Regex s_commandRegex = CommandRegex(); - [GeneratedRegex(HashRegexMask)] - private static partial Regex HashRegex(); - private static readonly Regex s_hashRegex = HashRegex(); - [GeneratedRegex(SizeRegexMask)] - private static partial Regex SizeRegex(); - private static readonly Regex s_sizeRegex = SizeRegex(); + [GeneratedRegex(CommandRegexMask)] + private static partial Regex CommandRegex(); + private static readonly Regex s_commandRegex = CommandRegex(); + [GeneratedRegex(HashRegexMask)] + private static partial Regex HashRegex(); + private static readonly Regex s_hashRegex = HashRegex(); + [GeneratedRegex(SizeRegexMask)] + private static partial Regex SizeRegex(); + private static readonly Regex s_sizeRegex = SizeRegex(); #else - private static readonly Regex s_commandRegex = new(CommandRegexMask, RegexOptions.Compiled); - private static readonly Regex s_hashRegex = new(HashRegexMask, RegexOptions.Compiled); - private static readonly Regex s_sizeRegex = new(SizeRegexMask, RegexOptions.Compiled); + private static readonly Regex s_commandRegex = new(CommandRegexMask, RegexOptions.Compiled); + private static readonly Regex s_hashRegex = new(HashRegexMask, RegexOptions.Compiled); + private static readonly Regex s_sizeRegex = new(SizeRegexMask, RegexOptions.Compiled); #endif - public JoinCommand(Cloud cloud, string path, IList parameters): base(cloud, path, parameters) - { - var m = s_commandRegex.Match(_parameters[0]); + public JoinCommand(Cloud cloud, string path, IList parameters) : base(cloud, path, parameters) + { + var m = s_commandRegex.Match(_parameters[0]); - if (m.Success) //join by shared link - _func = () => ExecuteByLink(_path, m.Groups["data"].Value); - else + if (m.Success) //join by shared link + _func = () => ExecuteByLink(_path, m.Groups["data"].Value); + else + { + var mhash = s_hashRegex.Match(_parameters[0]); + var msize = s_sizeRegex.Match(_parameters[1]); + if (mhash.Success && msize.Success && _parameters.Count == 3) //join by hash and size { - var mhash = s_hashRegex.Match(_parameters[0]); - var msize = s_sizeRegex.Match(_parameters[1]); - if (mhash.Success && msize.Success && _parameters.Count == 3) //join by hash and size - { - _func = () => ExecuteByHash(_path, mhash.Groups["data"].Value, long.Parse(_parameters[1]), _parameters[2]); - } + _func = () => ExecuteByHash(_path, mhash.Groups["data"].Value, long.Parse(_parameters[1]), _parameters[2]); } } + } - protected override MinMax MinMaxParamsCount { get; } = new(1, 3); + protected override MinMax MinMaxParamsCount { get; } = new(1, 3); - private readonly Func> _func; + private readonly Func> _func; - public override Task Execute() - { - return _func != null - ? _func() - : Task.FromResult(new SpecialCommandResult(false, "Invalid parameters")); - } + public override Task Execute() + { + return _func != null + ? _func() + : Task.FromResult(new SpecialCommandResult(false, "Invalid parameters")); + } - private async Task ExecuteByLink(string path, string link) - { - var k = await _cloud.CloneItem(path, link); - return new SpecialCommandResult(k.IsSuccess); - } + private async Task ExecuteByLink(string path, string link) + { + var k = await _cloud.CloneItem(path, link); + return new SpecialCommandResult(k.IsSuccess); + } - private async Task ExecuteByHash(string path, string hash, long size, string paramPath) - { - string fpath = WebDavPath.IsFullPath(paramPath) - ? paramPath - : WebDavPath.Combine(path, paramPath); + private async Task ExecuteByHash(string path, string hash, long size, string paramPath) + { + string fpath = WebDavPath.IsFullPath(paramPath) + ? paramPath + : WebDavPath.Combine(path, paramPath); - //TODO: now mail.ru only - var k = await _cloud.AddFile(new FileHashMrc(hash), fpath, size, ConflictResolver.Rename); - return new SpecialCommandResult(k.Success); - } + //TODO: now mail.ru only + var k = await _cloud.AddFile(new FileHashMrc(hash), fpath, size, ConflictResolver.Rename); + return new SpecialCommandResult(k.Success); } } diff --git a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/ListCommand.cs b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/ListCommand.cs index 0911292e..6d2f5334 100644 --- a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/ListCommand.cs +++ b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/ListCommand.cs @@ -7,64 +7,63 @@ using YaR.Clouds.Base.Repos; using YaR.Clouds.Links; -namespace YaR.Clouds.SpecialCommands.Commands +namespace YaR.Clouds.SpecialCommands.Commands; + +public class ListCommand : SpecialCommand { - public class ListCommand : SpecialCommand - { - private const string FileListExtention = ".wdmrc.list.lst"; + private const string FileListExtention = ".wdmrc.list.lst"; - //private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(FishCommand)); + //private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(FishCommand)); - public ListCommand(Cloud cloud, string path, IList parameters) : base(cloud, path, parameters) - { - } + public ListCommand(Cloud cloud, string path, IList parameters) : base(cloud, path, parameters) + { + } - protected override MinMax MinMaxParamsCount { get; } = new(0, 1); + protected override MinMax MinMaxParamsCount { get; } = new(0, 1); - public override async Task Execute() - { - string target = _parameters.Count > 0 && !string.IsNullOrWhiteSpace(_parameters[0]) - ? _parameters[0].StartsWith(WebDavPath.Separator) ? _parameters[0] : WebDavPath.Combine(_path, _parameters[0]) - : _path; + public override async Task Execute() + { + string target = _parameters.Count > 0 && !string.IsNullOrWhiteSpace(_parameters[0]) + ? _parameters[0].StartsWith(WebDavPath.Separator) ? _parameters[0] : WebDavPath.Combine(_path, _parameters[0]) + : _path; - var resolvedTarget = await RemotePath.Get(target, _cloud.LinkManager); - var entry = await _cloud.RequestRepo.FolderInfo(resolvedTarget); - string resFilepath = WebDavPath.Combine(_path, string.Concat(entry.Name, FileListExtention)); + var resolvedTarget = await RemotePath.Get(target, _cloud.LinkManager); + var entry = await _cloud.RequestRepo.FolderInfo(resolvedTarget); + string resFilepath = WebDavPath.Combine(_path, string.Concat(entry.Name, FileListExtention)); - var sb = new StringBuilder(); + var sb = new StringBuilder(); - foreach (var e in Flat(entry, _cloud.LinkManager)) - { - string hash = (e as File)?.Hash.ToString() ?? "-"; - string link = e.PublicLinks.Values.FirstOrDefault()?.Uri.OriginalString ?? "-"; - sb.AppendLine( - $"{e.FullPath}\t{e.Size.DefaultValue}\t{e.CreationTimeUtc:yyyy.MM.dd HH:mm:ss}\t{hash}\t{link}"); - } + foreach (var e in Flat(entry, _cloud.LinkManager)) + { + string hash = (e as File)?.Hash.ToString() ?? "-"; + string link = e.PublicLinks.Values.FirstOrDefault()?.Uri.OriginalString ?? "-"; + sb.AppendLine( + $"{e.FullPath}\t{e.Size.DefaultValue}\t{e.CreationTimeUtc:yyyy.MM.dd HH:mm:ss}\t{hash}\t{link}"); + } - _cloud.UploadFile(resFilepath, sb.ToString()); + _cloud.UploadFile(resFilepath, sb.ToString()); - return SpecialCommandResult.Success; - } + return SpecialCommandResult.Success; + } - private IEnumerable Flat(IEntry entry, LinkManager lm) - { - yield return entry; + private IEnumerable Flat(IEntry entry, LinkManager lm) + { + yield return entry; - var ifolders = entry.Descendants - .AsParallel() - .WithDegreeOfParallelism(5) - .Select(it => it switch - { - File => it, - Folder ifolder => ifolder.IsChildrenLoaded - ? ifolder - : _cloud.RequestRepo.FolderInfo(RemotePath.Get(it.FullPath, lm).Result, depth: 3).Result, - _ => throw new NotImplementedException("Unknown item type") - }) - .OrderBy(it => it.Name); + var ifolders = entry.Descendants + .AsParallel() + .WithDegreeOfParallelism(5) + .Select(it => it switch + { + File => it, + Folder ifolder => ifolder.IsChildrenLoaded + ? ifolder + : _cloud.RequestRepo.FolderInfo(RemotePath.Get(it.FullPath, lm).Result, depth: 3).Result, + _ => throw new NotImplementedException("Unknown item type") + }) + .OrderBy(it => it.Name); - foreach (var item in ifolders.SelectMany(f => Flat(f, lm))) - yield return item; - } + foreach (var item in ifolders.SelectMany(f => Flat(f, lm))) + yield return item; } } diff --git a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/LocalToServerCopyCommand.cs b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/LocalToServerCopyCommand.cs index 3738e81a..163e4574 100644 --- a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/LocalToServerCopyCommand.cs +++ b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/LocalToServerCopyCommand.cs @@ -3,39 +3,38 @@ using System.Threading.Tasks; using YaR.Clouds.Base; -namespace YaR.Clouds.SpecialCommands.Commands +namespace YaR.Clouds.SpecialCommands.Commands; + +public class LocalToServerCopyCommand : SpecialCommand { - public class LocalToServerCopyCommand : SpecialCommand - { - private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(LocalToServerCopyCommand)); + private static readonly log4net.ILog Logger = log4net.LogManager.GetLogger(typeof(LocalToServerCopyCommand)); - public LocalToServerCopyCommand(Cloud cloud, string path, IList parameters) : base(cloud, path, parameters) - { - } + public LocalToServerCopyCommand(Cloud cloud, string path, IList parameters) : base(cloud, path, parameters) + { + } - protected override MinMax MinMaxParamsCount { get; } = new(1); + protected override MinMax MinMaxParamsCount { get; } = new(1); - public override async Task Execute() + public override async Task Execute() + { + var res = await Task.Run(async () => { - var res = await Task.Run(async () => - { - var sourceFileInfo = new FileInfo(_parameters[0]); + var sourceFileInfo = new FileInfo(_parameters[0]); - string name = sourceFileInfo.Name; - string targetPath = WebDavPath.Combine(_path, name); + string name = sourceFileInfo.Name; + string targetPath = WebDavPath.Combine(_path, name); - Logger.Info($"COMMAND:COPY:{_parameters[0]}"); + Logger.Info($"COMMAND:COPY:{_parameters[0]}"); - using (var source = System.IO.File.Open(_parameters[0], FileMode.Open, FileAccess.Read, FileShare.Read)) - using (var target = await _cloud.GetFileUploadStream(targetPath, sourceFileInfo.Length, null, null).ConfigureAwait(false)) - { - await source.CopyToAsync(target); - } + using (var source = System.IO.File.Open(_parameters[0], FileMode.Open, FileAccess.Read, FileShare.Read)) + using (var target = await _cloud.GetFileUploadStream(targetPath, sourceFileInfo.Length, null, null).ConfigureAwait(false)) + { + await source.CopyToAsync(target); + } - return SpecialCommandResult.Success; - }); + return SpecialCommandResult.Success; + }); - return res; - } + return res; } } diff --git a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/MoveCommand.cs b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/MoveCommand.cs index 42dd3abb..64b0693b 100644 --- a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/MoveCommand.cs +++ b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/MoveCommand.cs @@ -2,28 +2,27 @@ using System.Threading.Tasks; using YaR.Clouds.Base; -namespace YaR.Clouds.SpecialCommands.Commands +namespace YaR.Clouds.SpecialCommands.Commands; + +public class MoveCommand : SpecialCommand { - public class MoveCommand : SpecialCommand + public MoveCommand(Cloud cloud, string path, IList parameters) + : base(cloud, path, parameters) { - public MoveCommand(Cloud cloud, string path, IList parameters) - : base(cloud, path, parameters) - { - } + } - protected override MinMax MinMaxParamsCount { get; } = new(1, 2); + protected override MinMax MinMaxParamsCount { get; } = new(1, 2); - public override async Task Execute() - { - string source = WebDavPath.Clean(_parameters.Count == 1 ? _path : _parameters[0]); - string target = WebDavPath.Clean(_parameters.Count == 1 ? _parameters[0] : _parameters[1]); + public override async Task Execute() + { + string source = WebDavPath.Clean(_parameters.Count == 1 ? _path : _parameters[0]); + string target = WebDavPath.Clean(_parameters.Count == 1 ? _parameters[0] : _parameters[1]); - var entry = await _cloud.GetItemAsync(source); - if (entry is null) - return SpecialCommandResult.Fail; + var entry = await _cloud.GetItemAsync(source); + if (entry is null) + return SpecialCommandResult.Fail; - var res = await _cloud.MoveAsync(entry, target); - return new SpecialCommandResult(res); - } + var res = await _cloud.MoveAsync(entry, target); + return new SpecialCommandResult(res); } } diff --git a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/RemoveBadLinksCommand.cs b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/RemoveBadLinksCommand.cs index 6e0382d4..a2def60b 100644 --- a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/RemoveBadLinksCommand.cs +++ b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/RemoveBadLinksCommand.cs @@ -1,20 +1,20 @@ using System.Collections.Generic; using System.Threading.Tasks; -namespace YaR.Clouds.SpecialCommands.Commands +namespace YaR.Clouds.SpecialCommands.Commands; + +public class RemoveBadLinksCommand : SpecialCommand { - public class RemoveBadLinksCommand : SpecialCommand + public RemoveBadLinksCommand(Cloud cloud, string path, IList parameters) + : base(cloud, path, parameters) { - public RemoveBadLinksCommand(Cloud cloud, string path, IList parameters): base(cloud, path, parameters) - { - } + } - protected override MinMax MinMaxParamsCount { get; } = new(0); + protected override MinMax MinMaxParamsCount { get; } = new(0); - public override Task Execute() - { - _cloud.RemoveDeadLinks(); - return Task.FromResult(SpecialCommandResult.Success); - } + public override Task Execute() + { + _cloud.RemoveDeadLinks(); + return Task.FromResult(SpecialCommandResult.Success); } } diff --git a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/ShareCommand.cs b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/ShareCommand.cs index ce89c287..a162e335 100644 --- a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/ShareCommand.cs +++ b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/ShareCommand.cs @@ -5,55 +5,54 @@ using YaR.Clouds.Common; using YaR.Clouds.Extensions; -namespace YaR.Clouds.SpecialCommands.Commands +namespace YaR.Clouds.SpecialCommands.Commands; + +public class ShareCommand : SpecialCommand { - public class ShareCommand : SpecialCommand - { - public ShareCommand(Cloud cloud, string path, bool generateDirectVideoLink, bool makeM3UFile, IList parameters) - : base(cloud, path, parameters) - { - _generateDirectVideoLink = generateDirectVideoLink; - _makeM3UFile = makeM3UFile; - } + public ShareCommand(Cloud cloud, string path, bool generateDirectVideoLink, bool makeM3UFile, IList parameters) + : base(cloud, path, parameters) + { + _generateDirectVideoLink = generateDirectVideoLink; + _makeM3UFile = makeM3UFile; + } - private readonly bool _generateDirectVideoLink; - private readonly bool _makeM3UFile; + private readonly bool _generateDirectVideoLink; + private readonly bool _makeM3UFile; - protected override MinMax MinMaxParamsCount { get; } = new(0, 2); + protected override MinMax MinMaxParamsCount { get; } = new(0, 2); - public override async Task Execute() + public override async Task Execute() + { + string path; + string param = _parameters.Count == 0 + ? string.Empty + : _parameters[0].Replace("\\", WebDavPath.Separator); + SharedVideoResolution videoResolution = _parameters.Count < 2 + ? _cloud.Settings.DefaultSharedVideoResolution + : EnumExtensions.ParseEnumMemberValue(_parameters[1]); + + if (_parameters.Count == 0) + path = _path; + else if (param.StartsWith(WebDavPath.Separator)) + path = param; + else + path = WebDavPath.Combine(_path, param); + + var entry = await _cloud.GetItemAsync(path); + if (entry is null) + return SpecialCommandResult.Fail; + + try { - string path; - string param = _parameters.Count == 0 - ? string.Empty - : _parameters[0].Replace("\\", WebDavPath.Separator); - SharedVideoResolution videoResolution = _parameters.Count < 2 - ? _cloud.Settings.DefaultSharedVideoResolution - : EnumExtensions.ParseEnumMemberValue(_parameters[1]); - - if (_parameters.Count == 0) - path = _path; - else if (param.StartsWith(WebDavPath.Separator)) - path = param; - else - path = WebDavPath.Combine(_path, param); - - var entry = await _cloud.GetItemAsync(path); - if (entry is null) - return SpecialCommandResult.Fail; - - try - { - await _cloud.Publish(entry, true, _generateDirectVideoLink, _makeM3UFile, videoResolution); - } - catch (Exception e) - { - return new SpecialCommandResult(false, e.Message); - } - - return SpecialCommandResult.Success; + await _cloud.Publish(entry, true, _generateDirectVideoLink, _makeM3UFile, videoResolution); } + catch (Exception e) + { + return new SpecialCommandResult(false, e.Message); + } + + return SpecialCommandResult.Success; } } diff --git a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/SharedFolderLinkCommand.cs b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/SharedFolderLinkCommand.cs index ecc5824d..1d01a281 100644 --- a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/SharedFolderLinkCommand.cs +++ b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/SharedFolderLinkCommand.cs @@ -6,56 +6,55 @@ using YaR.Clouds.Base.Repos.MailRuCloud; using YaR.Clouds.Links; -namespace YaR.Clouds.SpecialCommands.Commands +namespace YaR.Clouds.SpecialCommands.Commands; + +public partial class SharedFolderLinkCommand : SpecialCommand { - public partial class SharedFolderLinkCommand : SpecialCommand + public SharedFolderLinkCommand(Cloud cloud, string path, IList parameters) + : base(cloud, path, parameters) { - public SharedFolderLinkCommand(Cloud cloud, string path, IList parameters) - : base(cloud, path, parameters) - { - } + } - protected override MinMax MinMaxParamsCount { get; } = new(1, 2); + protected override MinMax MinMaxParamsCount { get; } = new(1, 2); - private const string CommandRegexMask = @"(?snx-)\s* (?(https://?cloud.mail.ru/public)?.*)/? \s*"; + private const string CommandRegexMask = @"(?snx-)\s* (?(https://?cloud.mail.ru/public)?.*)/? \s*"; #if NET7_0_OR_GREATER - [GeneratedRegex(CommandRegexMask)] - private static partial Regex CommandRegex(); - private static readonly Regex s_commandRegex = CommandRegex(); + [GeneratedRegex(CommandRegexMask)] + private static partial Regex CommandRegex(); + private static readonly Regex s_commandRegex = CommandRegex(); #else - private static readonly Regex s_commandRegex = new(CommandRegexMask, RegexOptions.Compiled); + private static readonly Regex s_commandRegex = new(CommandRegexMask, RegexOptions.Compiled); #endif - public override async Task Execute() - { - var m = s_commandRegex.Match(_parameters[0]); + public override async Task Execute() + { + var m = s_commandRegex.Match(_parameters[0]); - if (!m.Success) return SpecialCommandResult.Fail; + if (!m.Success) return SpecialCommandResult.Fail; - var publicBaseUrl = _cloud.RequestRepo.PublicBaseUrlDefault; - var url = new Uri(m.Groups["url"].Value, UriKind.RelativeOrAbsolute); - if (!url.IsAbsoluteUri) - url = new Uri(publicBaseUrl + m.Groups["url"].Value, UriKind.Absolute); + var publicBaseUrl = _cloud.RequestRepo.PublicBaseUrlDefault; + var url = new Uri(m.Groups["url"].Value, UriKind.RelativeOrAbsolute); + if (!url.IsAbsoluteUri) + url = new Uri(publicBaseUrl + m.Groups["url"].Value, UriKind.Absolute); - //TODO: make method in MailRuCloud to get entry by url - //var item = await new ItemInfoRequest(Cloud.CloudApi, m.Groups["url"].Value, true).MakeRequestAsync(_connectionLimiter); + //TODO: make method in MailRuCloud to get entry by url + //var item = await new ItemInfoRequest(Cloud.CloudApi, m.Groups["url"].Value, true).MakeRequestAsync(_connectionLimiter); - var item = await _cloud.RequestRepo.ItemInfo(RemotePath.Get(new Link(url))); - var entry = item.ToEntry(publicBaseUrl); - if (entry is null) - return SpecialCommandResult.Fail; + var item = await _cloud.RequestRepo.ItemInfo(RemotePath.Get(new Link(url))); + var entry = item.ToEntry(publicBaseUrl); + if (entry is null) + return SpecialCommandResult.Fail; - string name = _parameters.Count > 1 && !string.IsNullOrWhiteSpace(_parameters[1]) - ? _parameters[1] - : entry.Name; + string name = _parameters.Count > 1 && !string.IsNullOrWhiteSpace(_parameters[1]) + ? _parameters[1] + : entry.Name; - var res = await _cloud.LinkItem( - url, - _path, name, entry.IsFile, entry.Size, entry.CreationTimeUtc); + var res = await _cloud.LinkItem( + url, + _path, name, entry.IsFile, entry.Size, entry.CreationTimeUtc); - return new SpecialCommandResult(res); - } + return new SpecialCommandResult(res); } } diff --git a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/TestCommand.cs b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/TestCommand.cs index dcc83d47..4333a11c 100644 --- a/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/TestCommand.cs +++ b/MailRuCloud/MailRuCloudApi/SpecialCommands/Commands/TestCommand.cs @@ -2,34 +2,33 @@ using System.Threading.Tasks; using YaR.Clouds.Base; -namespace YaR.Clouds.SpecialCommands.Commands +namespace YaR.Clouds.SpecialCommands.Commands; + +public class TestCommand : SpecialCommand { - public class TestCommand : SpecialCommand + public TestCommand(Cloud cloud, string path, IList parameters) + : base(cloud, path, parameters) { - public TestCommand(Cloud cloud, string path, IList parameters) - : base(cloud, path, parameters) - { - } + } - protected override MinMax MinMaxParamsCount { get; } = new(1); + protected override MinMax MinMaxParamsCount { get; } = new(1); - public override async Task Execute() - { - string path = _parameters[0].Replace("\\", WebDavPath.Separator); - - if (await _cloud.GetItemAsync(path) is not File entry) - return SpecialCommandResult.Fail; + public override async Task Execute() + { + string path = _parameters[0].Replace("\\", WebDavPath.Separator); - //var auth = await new OAuthRequest(Cloud.CloudApi).MakeRequestAsync(_connectionLimiter); + if (await _cloud.GetItemAsync(path) is not File entry) + return SpecialCommandResult.Fail; - bool removed = await _cloud.Remove(entry, false); - if (removed) - { - //var addreq = await new MobAddFileRequest(Cloud.CloudApi, entry.FullPath, entry.Hash, entry.Size, new DateTime(2010, 1, 1), ConflictResolver.Rename) - // .MakeRequestAsync(_connectionLimiter); - } + //var auth = await new OAuthRequest(Cloud.CloudApi).MakeRequestAsync(_connectionLimiter); - return SpecialCommandResult.Success; + bool removed = await _cloud.Remove(entry, false); + if (removed) + { + //var addreq = await new MobAddFileRequest(Cloud.CloudApi, entry.FullPath, entry.Hash, entry.Size, new DateTime(2010, 1, 1), ConflictResolver.Rename) + // .MakeRequestAsync(_connectionLimiter); } + + return SpecialCommandResult.Success; } } diff --git a/MailRuCloud/MailRuCloudApi/SpecialCommands/SpecialCommand.cs b/MailRuCloud/MailRuCloudApi/SpecialCommands/SpecialCommand.cs index 39d9d7fb..6426ce6a 100644 --- a/MailRuCloud/MailRuCloudApi/SpecialCommands/SpecialCommand.cs +++ b/MailRuCloud/MailRuCloudApi/SpecialCommands/SpecialCommand.cs @@ -2,55 +2,54 @@ using System.Collections.Generic; using System.Threading.Tasks; -namespace YaR.Clouds.SpecialCommands +namespace YaR.Clouds.SpecialCommands; + +public abstract class SpecialCommand { - public abstract class SpecialCommand - { - protected readonly Cloud _cloud; - protected readonly string _path; - protected readonly IList _parameters; + protected readonly Cloud _cloud; + protected readonly string _path; + protected readonly IList _parameters; - protected abstract MinMax MinMaxParamsCount { get; } + protected abstract MinMax MinMaxParamsCount { get; } - protected SpecialCommand(Cloud cloud, string path, IList parameters) - { - _cloud = cloud; - _path = path; - _parameters = parameters; + protected SpecialCommand(Cloud cloud, string path, IList parameters) + { + _cloud = cloud; + _path = path; + _parameters = parameters; - CheckParams(); - } + CheckParams(); + } - public virtual Task Execute() - { - if (_parameters.Count < MinMaxParamsCount.Min || _parameters.Count > MinMaxParamsCount.Max) - return Task.FromResult(SpecialCommandResult.Fail); + public virtual Task Execute() + { + if (_parameters.Count < MinMaxParamsCount.Min || _parameters.Count > MinMaxParamsCount.Max) + return Task.FromResult(SpecialCommandResult.Fail); - return Task.FromResult(SpecialCommandResult.Success); - } + return Task.FromResult(SpecialCommandResult.Success); + } - private void CheckParams() - { - if (_parameters.Count < MinMaxParamsCount.Min || _parameters.Count > MinMaxParamsCount.Max) - throw new ArgumentException("Invalid parameters count"); - } + private void CheckParams() + { + if (_parameters.Count < MinMaxParamsCount.Min || _parameters.Count > MinMaxParamsCount.Max) + throw new ArgumentException("Invalid parameters count"); + } + +} +public readonly struct MinMax where T : IComparable +{ + public MinMax(T min, T max) + { + if (min.CompareTo(max) > 0) throw new ArgumentException("min > max"); + Min = min; + Max = max; } - public readonly struct MinMax where T : IComparable + public MinMax(T one) : this(one, one) { - public MinMax(T min, T max) - { - if (min.CompareTo(max) > 0) throw new ArgumentException("min > max"); - Min = min; - Max = max; - } - - public MinMax(T one) : this(one, one) - { - } - - public T Min { get; } - public T Max { get; } } + + public T Min { get; } + public T Max { get; } } diff --git a/MailRuCloud/MailRuCloudApi/SpecialCommands/SpecialCommandFabric.cs b/MailRuCloud/MailRuCloudApi/SpecialCommands/SpecialCommandFabric.cs index 666169ac..7d2a0e3d 100644 --- a/MailRuCloud/MailRuCloudApi/SpecialCommands/SpecialCommandFabric.cs +++ b/MailRuCloud/MailRuCloudApi/SpecialCommands/SpecialCommandFabric.cs @@ -5,186 +5,182 @@ using YaR.Clouds.Base; using YaR.Clouds.SpecialCommands.Commands; -namespace YaR.Clouds.SpecialCommands +namespace YaR.Clouds.SpecialCommands; + +///

+/// +/// +public partial class SpecialCommandFabric { - /// - /// - /// - public partial class SpecialCommandFabric + private static readonly List CommandContainers = new() { - private static readonly List CommandContainers = new() - { - new() - { - Commands = new [] {"del"}, - CreateFunc = (cloud, path, param) => new DeleteCommand(cloud, path, param) - }, - new() - { - Commands = new [] {"link"}, - CreateFunc = (cloud, path, param) => new SharedFolderLinkCommand(cloud, path, param) - }, - new() - { - Commands = new [] {"link", "check"}, - CreateFunc = (cloud, path, param) => new RemoveBadLinksCommand(cloud, path, param) - }, - new() - { - Commands = new [] {"join"}, - CreateFunc = (cloud, path, param) => new JoinCommand(cloud, path, param) - }, - new() - { - Commands = new [] {"copy"}, - CreateFunc = (cloud, path, param) => new CopyCommand(cloud, path, param) - }, - new() - { - Commands = new [] {"move"}, - CreateFunc = (cloud, path, param) => new MoveCommand(cloud, path, param) - }, - new() - { - Commands = new [] {"fish"}, - CreateFunc = (cloud, path, param) => new FishCommand(cloud, path, param) - }, - new() - { - Commands = new [] {"lcopy"}, - CreateFunc = (cloud, path, param) => new LocalToServerCopyCommand(cloud, path, param) - }, - new() - { - Commands = new [] {"crypt", "init"}, - CreateFunc = (cloud, path, param) => new CryptInitCommand(cloud, path, param) - }, - new() - { - Commands = new [] {"crypt", "passwd"}, - CreateFunc = (cloud, path, param) => new CryptPasswdCommand(cloud, path, param) - }, - new() - { - Commands = new [] {"share"}, - CreateFunc = (cloud, path, param) => new ShareCommand(cloud, path, false, false, param) - }, - new() - { - Commands = new [] {"sharev"}, - CreateFunc = (cloud, path, param) => new ShareCommand(cloud, path, true, false, param) - }, - new() - { - Commands = new [] {"pl"}, - CreateFunc = (cloud, path, param) => new ShareCommand(cloud, path, true, true, param) - }, - new() - { - Commands = new [] {"rlist"}, - CreateFunc = (cloud, path, param) => new ListCommand(cloud, path, param) - }, - new() - { - Commands = new [] {"clean", "trash"}, - CreateFunc = (cloud, path, param) => new CleanTrashCommand(cloud, path, param) - }, - - new() - { - Commands = new [] {"test"}, - CreateFunc = (cloud, path, param) => new TestCommand(cloud, path, param) - } - }; - + new() + { + Commands = new [] {"del"}, + CreateFunc = (cloud, path, param) => new DeleteCommand(cloud, path, param) + }, + new() + { + Commands = new [] {"link"}, + CreateFunc = (cloud, path, param) => new SharedFolderLinkCommand(cloud, path, param) + }, + new() + { + Commands = new [] {"link", "check"}, + CreateFunc = (cloud, path, param) => new RemoveBadLinksCommand(cloud, path, param) + }, + new() + { + Commands = new [] {"join"}, + CreateFunc = (cloud, path, param) => new JoinCommand(cloud, path, param) + }, + new() + { + Commands = new [] {"copy"}, + CreateFunc = (cloud, path, param) => new CopyCommand(cloud, path, param) + }, + new() + { + Commands = new [] {"move"}, + CreateFunc = (cloud, path, param) => new MoveCommand(cloud, path, param) + }, + new() + { + Commands = new [] {"fish"}, + CreateFunc = (cloud, path, param) => new FishCommand(cloud, path, param) + }, + new() + { + Commands = new [] {"lcopy"}, + CreateFunc = (cloud, path, param) => new LocalToServerCopyCommand(cloud, path, param) + }, + new() + { + Commands = new [] {"crypt", "init"}, + CreateFunc = (cloud, path, param) => new CryptInitCommand(cloud, path, param) + }, + new() + { + Commands = new [] {"crypt", "passwd"}, + CreateFunc = (cloud, path, param) => new CryptPasswdCommand(cloud, path, param) + }, + new() + { + Commands = new [] {"share"}, + CreateFunc = (cloud, path, param) => new ShareCommand(cloud, path, false, false, param) + }, + new() + { + Commands = new [] {"sharev"}, + CreateFunc = (cloud, path, param) => new ShareCommand(cloud, path, true, false, param) + }, + new() + { + Commands = new [] {"pl"}, + CreateFunc = (cloud, path, param) => new ShareCommand(cloud, path, true, true, param) + }, + new() + { + Commands = new [] {"rlist"}, + CreateFunc = (cloud, path, param) => new ListCommand(cloud, path, param) + }, + new() + { + Commands = new [] {"clean", "trash"}, + CreateFunc = (cloud, path, param) => new CleanTrashCommand(cloud, path, param) + }, - public SpecialCommand Build(Cloud cloud, string param) + new() { - var res = ParseLine(param, cloud.Settings.SpecialCommandPrefix); - if (!res.IsValid && !string.IsNullOrEmpty(cloud.Settings.AdditionalSpecialCommandPrefix)) - res = ParseLine(param, cloud.Settings.AdditionalSpecialCommandPrefix); - if (!res.IsValid) - return null; + Commands = new [] {"test"}, + CreateFunc = (cloud, path, param) => new TestCommand(cloud, path, param) + } + }; - var parameters = ParseParameters(res.Data); - var commandContainer = FindCommandContainer(parameters); - if (commandContainer == null) return null; - parameters = parameters.Skip(commandContainer.Commands.Length).ToList(); - var cmd = commandContainer.CreateFunc(cloud, res.Path, parameters); + public SpecialCommand Build(Cloud cloud, string param) + { + var res = ParseLine(param, cloud.Settings.SpecialCommandPrefix); + if (!res.IsValid && !string.IsNullOrEmpty(cloud.Settings.AdditionalSpecialCommandPrefix)) + res = ParseLine(param, cloud.Settings.AdditionalSpecialCommandPrefix); + if (!res.IsValid) + return null; - return cmd; - } + var parameters = ParseParameters(res.Data); + var commandContainer = FindCommandContainer(parameters); + if (commandContainer == null) return null; - private static ParamsData ParseLine(string param, string prefix) - { - if (string.IsNullOrEmpty(prefix)) return ParamsData.Invalid; + parameters = parameters.Skip(commandContainer.Commands.Length).ToList(); + var cmd = commandContainer.CreateFunc(cloud, res.Path, parameters); - string pre = "/" + prefix; - if (null == param || !param.Contains(pre)) return ParamsData.Invalid; + return cmd; + } - int pos = param.LastIndexOf(pre, StringComparison.Ordinal); - string path = WebDavPath.Clean(param.Substring(0, pos + 1)); - string data = param.Substring(pos + pre.Length); + private static ParamsData ParseLine(string param, string prefix) + { + if (string.IsNullOrEmpty(prefix)) return ParamsData.Invalid; - return new ParamsData - { - IsValid = true, - Path = path, - Data = data - }; - } + string pre = "/" + prefix; + if (null == param || !param.Contains(pre)) return ParamsData.Invalid; + + int pos = param.LastIndexOf(pre, StringComparison.Ordinal); + string path = WebDavPath.Clean(param.Substring(0, pos + 1)); + string data = param.Substring(pos + pre.Length); - private struct ParamsData + return new ParamsData { - public bool IsValid { get; set; } - public string Path { get; set; } - public string Data { get; set; } + IsValid = true, + Path = path, + Data = data + }; + } - public static ParamsData Invalid => new() {IsValid = false}; + private struct ParamsData + { + public bool IsValid { get; set; } + public string Path { get; set; } + public string Data { get; set; } - } + public static ParamsData Invalid => new() { IsValid = false }; - private static SpecialCommandContainer FindCommandContainer(ICollection parameters) - { - var commandContainer = CommandContainers - .Where(cm => - cm.Commands.Length <= parameters.Count && - cm.Commands.SequenceEqual(parameters.Take(cm.Commands.Length))) - .Aggregate((agg, next) => next.Commands.Length > agg.Commands.Length ? next : agg); + } - return commandContainer; - } + private static SpecialCommandContainer FindCommandContainer(ICollection parameters) + { + var commandContainer = CommandContainers + .Where(cm => + cm.Commands.Length <= parameters.Count && + cm.Commands.SequenceEqual(parameters.Take(cm.Commands.Length))) + .Aggregate((agg, next) => next.Commands.Length > agg.Commands.Length ? next : agg); + + return commandContainer; + } - private const string CommandRegexMask = @"((""((?.*?)(?[\S]+))(\s)*)"; + private const string CommandRegexMask = @"((""((?.*?)(?[\S]+))(\s)*)"; #if NET7_0_OR_GREATER - [GeneratedRegex(CommandRegexMask)] - private static partial Regex CommandRegex(); - private static readonly Regex s_commandRegex = CommandRegex(); + [GeneratedRegex(CommandRegexMask)] + private static partial Regex CommandRegex(); + private static readonly Regex s_commandRegex = CommandRegex(); #else - private static readonly Regex s_commandRegex = new(CommandRegexMask, RegexOptions.Compiled); + private static readonly Regex s_commandRegex = new(CommandRegexMask, RegexOptions.Compiled); #endif - private static List ParseParameters(string paramString) - { - var list = s_commandRegex - .Matches(paramString) - // ReSharper disable once RedundantEnumerableCastCall - .Cast() - .Select(m => m.Groups["token"].Value) - .ToList(); - - return list; - } - - - - private class SpecialCommandContainer - { - public string[] Commands; - public Func, SpecialCommand> CreateFunc; - } + private static List ParseParameters(string paramString) + { + var list = s_commandRegex + .Matches(paramString) + // ReSharper disable once RedundantEnumerableCastCall + .Cast() + .Select(m => m.Groups["token"].Value) + .ToList(); + + return list; + } + private class SpecialCommandContainer + { + public string[] Commands; + public Func, SpecialCommand> CreateFunc; } } diff --git a/MailRuCloud/MailRuCloudApi/SpecialCommands/SpecialCommandResult.cs b/MailRuCloud/MailRuCloudApi/SpecialCommands/SpecialCommandResult.cs index f152d6ee..73ddb2df 100644 --- a/MailRuCloud/MailRuCloudApi/SpecialCommands/SpecialCommandResult.cs +++ b/MailRuCloud/MailRuCloudApi/SpecialCommands/SpecialCommandResult.cs @@ -1,22 +1,20 @@ -namespace YaR.Clouds.SpecialCommands +namespace YaR.Clouds.SpecialCommands; + +public class SpecialCommandResult { - public class SpecialCommandResult + public SpecialCommandResult(bool isSuccess) { - public SpecialCommandResult(bool isSuccess) - { - IsSuccess = isSuccess; - } - - public SpecialCommandResult(bool isSuccess, string message) : this(isSuccess) - { - Message = message; - } + IsSuccess = isSuccess; + } - public bool IsSuccess { get;} - public string Message { get; } + public SpecialCommandResult(bool isSuccess, string message) : this(isSuccess) + { + Message = message; + } - public static SpecialCommandResult Success => new(true); - public static SpecialCommandResult Fail => new(false); + public bool IsSuccess { get; } + public string Message { get; } - } + public static SpecialCommandResult Success => new(true); + public static SpecialCommandResult Fail => new(false); } diff --git a/MailRuCloud/MailRuCloudApi/Streams/DownloadStreamFabric.cs b/MailRuCloud/MailRuCloudApi/Streams/DownloadStreamFabric.cs index 4ee8b9a3..52675a41 100644 --- a/MailRuCloud/MailRuCloudApi/Streams/DownloadStreamFabric.cs +++ b/MailRuCloud/MailRuCloudApi/Streams/DownloadStreamFabric.cs @@ -3,58 +3,57 @@ using YaR.Clouds.XTSSharp; using File = YaR.Clouds.Base.File; -namespace YaR.Clouds.Streams -{ - public class DownloadStreamFabric - { - private readonly Cloud _cloud; +namespace YaR.Clouds.Streams; - public DownloadStreamFabric(Cloud cloud) - { - _cloud = cloud; - } +public class DownloadStreamFabric +{ + private readonly Cloud _cloud; - public Stream Create(File file, long? start = null, long? end = null) - { - if (file.ServiceInfo.IsCrypted) - return CreateXTSStream(file, start, end); + public DownloadStreamFabric(Cloud cloud) + { + _cloud = cloud; + } - //return new DownloadStream(file, _cloud.CloudApi, start, end); + public Stream Create(File file, long? start = null, long? end = null) + { + if (file.ServiceInfo.IsCrypted) + return CreateXTSStream(file, start, end); - var stream = _cloud.RequestRepo.GetDownloadStream(file, start, end); - return stream; - } + //return new DownloadStream(file, _cloud.CloudApi, start, end); - private Stream CreateXTSStream(File file, long? start = null, long? end = null) - { - var pub = CryptoUtil.GetCryptoPublicInfo(_cloud, file); - var key = CryptoUtil.GetCryptoKey(_cloud.Credentials.PasswordCrypt, pub.Salt); - var xts = XtsAes256.Create(key, pub.IV); + var stream = _cloud.RequestRepo.GetDownloadStream(file, start, end); + return stream; + } - long fileLength = file.OriginalSize; - long requestedOffset = start ?? 0; - long requestedEnd = end ?? fileLength; + private Stream CreateXTSStream(File file, long? start = null, long? end = null) + { + var pub = CryptoUtil.GetCryptoPublicInfo(_cloud, file); + var key = CryptoUtil.GetCryptoKey(_cloud.Credentials.PasswordCrypt, pub.Salt); + var xts = XtsAes256.Create(key, pub.IV); - long alignedOffset = requestedOffset / XTSSectorSize * XTSSectorSize; - long alignedEnd = requestedEnd % XTSBlockSize == 0 - ? requestedEnd - : (requestedEnd / XTSBlockSize + 1) * XTSBlockSize; - if (alignedEnd == 0) alignedEnd = 16; + long fileLength = file.OriginalSize; + long requestedOffset = start ?? 0; + long requestedEnd = end ?? fileLength; - var downStream = _cloud.RequestRepo.GetDownloadStream(file, alignedOffset, alignedEnd); + long alignedOffset = requestedOffset / XTSSectorSize * XTSSectorSize; + long alignedEnd = requestedEnd % XTSBlockSize == 0 + ? requestedEnd + : (requestedEnd / XTSBlockSize + 1) * XTSBlockSize; + if (alignedEnd == 0) alignedEnd = 16; - ulong startSector = (ulong)alignedOffset / XTSSectorSize; - int trimStart = (int)(requestedOffset - alignedOffset); - uint trimEnd = alignedEnd == fileLength - ? file.ServiceInfo.CryptInfo.AlignBytes - : (uint)(alignedEnd - requestedEnd); + var downStream = _cloud.RequestRepo.GetDownloadStream(file, alignedOffset, alignedEnd); - var xtsStream = new XTSReadOnlyStream(downStream, xts, XTSSectorSize, startSector, trimStart, trimEnd); + ulong startSector = (ulong)alignedOffset / XTSSectorSize; + int trimStart = (int)(requestedOffset - alignedOffset); + uint trimEnd = alignedEnd == fileLength + ? file.ServiceInfo.CryptInfo.AlignBytes + : (uint)(alignedEnd - requestedEnd); - return xtsStream; - } + var xtsStream = new XTSReadOnlyStream(downStream, xts, XTSSectorSize, startSector, trimStart, trimEnd); - public const int XTSSectorSize = 512; - public const long XTSBlockSize = XTSWriteOnlyStream.BlockSize; + return xtsStream; } + + public const int XTSSectorSize = 512; + public const long XTSBlockSize = XTSWriteOnlyStream.BlockSize; } diff --git a/MailRuCloud/MailRuCloudApi/Streams/SplittedUploadStream.cs b/MailRuCloud/MailRuCloudApi/Streams/SplittedUploadStream.cs index 273118a7..3296d1a9 100644 --- a/MailRuCloud/MailRuCloudApi/Streams/SplittedUploadStream.cs +++ b/MailRuCloud/MailRuCloudApi/Streams/SplittedUploadStream.cs @@ -6,207 +6,205 @@ using YaR.Clouds.Base.Streams; using File = YaR.Clouds.Base.File; -namespace YaR.Clouds.Streams +namespace YaR.Clouds.Streams; + +class SplittedUploadStream : Stream { - class SplittedUploadStream : Stream - { - private readonly string _destinationPath; - private readonly Cloud _cloud; - private long _size; - private readonly bool _checkHash; - private readonly CryptInfo _cryptInfo; - private readonly long _maxFileSize; - private File _origfile; + private readonly string _destinationPath; + private readonly Cloud _cloud; + private long _size; + private readonly bool _checkHash; + private readonly CryptInfo _cryptInfo; + private readonly long _maxFileSize; + private File _origfile; - private int _currFileId = -1; - private long _bytesWrote; - private UploadStream _uploadStream; + private int _currFileId = -1; + private long _bytesWrote; + private UploadStream _uploadStream; - private readonly List _files = new(); - private bool _performAsSplitted; + private readonly List _files = new(); + private bool _performAsSplitted; - public SplittedUploadStream(string destinationPath, Cloud cloud, long size, Action fileStreamSent, Action serverFileProcessed, bool checkHash = true, CryptInfo cryptInfo = null) - { - _destinationPath = destinationPath; - _cloud = cloud; - _size = size; - _checkHash = checkHash; - _cryptInfo = cryptInfo; + public SplittedUploadStream(string destinationPath, Cloud cloud, long size, Action fileStreamSent, Action serverFileProcessed, bool checkHash = true, CryptInfo cryptInfo = null) + { + _destinationPath = destinationPath; + _cloud = cloud; + _size = size; + _checkHash = checkHash; + _cryptInfo = cryptInfo; - FileStreamSent = fileStreamSent; - ServerFileProcessed = serverFileProcessed; + FileStreamSent = fileStreamSent; + ServerFileProcessed = serverFileProcessed; - _maxFileSize = _cloud.AccountInfo.FileSizeLimit > 0 - ? _cloud.AccountInfo.FileSizeLimit - 1024 - : long.MaxValue - 1024; + _maxFileSize = _cloud.AccountInfo.FileSizeLimit > 0 + ? _cloud.AccountInfo.FileSizeLimit - 1024 + : long.MaxValue - 1024; - Initialize(); - } - - private void Initialize() - { - _performAsSplitted = _size > _maxFileSize || _cryptInfo != null; - _origfile = new File(_destinationPath, _size); + Initialize(); + } - if (!_performAsSplitted) // crypted are performed alike splitted file - { - _files.Add(_origfile); - } - else - { - var sinfo = new FilenameServiceInfo - { - CleanName = _origfile.Name, - CryptInfo = _cryptInfo, - SplitInfo = new FileSplitInfo {IsHeader = false} - }; - - - int nfiles = (int) (_size / _maxFileSize + 1); - if (nfiles > 999) - throw new OverflowException("Cannot upload more than 999 file parts"); - - //TODO: move file splitting in File class - for (int i = 1; i <= nfiles; i++) - { - sinfo.SplitInfo.PartNumber = i; - sinfo.CryptInfo = i != nfiles ? null : _cryptInfo; - - var f = new File($"{_origfile.FullPath}{sinfo}", - i != nfiles ? _maxFileSize : _size % _maxFileSize); - _files.Add(f); - } - } + private void Initialize() + { + _performAsSplitted = _size > _maxFileSize || _cryptInfo != null; + _origfile = new File(_destinationPath, _size); - NextFile(); + if (!_performAsSplitted) // crypted are performed alike splitted file + { + _files.Add(_origfile); } - - private void NextFile() + else { - if (_currFileId >= 0) + var sinfo = new FilenameServiceInfo { - var clostream = _uploadStream; - _uploadPendingTask = _uploadPendingTask.ContinueWith(_ => - { - clostream.Dispose(); - }); - } + CleanName = _origfile.Name, + CryptInfo = _cryptInfo, + SplitInfo = new FileSplitInfo { IsHeader = false } + }; - _currFileId++; - if (_currFileId >= _files.Count) - return; - _bytesWrote = 0; - var currFile = _files[_currFileId]; - _uploadStream = new UploadStream(currFile.FullPath, _cloud, currFile.OriginalSize) + int nfiles = (int)(_size / _maxFileSize + 1); + if (nfiles > 999) + throw new OverflowException("Cannot upload more than 999 file parts"); + + //TODO: move file splitting in File class + for (int i = 1; i <= nfiles; i++) { - CheckHashes = _checkHash + sinfo.SplitInfo.PartNumber = i; + sinfo.CryptInfo = i != nfiles ? null : _cryptInfo; - //FileStreamSent = FileStreamSent, - //ServerFileProcessed = ServerFileProcessed - }; + var f = new File($"{_origfile.FullPath}{sinfo}", + i != nfiles ? _maxFileSize : _size % _maxFileSize); + _files.Add(f); + } } - private Task _uploadPendingTask = Task.CompletedTask; - - public readonly Action FileStreamSent; - private void OnFileStreamSent() => FileStreamSent?.Invoke(); - - public readonly Action ServerFileProcessed; - private void OnServerFileProcessed() => ServerFileProcessed?.Invoke(); + NextFile(); + } - public override void Flush() + private void NextFile() + { + if (_currFileId >= 0) { - throw new NotImplementedException(); + var clostream = _uploadStream; + _uploadPendingTask = _uploadPendingTask.ContinueWith(_ => + { + clostream.Dispose(); + }); } - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotImplementedException(); - } + _currFileId++; + if (_currFileId >= _files.Count) + return; - public override void SetLength(long value) + _bytesWrote = 0; + var currFile = _files[_currFileId]; + _uploadStream = new UploadStream(currFile.FullPath, _cloud, currFile.OriginalSize) { - _size = value; - } + CheckHashes = _checkHash - public override int Read(byte[] buffer, int offset, int count) - { - throw new NotImplementedException(); - } + //FileStreamSent = FileStreamSent, + //ServerFileProcessed = ServerFileProcessed + }; + } - public override void Write(byte[] buffer, int offset, int count) - { - long diff = _bytesWrote + count - _files[_currFileId].OriginalSize; + private Task _uploadPendingTask = Task.CompletedTask; - if (diff > 0) - { - var zbuffer = new byte[count - diff]; - Array.Copy(buffer, 0, zbuffer, 0, count - diff); - long zcount = count; + public readonly Action FileStreamSent; + private void OnFileStreamSent() => FileStreamSent?.Invoke(); - _uploadStream.Write(zbuffer, offset, (int) (zcount - diff)); + public readonly Action ServerFileProcessed; + private void OnServerFileProcessed() => ServerFileProcessed?.Invoke(); - NextFile(); - } + public override void Flush() + { + throw new NotImplementedException(); + } - long ncount = diff <= 0 ? count : diff; - var nbuffer = new byte[ncount]; - Array.Copy(buffer, count - ncount, nbuffer, 0, ncount); + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotImplementedException(); + } - _bytesWrote += ncount; + public override void SetLength(long value) + { + _size = value; + } - _uploadStream.Write(nbuffer, offset, (int) ncount); - } + public override int Read(byte[] buffer, int offset, int count) + { + throw new NotImplementedException(); + } - public event FileUploadedDelegate FileUploaded; + public override void Write(byte[] buffer, int offset, int count) + { + long diff = _bytesWrote + count - _files[_currFileId].OriginalSize; - private void OnFileUploaded(IEnumerable files) + if (diff > 0) { - var e = FileUploaded; - e?.Invoke(files); - } + var zbuffer = new byte[count - diff]; + Array.Copy(buffer, 0, zbuffer, 0, count - diff); + long zcount = count; - ~SplittedUploadStream() - { - Dispose(false); + _uploadStream.Write(zbuffer, offset, (int)(zcount - diff)); + + NextFile(); } - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - if (!disposing) return; + long ncount = diff <= 0 ? count : diff; + var nbuffer = new byte[ncount]; + Array.Copy(buffer, count - ncount, nbuffer, 0, ncount); - OnFileStreamSent(); + _bytesWrote += ncount; - var clostream = _uploadStream; - _uploadPendingTask.ContinueWith(_ => - { - clostream?.Dispose(); - }).Wait(); + _uploadStream.Write(nbuffer, offset, (int)ncount); + } - if (_performAsSplitted) - { - var header = new HeaderFileContent - { - CreationDate = DateTime.Now, - Name = _origfile.Name, - Size = _origfile.Size, - PublicKey = _cryptInfo?.PublicKey - }; - _cloud.UploadFileJson(_origfile.FullPath, header, true); - } + public event FileUploadedDelegate FileUploaded; - OnServerFileProcessed(); - OnFileUploaded(_files); - } + private void OnFileUploaded(IEnumerable files) + { + var e = FileUploaded; + e?.Invoke(files); + } + + ~SplittedUploadStream() + { + Dispose(false); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + if (!disposing) return; + + OnFileStreamSent(); - public override bool CanRead => true; - public override bool CanSeek => true; - public override bool CanWrite => true; - public override long Length => _size; - public override long Position { get; set; } + var clostream = _uploadStream; + _uploadPendingTask.ContinueWith(_ => + { + clostream?.Dispose(); + }).Wait(); + if (_performAsSplitted) + { + var header = new HeaderFileContent + { + CreationDate = DateTime.Now, + Name = _origfile.Name, + Size = _origfile.Size, + PublicKey = _cryptInfo?.PublicKey + }; + _cloud.UploadFileJson(_origfile.FullPath, header, true); + } + + OnServerFileProcessed(); + OnFileUploaded(_files); } + + public override bool CanRead => true; + public override bool CanSeek => true; + public override bool CanWrite => true; + public override long Length => _size; + public override long Position { get; set; } } diff --git a/MailRuCloud/MailRuCloudApi/Streams/UploadStreamFabric.cs b/MailRuCloud/MailRuCloudApi/Streams/UploadStreamFabric.cs index c9719902..0518252c 100644 --- a/MailRuCloud/MailRuCloudApi/Streams/UploadStreamFabric.cs +++ b/MailRuCloud/MailRuCloudApi/Streams/UploadStreamFabric.cs @@ -5,85 +5,84 @@ using YaR.Clouds.XTSSharp; using File = YaR.Clouds.Base.File; -namespace YaR.Clouds.Streams +namespace YaR.Clouds.Streams; + +public class UploadStreamFabric { - public class UploadStreamFabric + private readonly Cloud _cloud; + + public UploadStreamFabric(Cloud cloud) + { + _cloud = cloud; + } + + public Action FileStreamSent; + public Action ServerFileProcessed; + + public async Task Create(File file, FileUploadedDelegate onUploaded = null, bool discardEncryption = false) { - private readonly Cloud _cloud; + string folderPath = file.Path; + IEntry entry = await _cloud.GetItemAsync(file.Path, Cloud.ItemType.Folder); + if (entry is null || entry is not Folder folder) + throw new DirectoryNotFoundException(file.Path); + + Stream stream; - public UploadStreamFabric(Cloud cloud) + string fullPath = _cloud.Find(CryptFileInfo.FileName, WebDavPath.GetParents(folder.FullPath).ToArray()); + bool cryptRequired = !string.IsNullOrEmpty(fullPath); + if (cryptRequired && !discardEncryption) { - _cloud = cloud; - } + if (!_cloud.Credentials.CanCrypt) + throw new Exception($"Cannot upload {file.FullPath} to crypt folder without additional password!"); - public Action FileStreamSent; - public Action ServerFileProcessed; + // #142 remove crypted file parts if size changed + await _cloud.Remove(file.FullPath); - public async Task Create(File file, FileUploadedDelegate onUploaded = null, bool discardEncryption = false) + stream = GetCryptoStream(file, onUploaded); + } + else { - string folderPath = file.Path; - IEntry entry = await _cloud.GetItemAsync(file.Path, Cloud.ItemType.Folder); - if(entry is null || entry is not Folder folder) - throw new DirectoryNotFoundException(file.Path); - - Stream stream; - - string fullPath = _cloud.Find(CryptFileInfo.FileName, WebDavPath.GetParents(folder.FullPath).ToArray()); - bool cryptRequired = !string.IsNullOrEmpty(fullPath); - if (cryptRequired && !discardEncryption) - { - if (!_cloud.Credentials.CanCrypt) - throw new Exception($"Cannot upload {file.FullPath} to crypt folder without additional password!"); - - // #142 remove crypted file parts if size changed - await _cloud.Remove(file.FullPath); - - stream = GetCryptoStream(file, onUploaded); - } - else - { - stream = GetPlainStream(file, onUploaded); - } - - return stream; + stream = GetPlainStream(file, onUploaded); } - private Stream GetPlainStream(File file, FileUploadedDelegate onUploaded) + return stream; + } + + private Stream GetPlainStream(File file, FileUploadedDelegate onUploaded) + { + var stream = new SplittedUploadStream(file.FullPath, _cloud, file.Size, FileStreamSent, ServerFileProcessed); + if (onUploaded is not null) + stream.FileUploaded += onUploaded; + return stream; + } + + private Stream GetCryptoStream(File file, FileUploadedDelegate onUploaded) + { + var info = CryptoUtil.GetCryptoKeyAndSalt(_cloud.Credentials.PasswordCrypt); + var xts = XtsAes256.Create(info.Key, info.IV); + + file.ServiceInfo.CryptInfo = new CryptInfo { - var stream = new SplittedUploadStream(file.FullPath, _cloud, file.Size, FileStreamSent, ServerFileProcessed); - if (onUploaded is not null) - stream.FileUploaded += onUploaded; - return stream; - } + PublicKey = new CryptoKeyInfo { Salt = info.Salt, IV = info.IV }, + AlignBytes = (uint)(file.Size % XTSWriteOnlyStream.BlockSize != 0 + ? XTSWriteOnlyStream.BlockSize - file.Size % XTSWriteOnlyStream.BlockSize + : 0) + }; + + var size = file.OriginalSize % XTSWriteOnlyStream.BlockSize == 0 + ? file.OriginalSize.DefaultValue + : (file.OriginalSize / XTSWriteOnlyStream.BlockSize + 1) * XTSWriteOnlyStream.BlockSize; - private Stream GetCryptoStream(File file, FileUploadedDelegate onUploaded) + var ustream = new SplittedUploadStream(file.FullPath, _cloud, size, null, null, false, file.ServiceInfo.CryptInfo); + if (onUploaded is not null) + ustream.FileUploaded += onUploaded; + // ReSharper disable once RedundantArgumentDefaultValue + var encustream = new XTSWriteOnlyStream(ustream, xts, XTSWriteOnlyStream.DefaultSectorSize) { - var info = CryptoUtil.GetCryptoKeyAndSalt(_cloud.Credentials.PasswordCrypt); - var xts = XtsAes256.Create(info.Key, info.IV); - - file.ServiceInfo.CryptInfo = new CryptInfo - { - PublicKey = new CryptoKeyInfo {Salt = info.Salt, IV = info.IV}, - AlignBytes = (uint) (file.Size % XTSWriteOnlyStream.BlockSize != 0 - ? XTSWriteOnlyStream.BlockSize - file.Size % XTSWriteOnlyStream.BlockSize - : 0) - }; - - var size = file.OriginalSize % XTSWriteOnlyStream.BlockSize == 0 - ? file.OriginalSize.DefaultValue - : (file.OriginalSize / XTSWriteOnlyStream.BlockSize + 1) * XTSWriteOnlyStream.BlockSize; - - var ustream = new SplittedUploadStream(file.FullPath, _cloud, size, null, null, false, file.ServiceInfo.CryptInfo); - if (onUploaded is not null) - ustream.FileUploaded += onUploaded; - // ReSharper disable once RedundantArgumentDefaultValue - var encustream = new XTSWriteOnlyStream(ustream, xts, XTSWriteOnlyStream.DefaultSectorSize) - { - FileStreamSent = FileStreamSent, - ServerFileProcessed = ServerFileProcessed - }; - - return encustream; - } + FileStreamSent = FileStreamSent, + ServerFileProcessed = ServerFileProcessed + }; + + return encustream; } } diff --git a/MailRuCloud/MailRuCloudApi/TwoFAHandler.cs b/MailRuCloud/MailRuCloudApi/TwoFAHandler.cs index 23262843..dd7003a7 100644 --- a/MailRuCloud/MailRuCloudApi/TwoFAHandler.cs +++ b/MailRuCloud/MailRuCloudApi/TwoFAHandler.cs @@ -1,7 +1,6 @@ -namespace YaR.Clouds +namespace YaR.Clouds; + +public interface ITwoFaHandler { - public interface ITwoFaHandler - { - string Get(string login, bool isAutoRelogin); - } + string Get(string login, bool isAutoRelogin); } diff --git a/MailRuCloud/MailRuCloudApi/XTSSharp/RandomAccessSectorStream.cs b/MailRuCloud/MailRuCloudApi/XTSSharp/RandomAccessSectorStream.cs index 1f25c90d..73ef35c9 100644 --- a/MailRuCloud/MailRuCloudApi/XTSSharp/RandomAccessSectorStream.cs +++ b/MailRuCloud/MailRuCloudApi/XTSSharp/RandomAccessSectorStream.cs @@ -27,269 +27,268 @@ using System; using System.IO; -namespace YaR.Clouds.XTSSharp +namespace YaR.Clouds.XTSSharp; + +/// +/// A wraps a sector based stream and provides random access to it +/// +public class RandomAccessSectorStream : Stream { - /// - /// A wraps a sector based stream and provides random access to it - /// - public class RandomAccessSectorStream : Stream - { - private readonly byte[] _buffer; - private readonly int _bufferSize; - private readonly SectorStream _s; - private readonly bool _isStreamOwned; - private bool _bufferDirty; - private bool _bufferLoaded; - private int _bufferPos; - - /// - /// Creates a new stream - /// - /// Base stream - /// Does this stream own the base stream? i.e. should it be automatically disposed? - public RandomAccessSectorStream(SectorStream s, bool isStreamOwned = false) - { - _s = s; - _isStreamOwned = isStreamOwned; - _buffer = new byte[s.SectorSize]; - _bufferSize = s.SectorSize; - } - - /// - /// Gets a value indicating whether the current stream supports reading. - /// - /// true if the stream supports reading; otherwise, false. - public override bool CanRead => _s.CanRead; - - /// - /// Gets a value indicating whether the current stream supports seeking. - /// - /// true if the stream supports seeking; otherwise, false. - public override bool CanSeek => _s.CanSeek; - - /// - /// Gets a value indicating whether the current stream supports writing. - /// - /// true if the stream supports writing; otherwise, false. - public override bool CanWrite => _s.CanWrite; - - /// - /// Gets the length in bytes of the stream. - /// - /// A long value representing the length of the stream in bytes. - public override long Length => _s.Length + _bufferPos; - - /// - /// Gets or sets the position within the current stream. - /// - /// The current position within the stream. - public override long Position - { - get => _bufferLoaded - ? _s.Position - _bufferSize + _bufferPos - : _s.Position + _bufferPos; - set - { - if (value < 0L) - throw new ArgumentOutOfRangeException(nameof(value)); - - var sectorPosition = value % _bufferSize; - var position = value - sectorPosition; - - //see if its within the current sector - if (_bufferLoaded) - { - var basePosition = _s.Position - _bufferSize; - if (value > basePosition && value < basePosition + _bufferSize) - { - _bufferPos = (int) sectorPosition; - return; - } - } - //outside the current buffer - - //write it - if (_bufferDirty) - WriteSector(); - - _s.Position = position; - - //read this sector - ReadSector(); - - //bump us forward if need be - _bufferPos = (int) sectorPosition; - } - } - - /// - /// Releases the unmanaged resources used by the and optionally releases the managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected override void Dispose(bool disposing) - { - Flush(); - - base.Dispose(disposing); - - if (_isStreamOwned) - _s.Dispose(); - } - - /// - /// Clears all buffers for this stream and causes any buffered data to be written to the underlying device. - /// - public override void Flush() - { - if (_bufferDirty) - WriteSector(); - } - - /// - /// Sets the position within the current stream. - /// - /// - /// The new position within the current stream. - /// - /// A byte offset relative to the parameter. - /// A value of type indicating the reference point used to obtain the new position. - public override long Seek(long offset, SeekOrigin origin) - { - long newPosition = origin switch + private readonly byte[] _buffer; + private readonly int _bufferSize; + private readonly SectorStream _s; + private readonly bool _isStreamOwned; + private bool _bufferDirty; + private bool _bufferLoaded; + private int _bufferPos; + + /// + /// Creates a new stream + /// + /// Base stream + /// Does this stream own the base stream? i.e. should it be automatically disposed? + public RandomAccessSectorStream(SectorStream s, bool isStreamOwned = false) + { + _s = s; + _isStreamOwned = isStreamOwned; + _buffer = new byte[s.SectorSize]; + _bufferSize = s.SectorSize; + } + + /// + /// Gets a value indicating whether the current stream supports reading. + /// + /// true if the stream supports reading; otherwise, false. + public override bool CanRead => _s.CanRead; + + /// + /// Gets a value indicating whether the current stream supports seeking. + /// + /// true if the stream supports seeking; otherwise, false. + public override bool CanSeek => _s.CanSeek; + + /// + /// Gets a value indicating whether the current stream supports writing. + /// + /// true if the stream supports writing; otherwise, false. + public override bool CanWrite => _s.CanWrite; + + /// + /// Gets the length in bytes of the stream. + /// + /// A long value representing the length of the stream in bytes. + public override long Length => _s.Length + _bufferPos; + + /// + /// Gets or sets the position within the current stream. + /// + /// The current position within the stream. + public override long Position + { + get => _bufferLoaded + ? _s.Position - _bufferSize + _bufferPos + : _s.Position + _bufferPos; + set + { + if (value < 0L) + throw new ArgumentOutOfRangeException(nameof(value)); + + var sectorPosition = value % _bufferSize; + var position = value - sectorPosition; + + //see if its within the current sector + if (_bufferLoaded) { - SeekOrigin.Begin => offset, - SeekOrigin.End => Length - offset, - _ => Position + offset - }; - - Position = newPosition; - - return newPosition; - } - - /// - /// Sets the length of the current stream. - /// - /// The desired length of the current stream in bytes. - public override void SetLength(long value) - { - var remainder = value%_s.SectorSize; - - if (remainder > 0) - value = value - remainder + _bufferSize; - - _s.SetLength(value); - } - - /// - /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. - /// - /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached. - /// An array of bytes. When this method returns, the buffer contains the specified byte array with the values between and ( + - 1) replaced by the bytes read from the current source. - /// The zero-based byte offset in at which to begin storing the data read from the current stream. - /// The maximum number of bytes to be read from the current stream. - public override int Read(byte[] buffer, int offset, int count) - { - var position = Position; - - if (position + count > _s.Length) - { - count = (int) (_s.Length - position); - } - - if (!_bufferLoaded) - ReadSector(); - - var totalBytesRead = 0; - while (count > 0) - { - var bytesToRead = Math.Min(count, _bufferSize - _bufferPos); - - Buffer.BlockCopy(_buffer, _bufferPos, buffer, offset, bytesToRead); - - offset += bytesToRead; - _bufferPos += bytesToRead; - count -= bytesToRead; - - totalBytesRead += bytesToRead; - - if (_bufferPos == _bufferSize) - ReadSector(); - } - - return totalBytesRead; - } - - /// - /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. - /// - /// An array of bytes. This method copies bytes from to the current stream. - /// The zero-based byte offset in at which to begin copying bytes to the current stream. - /// The number of bytes to be written to the current stream. - public override void Write(byte[] buffer, int offset, int count) - { - while (count > 0) - { - if (!_bufferLoaded) - ReadSector(); - - var bytesToWrite = Math.Min(count, _bufferSize - _bufferPos); - - Buffer.BlockCopy(buffer, offset, _buffer, _bufferPos, bytesToWrite); - - offset += bytesToWrite; - _bufferPos += bytesToWrite; - count -= bytesToWrite; - _bufferDirty = true; - - if (_bufferPos == _bufferSize) - WriteSector(); - } - } - - /// - /// Reads a sector - /// - private void ReadSector() - { - if (_bufferLoaded && _bufferDirty) - { - WriteSector(); - } - - if (_s.Position == _s.Length) - { - return; - } - - var bytesRead = _s.Read(_buffer, 0, _buffer.Length); - - //clean the end of it - if (bytesRead != _bufferSize) - Array.Clear(_buffer, bytesRead, _buffer.Length - bytesRead); - - _bufferLoaded = true; - _bufferPos = 0; - _bufferDirty = false; - } - - /// - /// Writes a sector - /// - private void WriteSector() - { - if (_bufferLoaded) - { - //go back to beginning of the current sector - _s.Seek(-_bufferSize, SeekOrigin.Current); - } - - //write it - _s.Write(_buffer, 0, _bufferSize); - _bufferDirty = false; - _bufferLoaded = false; - _bufferPos = 0; - Array.Clear(_buffer, 0, _bufferSize); - } - } -} \ No newline at end of file + var basePosition = _s.Position - _bufferSize; + if (value > basePosition && value < basePosition + _bufferSize) + { + _bufferPos = (int)sectorPosition; + return; + } + } + //outside the current buffer + + //write it + if (_bufferDirty) + WriteSector(); + + _s.Position = position; + + //read this sector + ReadSector(); + + //bump us forward if need be + _bufferPos = (int)sectorPosition; + } + } + + /// + /// Releases the unmanaged resources used by the and optionally releases the managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected override void Dispose(bool disposing) + { + Flush(); + + base.Dispose(disposing); + + if (_isStreamOwned) + _s.Dispose(); + } + + /// + /// Clears all buffers for this stream and causes any buffered data to be written to the underlying device. + /// + public override void Flush() + { + if (_bufferDirty) + WriteSector(); + } + + /// + /// Sets the position within the current stream. + /// + /// + /// The new position within the current stream. + /// + /// A byte offset relative to the parameter. + /// A value of type indicating the reference point used to obtain the new position. + public override long Seek(long offset, SeekOrigin origin) + { + long newPosition = origin switch + { + SeekOrigin.Begin => offset, + SeekOrigin.End => Length - offset, + _ => Position + offset + }; + + Position = newPosition; + + return newPosition; + } + + /// + /// Sets the length of the current stream. + /// + /// The desired length of the current stream in bytes. + public override void SetLength(long value) + { + var remainder = value % _s.SectorSize; + + if (remainder > 0) + value = value - remainder + _bufferSize; + + _s.SetLength(value); + } + + /// + /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. + /// + /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached. + /// An array of bytes. When this method returns, the buffer contains the specified byte array with the values between and ( + - 1) replaced by the bytes read from the current source. + /// The zero-based byte offset in at which to begin storing the data read from the current stream. + /// The maximum number of bytes to be read from the current stream. + public override int Read(byte[] buffer, int offset, int count) + { + var position = Position; + + if (position + count > _s.Length) + { + count = (int)(_s.Length - position); + } + + if (!_bufferLoaded) + ReadSector(); + + var totalBytesRead = 0; + while (count > 0) + { + var bytesToRead = Math.Min(count, _bufferSize - _bufferPos); + + Buffer.BlockCopy(_buffer, _bufferPos, buffer, offset, bytesToRead); + + offset += bytesToRead; + _bufferPos += bytesToRead; + count -= bytesToRead; + + totalBytesRead += bytesToRead; + + if (_bufferPos == _bufferSize) + ReadSector(); + } + + return totalBytesRead; + } + + /// + /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. + /// + /// An array of bytes. This method copies bytes from to the current stream. + /// The zero-based byte offset in at which to begin copying bytes to the current stream. + /// The number of bytes to be written to the current stream. + public override void Write(byte[] buffer, int offset, int count) + { + while (count > 0) + { + if (!_bufferLoaded) + ReadSector(); + + var bytesToWrite = Math.Min(count, _bufferSize - _bufferPos); + + Buffer.BlockCopy(buffer, offset, _buffer, _bufferPos, bytesToWrite); + + offset += bytesToWrite; + _bufferPos += bytesToWrite; + count -= bytesToWrite; + _bufferDirty = true; + + if (_bufferPos == _bufferSize) + WriteSector(); + } + } + + /// + /// Reads a sector + /// + private void ReadSector() + { + if (_bufferLoaded && _bufferDirty) + { + WriteSector(); + } + + if (_s.Position == _s.Length) + { + return; + } + + var bytesRead = _s.Read(_buffer, 0, _buffer.Length); + + //clean the end of it + if (bytesRead != _bufferSize) + Array.Clear(_buffer, bytesRead, _buffer.Length - bytesRead); + + _bufferLoaded = true; + _bufferPos = 0; + _bufferDirty = false; + } + + /// + /// Writes a sector + /// + private void WriteSector() + { + if (_bufferLoaded) + { + //go back to beginning of the current sector + _s.Seek(-_bufferSize, SeekOrigin.Current); + } + + //write it + _s.Write(_buffer, 0, _bufferSize); + _bufferDirty = false; + _bufferLoaded = false; + _bufferPos = 0; + Array.Clear(_buffer, 0, _bufferSize); + } +} diff --git a/MailRuCloud/MailRuCloudApi/XTSSharp/SectorStream.cs b/MailRuCloud/MailRuCloudApi/XTSSharp/SectorStream.cs index 810ab294..ce40a272 100644 --- a/MailRuCloud/MailRuCloudApi/XTSSharp/SectorStream.cs +++ b/MailRuCloud/MailRuCloudApi/XTSSharp/SectorStream.cs @@ -27,176 +27,175 @@ using System; using System.IO; -namespace YaR.Clouds.XTSSharp +namespace YaR.Clouds.XTSSharp; + +/// +/// Sector-based stream +/// +public class SectorStream : Stream { - /// - /// Sector-based stream - /// - public class SectorStream : Stream - { - private readonly Stream _baseStream; - private readonly long _offset; - - /// - /// Creates a new stream - /// - /// The base stream to read/write from - /// The size of the sectors to read/write - /// Offset to start counting sectors - public SectorStream(Stream baseStream, int sectorSize, long offset = 0) - { - SectorSize = sectorSize; - _baseStream = baseStream; - _offset = offset; - } - - /// - /// The size of the sectors - /// - public int SectorSize { get; } - - /// - /// Gets a value indicating whether the current stream supports reading. - /// - /// true if the stream supports reading; otherwise, false. - public override bool CanRead => _baseStream.CanRead; - - /// - /// Gets a value indicating whether the current stream supports seeking. - /// - /// true if the stream supports seeking; otherwise, false. - public override bool CanSeek => _baseStream.CanSeek; - - /// - /// Gets a value indicating whether the current stream supports writing. - /// - /// true if the stream supports writing; otherwise, false. - public override bool CanWrite => _baseStream.CanWrite; - - /// - /// Gets the length in bytes of the stream. - /// - /// A long value representing the length of the stream in bytes. - public override long Length => _baseStream.Length - _offset; - - /// - /// Gets or sets the position within the current stream. - /// - /// The current position within the stream. - public override long Position - { - get => _baseStream.Position - _offset; - set - { - ValidateSizeMultiple(value); - - //base stream gets the non-tweaked value - _baseStream.Position = value + _offset; - CurrentSector = (ulong)(value / SectorSize); - } - } - - /// - /// The current sector this stream is at - /// - protected ulong CurrentSector { get; private set; } - - /// - /// Validates that the size is a multiple of the sector size - /// - private void ValidateSizeMultiple(long value) - { - if (value % SectorSize != 0) - throw new ArgumentException($"Value needs to be a multiple of {SectorSize}"); - } - - /// - /// Validates that the size is equal to the sector size - /// - protected void ValidateSize(long value) - { - if (value != SectorSize) - throw new ArgumentException($"Value needs to be {SectorSize}"); - } - - /// - /// Validates that the size is equal to the sector size - /// - protected void ValidateSize(int value) - { - if (value != SectorSize) - throw new ArgumentException($"Value needs to be {SectorSize}"); - } - - /// - /// Clears all buffers for this stream and causes any buffered data to be written to the underlying device. - /// - public override void Flush() - { - _baseStream.Flush(); - } - - /// - /// Sets the position within the current stream. - /// - /// - /// The new position within the current stream. - /// - /// A byte offset relative to the parameter. - /// A value of type indicating the reference point used to obtain the new position. - public override long Seek(long offset, SeekOrigin origin) - { - long newPosition = origin switch - { - SeekOrigin.Begin => offset, - SeekOrigin.End => Length - offset, - _ => Position + offset - }; - - Position = newPosition; - - return newPosition; - } - - /// - /// Sets the length of the current stream. - /// - /// The desired length of the current stream in bytes. - public override void SetLength(long value) - { - ValidateSizeMultiple(value); - - _baseStream.SetLength(value); - } - - /// - /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. - /// - /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached. - /// An array of bytes. When this method returns, the buffer contains the specified byte array with the values between and ( + - 1) replaced by the bytes read from the current source. - /// The zero-based byte offset in at which to begin storing the data read from the current stream. - /// The maximum number of bytes to be read from the current stream. - public override int Read(byte[] buffer, int offset, int count) - { - ValidateSize(count); - - var ret = _baseStream.Read(buffer, offset, count); - CurrentSector++; - return ret; - } - - /// - /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. - /// - /// An array of bytes. This method copies bytes from to the current stream. - /// The zero-based byte offset in at which to begin copying bytes to the current stream. - /// The number of bytes to be written to the current stream. - public override void Write(byte[] buffer, int offset, int count) - { - ValidateSize(count); - - _baseStream.Write(buffer, offset, count); - CurrentSector++; - } - } -} \ No newline at end of file + private readonly Stream _baseStream; + private readonly long _offset; + + /// + /// Creates a new stream + /// + /// The base stream to read/write from + /// The size of the sectors to read/write + /// Offset to start counting sectors + public SectorStream(Stream baseStream, int sectorSize, long offset = 0) + { + SectorSize = sectorSize; + _baseStream = baseStream; + _offset = offset; + } + + /// + /// The size of the sectors + /// + public int SectorSize { get; } + + /// + /// Gets a value indicating whether the current stream supports reading. + /// + /// true if the stream supports reading; otherwise, false. + public override bool CanRead => _baseStream.CanRead; + + /// + /// Gets a value indicating whether the current stream supports seeking. + /// + /// true if the stream supports seeking; otherwise, false. + public override bool CanSeek => _baseStream.CanSeek; + + /// + /// Gets a value indicating whether the current stream supports writing. + /// + /// true if the stream supports writing; otherwise, false. + public override bool CanWrite => _baseStream.CanWrite; + + /// + /// Gets the length in bytes of the stream. + /// + /// A long value representing the length of the stream in bytes. + public override long Length => _baseStream.Length - _offset; + + /// + /// Gets or sets the position within the current stream. + /// + /// The current position within the stream. + public override long Position + { + get => _baseStream.Position - _offset; + set + { + ValidateSizeMultiple(value); + + //base stream gets the non-tweaked value + _baseStream.Position = value + _offset; + CurrentSector = (ulong)(value / SectorSize); + } + } + + /// + /// The current sector this stream is at + /// + protected ulong CurrentSector { get; private set; } + + /// + /// Validates that the size is a multiple of the sector size + /// + private void ValidateSizeMultiple(long value) + { + if (value % SectorSize != 0) + throw new ArgumentException($"Value needs to be a multiple of {SectorSize}"); + } + + /// + /// Validates that the size is equal to the sector size + /// + protected void ValidateSize(long value) + { + if (value != SectorSize) + throw new ArgumentException($"Value needs to be {SectorSize}"); + } + + /// + /// Validates that the size is equal to the sector size + /// + protected void ValidateSize(int value) + { + if (value != SectorSize) + throw new ArgumentException($"Value needs to be {SectorSize}"); + } + + /// + /// Clears all buffers for this stream and causes any buffered data to be written to the underlying device. + /// + public override void Flush() + { + _baseStream.Flush(); + } + + /// + /// Sets the position within the current stream. + /// + /// + /// The new position within the current stream. + /// + /// A byte offset relative to the parameter. + /// A value of type indicating the reference point used to obtain the new position. + public override long Seek(long offset, SeekOrigin origin) + { + long newPosition = origin switch + { + SeekOrigin.Begin => offset, + SeekOrigin.End => Length - offset, + _ => Position + offset + }; + + Position = newPosition; + + return newPosition; + } + + /// + /// Sets the length of the current stream. + /// + /// The desired length of the current stream in bytes. + public override void SetLength(long value) + { + ValidateSizeMultiple(value); + + _baseStream.SetLength(value); + } + + /// + /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. + /// + /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached. + /// An array of bytes. When this method returns, the buffer contains the specified byte array with the values between and ( + - 1) replaced by the bytes read from the current source. + /// The zero-based byte offset in at which to begin storing the data read from the current stream. + /// The maximum number of bytes to be read from the current stream. + public override int Read(byte[] buffer, int offset, int count) + { + ValidateSize(count); + + var ret = _baseStream.Read(buffer, offset, count); + CurrentSector++; + return ret; + } + + /// + /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. + /// + /// An array of bytes. This method copies bytes from to the current stream. + /// The zero-based byte offset in at which to begin copying bytes to the current stream. + /// The number of bytes to be written to the current stream. + public override void Write(byte[] buffer, int offset, int count) + { + ValidateSize(count); + + _baseStream.Write(buffer, offset, count); + CurrentSector++; + } +} diff --git a/MailRuCloud/MailRuCloudApi/XTSSharp/XTSReadOnlyStream.cs b/MailRuCloud/MailRuCloudApi/XTSSharp/XTSReadOnlyStream.cs index cbd559a2..8ac91fae 100644 --- a/MailRuCloud/MailRuCloudApi/XTSSharp/XTSReadOnlyStream.cs +++ b/MailRuCloud/MailRuCloudApi/XTSSharp/XTSReadOnlyStream.cs @@ -1,125 +1,124 @@ using System; using System.IO; -namespace YaR.Clouds.XTSSharp -{ - class XTSReadOnlyStream : XtsSectorStream - { - private readonly Stream _baseStream; - private readonly XtsCryptoTransform _decryptor; +namespace YaR.Clouds.XTSSharp; - public XTSReadOnlyStream(Stream baseStream, Xts xts, int sectorSize, ulong startSector, int trimStart, uint trimEnd) : base(baseStream, xts, sectorSize) - { - _baseStream = baseStream; - _tempBuffer = new byte[SectorSize]; - _decryptor = xts.CreateDecryptor(); - _currentSector = startSector; - Length = baseStream.Length - trimStart - trimEnd; +class XTSReadOnlyStream : XtsSectorStream +{ + private readonly Stream _baseStream; + private readonly XtsCryptoTransform _decryptor; - if (trimStart <= 0) - return; + public XTSReadOnlyStream(Stream baseStream, Xts xts, int sectorSize, ulong startSector, int trimStart, uint trimEnd) : base(baseStream, xts, sectorSize) + { + _baseStream = baseStream; + _tempBuffer = new byte[SectorSize]; + _decryptor = xts.CreateDecryptor(); + _currentSector = startSector; + Length = baseStream.Length - trimStart - trimEnd; + + if (trimStart <= 0) + return; + + int read = ReadExactly(_baseStream, _tempBuffer, sectorSize); + _decryptor.TransformBlock(_tempBuffer, 0, SectorSize, _tempBuffer, 0, _currentSector); + _tempBufferCount = read; + _tempBufferPos = trimStart; + _currentSector++; + } - int read = ReadExactly(_baseStream, _tempBuffer, sectorSize); - _decryptor.TransformBlock(_tempBuffer, 0, SectorSize, _tempBuffer, 0, _currentSector); - _tempBufferCount = read; - _tempBufferPos = trimStart; - _currentSector++; - } + private long _position; - private long _position; + private readonly byte[] _tempBuffer; + private int _tempBufferPos; + private int _tempBufferCount; - private readonly byte[] _tempBuffer; - private int _tempBufferPos; - private int _tempBufferCount; + private ulong _currentSector; - private ulong _currentSector; + public override int Read(byte[] buffer, int offset, int count) + { + int totalread = 0; - public override int Read(byte[] buffer, int offset, int count) + if (_tempBufferCount > 0) { - int totalread = 0; - - if (_tempBufferCount > 0) + int tbtocopy = Math.Min(count - offset, _tempBufferCount - _tempBufferPos); + if (tbtocopy > 0) { - int tbtocopy = Math.Min(count - offset, _tempBufferCount - _tempBufferPos); - if (tbtocopy > 0) + Array.Copy(_tempBuffer, _tempBufferPos, buffer, offset, tbtocopy); + offset += tbtocopy; + _tempBufferPos += tbtocopy; + totalread += tbtocopy; + _position += tbtocopy; + if (_tempBufferPos == _tempBufferCount) { - Array.Copy(_tempBuffer, _tempBufferPos, buffer, offset, tbtocopy); - offset += tbtocopy; - _tempBufferPos += tbtocopy; - totalread += tbtocopy; - _position += tbtocopy; - if (_tempBufferPos == _tempBufferCount) - { - _tempBufferPos = 0; - _tempBufferCount = 0; - } + _tempBufferPos = 0; + _tempBufferCount = 0; } } + } - while (offset < count && _position < Length) - { - int read = ReadExactly(_baseStream, _tempBuffer, SectorSize); - _decryptor.TransformBlock(_tempBuffer, 0, SectorSize, _tempBuffer, 0, _currentSector); + while (offset < count && _position < Length) + { + int read = ReadExactly(_baseStream, _tempBuffer, SectorSize); + _decryptor.TransformBlock(_tempBuffer, 0, SectorSize, _tempBuffer, 0, _currentSector); - _currentSector++; - _tempBufferCount = read; - _tempBufferPos = 0; + _currentSector++; + _tempBufferCount = read; + _tempBufferPos = 0; - int tocopy = Math.Min(count - offset, read); + int tocopy = Math.Min(count - offset, read); - Array.Copy(_tempBuffer, _tempBufferPos, buffer, offset, tocopy); - _tempBufferPos += tocopy; - offset += tocopy; - totalread += tocopy; - _position += tocopy; + Array.Copy(_tempBuffer, _tempBufferPos, buffer, offset, tocopy); + _tempBufferPos += tocopy; + offset += tocopy; + totalread += tocopy; + _position += tocopy; - if (_tempBufferPos != _tempBufferCount) - continue; + if (_tempBufferPos != _tempBufferCount) + continue; - _tempBufferPos = 0; - _tempBufferCount = 0; - } - - return totalread; + _tempBufferPos = 0; + _tempBufferCount = 0; } + return totalread; + } + - private static int ReadExactly(Stream stream, byte[] buffer, int count) + private static int ReadExactly(Stream stream, byte[] buffer, int count) + { + int offset = 0; + int totalread = 0; + while (offset < count) { - int offset = 0; - int totalread = 0; - while (offset < count) - { - int read = stream.Read(buffer, offset, count - offset); - totalread += read; - if (read == 0) - return totalread; - offset += read; - } - System.Diagnostics.Debug.Assert(offset == count); - return totalread; + int read = stream.Read(buffer, offset, count - offset); + totalread += read; + if (read == 0) + return totalread; + offset += read; } + System.Diagnostics.Debug.Assert(offset == count); + return totalread; + } - public override long Length { get; } + public override long Length { get; } - public override void Write(byte[] buffer, int offset, int count) - { - throw new NotSupportedException(); - } + public override void Write(byte[] buffer, int offset, int count) + { + throw new NotSupportedException(); + } - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotSupportedException(); - } + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotSupportedException(); + } - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - if (!disposing) return; + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + if (!disposing) return; - _decryptor.Dispose(); - } + _decryptor.Dispose(); } -} \ No newline at end of file +} diff --git a/MailRuCloud/MailRuCloudApi/XTSSharp/XTSWriteOnlyStream.cs b/MailRuCloud/MailRuCloudApi/XTSSharp/XTSWriteOnlyStream.cs index 71163f0a..ec2e20e3 100644 --- a/MailRuCloud/MailRuCloudApi/XTSSharp/XTSWriteOnlyStream.cs +++ b/MailRuCloud/MailRuCloudApi/XTSSharp/XTSWriteOnlyStream.cs @@ -1,121 +1,120 @@ using System; using System.IO; -namespace YaR.Clouds.XTSSharp +namespace YaR.Clouds.XTSSharp; + +class XTSWriteOnlyStream : Stream { - class XTSWriteOnlyStream : Stream + public const int DefaultSectorSize = 512; + public const int BlockSize = 16; + + private readonly XtsCryptoTransform _encryptor; + private readonly Stream _baseStream; + + private ulong _currentSector; + private readonly int _sectorSize; + private readonly byte[] _encriptedBuffer; + private readonly byte[] _sectorBuffer; + + /// + /// Creates a new stream + /// + /// The base stream + /// The xts transform + /// Sector size + public XTSWriteOnlyStream(Stream baseStream, Xts xts, int sectorSize = DefaultSectorSize) { - public const int DefaultSectorSize = 512; - public const int BlockSize = 16; - - private readonly XtsCryptoTransform _encryptor; - private readonly Stream _baseStream; - - private ulong _currentSector; - private readonly int _sectorSize; - private readonly byte[] _encriptedBuffer; - private readonly byte[] _sectorBuffer; - - /// - /// Creates a new stream - /// - /// The base stream - /// The xts transform - /// Sector size - public XTSWriteOnlyStream(Stream baseStream, Xts xts, int sectorSize = DefaultSectorSize) - { - _baseStream = baseStream; + _baseStream = baseStream; - _encryptor = xts.CreateEncryptor(); + _encryptor = xts.CreateEncryptor(); - _sectorSize = sectorSize; - _encriptedBuffer = new byte[sectorSize]; - _sectorBuffer = new byte[sectorSize]; - } + _sectorSize = sectorSize; + _encriptedBuffer = new byte[sectorSize]; + _sectorBuffer = new byte[sectorSize]; + } - public Action FileStreamSent; - private void OnFileStreamSent() => FileStreamSent?.Invoke(); + public Action FileStreamSent; + private void OnFileStreamSent() => FileStreamSent?.Invoke(); - public Action ServerFileProcessed; - private void OnServerFileProcessed() => ServerFileProcessed?.Invoke(); + public Action ServerFileProcessed; + private void OnServerFileProcessed() => ServerFileProcessed?.Invoke(); - private int _sectorBufferCount; + private int _sectorBufferCount; - public override void Write(byte[] buffer, int offset, int count) - { - if (count == 0) - return; - - while (count > 0) - { - int bytesToCopy = Math.Min(count, _sectorSize - _sectorBufferCount); - Buffer.BlockCopy(buffer, offset, _sectorBuffer, _sectorBufferCount, bytesToCopy); - _sectorBufferCount += bytesToCopy; - offset += bytesToCopy; - count -= bytesToCopy; - - if (_sectorBufferCount != _sectorSize) - continue; - - //sector filled - _encryptor.TransformBlock(_sectorBuffer, 0, _sectorSize, _encriptedBuffer, 0, _currentSector); - _baseStream.Write(_encriptedBuffer, 0, _sectorSize); - - _currentSector++; - _sectorBufferCount = 0; - } - } + public override void Write(byte[] buffer, int offset, int count) + { + if (count == 0) + return; - protected override void Dispose(bool disposing) + while (count > 0) { - base.Dispose(disposing); - if (!disposing) return; - - if (_sectorBufferCount > 0) - { - int towrite = _sectorBufferCount % BlockSize == 0 - ? _sectorBufferCount - : (_sectorBufferCount / BlockSize + 1) * BlockSize; + int bytesToCopy = Math.Min(count, _sectorSize - _sectorBufferCount); + Buffer.BlockCopy(buffer, offset, _sectorBuffer, _sectorBufferCount, bytesToCopy); + _sectorBufferCount += bytesToCopy; + offset += bytesToCopy; + count -= bytesToCopy; - _encryptor.TransformBlock(_sectorBuffer, 0, towrite, _encriptedBuffer, 0, _currentSector); - _baseStream.Write(_encriptedBuffer, 0, towrite); - - } + if (_sectorBufferCount != _sectorSize) + continue; - OnFileStreamSent(); + //sector filled + _encryptor.TransformBlock(_sectorBuffer, 0, _sectorSize, _encriptedBuffer, 0, _currentSector); + _baseStream.Write(_encriptedBuffer, 0, _sectorSize); - _baseStream.Dispose(); - _encryptor.Dispose(); - - OnServerFileProcessed(); + _currentSector++; + _sectorBufferCount = 0; } + } + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + if (!disposing) return; - public override void Flush() + if (_sectorBufferCount > 0) { - throw new NotImplementedException(); - } + int towrite = _sectorBufferCount % BlockSize == 0 + ? _sectorBufferCount + : (_sectorBufferCount / BlockSize + 1) * BlockSize; - public override int Read(byte[] buffer, int offset, int count) - { - throw new NotImplementedException(); - } + _encryptor.TransformBlock(_sectorBuffer, 0, towrite, _encriptedBuffer, 0, _currentSector); + _baseStream.Write(_encriptedBuffer, 0, towrite); - public override long Seek(long offset, SeekOrigin origin) - { - throw new NotImplementedException(); } - public override void SetLength(long value) - { - throw new NotImplementedException(); - } + OnFileStreamSent(); + + _baseStream.Dispose(); + _encryptor.Dispose(); + + OnServerFileProcessed(); + } + + + public override void Flush() + { + throw new NotImplementedException(); + } - public override bool CanRead => false; - public override bool CanSeek => false; - public override bool CanWrite => true; - public override long Length { get; } - public override long Position { get; set; } + public override int Read(byte[] buffer, int offset, int count) + { + throw new NotImplementedException(); + } + + public override long Seek(long offset, SeekOrigin origin) + { + throw new NotImplementedException(); } + + public override void SetLength(long value) + { + throw new NotImplementedException(); + } + + public override bool CanRead => false; + public override bool CanSeek => false; + public override bool CanWrite => true; + public override long Length { get; } + public override long Position { get; set; } } diff --git a/MailRuCloud/MailRuCloudApi/XTSSharp/Xts.cs b/MailRuCloud/MailRuCloudApi/XTSSharp/Xts.cs index ccd52d96..e65134d6 100644 --- a/MailRuCloud/MailRuCloudApi/XTSSharp/Xts.cs +++ b/MailRuCloud/MailRuCloudApi/XTSSharp/Xts.cs @@ -27,97 +27,96 @@ using System; using System.Security.Cryptography; -namespace YaR.Clouds.XTSSharp +namespace YaR.Clouds.XTSSharp; + +/// +/// Xts. See and . +/// +public class Xts : IDisposable { - /// - /// Xts. See and . - /// - public class Xts :IDisposable - { - private readonly SymmetricAlgorithm _key1; - private readonly SymmetricAlgorithm _key2; - - /// - /// Creates a new Xts implementation. - /// - /// Function to create the implementations - /// Key 1 - /// Key 2 - protected Xts(Func create, byte[] key1, byte[] key2) - { - if (create == null) - throw new ArgumentNullException(nameof(create)); - if (key1 == null) - throw new ArgumentNullException(nameof(key1)); - if (key2 == null) - throw new ArgumentNullException(nameof(key2)); - - _key1 = create(); - _key2 = create(); - - if (key1.Length != key2.Length) - throw new ArgumentException("Key lengths don't match"); - - //set the key sizes - _key1.KeySize = key1.Length*8; - _key2.KeySize = key2.Length*8; - - //set the keys - _key1.Key = key1; - _key2.Key = key2; - - //ecb mode - _key1.Mode = CipherMode.ECB; - _key2.Mode = CipherMode.ECB; - - //no padding - we're always going to be writing full blocks - _key1.Padding = PaddingMode.None; - _key2.Padding = PaddingMode.None; - - //fixed block size of 128 bits. - _key1.BlockSize = 16*8; - _key2.BlockSize = 16*8; - } - - /// - /// Creates an xts encryptor - /// - public XtsCryptoTransform CreateEncryptor() - { - return new XtsCryptoTransform(_key1.CreateEncryptor(), _key2.CreateEncryptor(), false); - } - - /// - /// Creates an xts decryptor - /// - public XtsCryptoTransform CreateDecryptor() - { - return new XtsCryptoTransform(_key1.CreateDecryptor(), _key2.CreateEncryptor(), true); - } - - /// - /// Verify that the key is of an expected size of bits - /// - /// Expected size of the key in bits - /// The key - /// The key - /// If the key is null - /// If the key length does not match the expected length - protected static byte[] VerifyKey(int expectedSize, byte[] key) - { - if (key == null) - throw new ArgumentNullException(nameof(key)); - - if (key.Length*8 != expectedSize) - throw new ArgumentException($"Expected key length of {expectedSize} bits, got {key.Length * 8}"); - - return key; - } - - public void Dispose() - { - _key1?.Dispose(); - _key2?.Dispose(); - } - } -} \ No newline at end of file + private readonly SymmetricAlgorithm _key1; + private readonly SymmetricAlgorithm _key2; + + /// + /// Creates a new Xts implementation. + /// + /// Function to create the implementations + /// Key 1 + /// Key 2 + protected Xts(Func create, byte[] key1, byte[] key2) + { + if (create == null) + throw new ArgumentNullException(nameof(create)); + if (key1 == null) + throw new ArgumentNullException(nameof(key1)); + if (key2 == null) + throw new ArgumentNullException(nameof(key2)); + + _key1 = create(); + _key2 = create(); + + if (key1.Length != key2.Length) + throw new ArgumentException("Key lengths don't match"); + + //set the key sizes + _key1.KeySize = key1.Length * 8; + _key2.KeySize = key2.Length * 8; + + //set the keys + _key1.Key = key1; + _key2.Key = key2; + + //ecb mode + _key1.Mode = CipherMode.ECB; + _key2.Mode = CipherMode.ECB; + + //no padding - we're always going to be writing full blocks + _key1.Padding = PaddingMode.None; + _key2.Padding = PaddingMode.None; + + //fixed block size of 128 bits. + _key1.BlockSize = 16 * 8; + _key2.BlockSize = 16 * 8; + } + + /// + /// Creates an xts encryptor + /// + public XtsCryptoTransform CreateEncryptor() + { + return new XtsCryptoTransform(_key1.CreateEncryptor(), _key2.CreateEncryptor(), false); + } + + /// + /// Creates an xts decryptor + /// + public XtsCryptoTransform CreateDecryptor() + { + return new XtsCryptoTransform(_key1.CreateDecryptor(), _key2.CreateEncryptor(), true); + } + + /// + /// Verify that the key is of an expected size of bits + /// + /// Expected size of the key in bits + /// The key + /// The key + /// If the key is null + /// If the key length does not match the expected length + protected static byte[] VerifyKey(int expectedSize, byte[] key) + { + if (key == null) + throw new ArgumentNullException(nameof(key)); + + if (key.Length * 8 != expectedSize) + throw new ArgumentException($"Expected key length of {expectedSize} bits, got {key.Length * 8}"); + + return key; + } + + public void Dispose() + { + _key1?.Dispose(); + _key2?.Dispose(); + } +} diff --git a/MailRuCloud/MailRuCloudApi/XTSSharp/XtsAes128.cs b/MailRuCloud/MailRuCloudApi/XTSSharp/XtsAes128.cs index 5529344e..a0540ce6 100644 --- a/MailRuCloud/MailRuCloudApi/XTSSharp/XtsAes128.cs +++ b/MailRuCloud/MailRuCloudApi/XTSSharp/XtsAes128.cs @@ -27,56 +27,55 @@ using System; using System.Security.Cryptography; -namespace YaR.Clouds.XTSSharp +namespace YaR.Clouds.XTSSharp; + +/// +/// XTS-AES-128 implementation +/// +public class XtsAes128 : Xts { - /// - /// XTS-AES-128 implementation - /// - public class XtsAes128 : Xts - { - private const int KeyLength = 128; - private const int KeyByteLength = KeyLength/8; + private const int KeyLength = 128; + private const int KeyByteLength = KeyLength / 8; - /// - /// Creates a new instance - /// - protected XtsAes128(Func create, byte[] key1, byte[] key2) - : base(create, VerifyKey(KeyLength, key1), VerifyKey(KeyLength, key2)) - { - } + /// + /// Creates a new instance + /// + protected XtsAes128(Func create, byte[] key1, byte[] key2) + : base(create, VerifyKey(KeyLength, key1), VerifyKey(KeyLength, key2)) + { + } - /// - /// Creates a new implementation - /// - /// First key - /// Second key - /// Xts implementation - /// Keys need to be 128 bits long (i.e. 16 bytes) - public static Xts Create(byte[] key1, byte[] key2) - { - VerifyKey(KeyLength, key1); - VerifyKey(KeyLength, key2); + /// + /// Creates a new implementation + /// + /// First key + /// Second key + /// Xts implementation + /// Keys need to be 128 bits long (i.e. 16 bytes) + public static Xts Create(byte[] key1, byte[] key2) + { + VerifyKey(KeyLength, key1); + VerifyKey(KeyLength, key2); - return new XtsAes128(Aes.Create, key1, key2); - } + return new XtsAes128(Aes.Create, key1, key2); + } - /// - /// Creates a new implementation - /// - /// Key to use - /// Xts implementation - /// Key need to be 256 bits long (i.e. 32 bytes) - public static Xts Create(byte[] key) - { - VerifyKey(KeyLength*2, key); + /// + /// Creates a new implementation + /// + /// Key to use + /// Xts implementation + /// Key need to be 256 bits long (i.e. 32 bytes) + public static Xts Create(byte[] key) + { + VerifyKey(KeyLength * 2, key); - byte[] key1 = new byte[KeyByteLength]; - byte[] key2 = new byte[KeyByteLength]; + byte[] key1 = new byte[KeyByteLength]; + byte[] key2 = new byte[KeyByteLength]; - Buffer.BlockCopy(key, 0, key1, 0, KeyByteLength); - Buffer.BlockCopy(key, KeyByteLength, key2, 0, KeyByteLength); + Buffer.BlockCopy(key, 0, key1, 0, KeyByteLength); + Buffer.BlockCopy(key, KeyByteLength, key2, 0, KeyByteLength); - return new XtsAes128(Aes.Create, key1, key2); - } - } -} \ No newline at end of file + return new XtsAes128(Aes.Create, key1, key2); + } +} diff --git a/MailRuCloud/MailRuCloudApi/XTSSharp/XtsAes256.cs b/MailRuCloud/MailRuCloudApi/XTSSharp/XtsAes256.cs index 0b4cf2f5..7a8a79f4 100644 --- a/MailRuCloud/MailRuCloudApi/XTSSharp/XtsAes256.cs +++ b/MailRuCloud/MailRuCloudApi/XTSSharp/XtsAes256.cs @@ -27,56 +27,55 @@ using System; using System.Security.Cryptography; -namespace YaR.Clouds.XTSSharp +namespace YaR.Clouds.XTSSharp; + +/// +/// XTS-AES-256 implementation +/// +public class XtsAes256 : Xts { - /// - /// XTS-AES-256 implementation - /// - public class XtsAes256 : Xts - { - private const int KeyLength = 256; - private const int KeyByteLength = KeyLength/8; + private const int KeyLength = 256; + private const int KeyByteLength = KeyLength / 8; - /// - /// Creates a new instance - /// - protected XtsAes256(Func create, byte[] key1, byte[] key2) - : base(create, VerifyKey(KeyLength, key1), VerifyKey(KeyLength, key2)) - { - } + /// + /// Creates a new instance + /// + protected XtsAes256(Func create, byte[] key1, byte[] key2) + : base(create, VerifyKey(KeyLength, key1), VerifyKey(KeyLength, key2)) + { + } - /// - /// Creates a new implementation - /// - /// First key - /// Second key - /// Xts implementation - /// Keys need to be 256 bits long (i.e. 32 bytes) - public static Xts Create(byte[] key1, byte[] key2) - { - VerifyKey(KeyLength, key1); - VerifyKey(KeyLength, key2); + /// + /// Creates a new implementation + /// + /// First key + /// Second key + /// Xts implementation + /// Keys need to be 256 bits long (i.e. 32 bytes) + public static Xts Create(byte[] key1, byte[] key2) + { + VerifyKey(KeyLength, key1); + VerifyKey(KeyLength, key2); - return new XtsAes256(Aes.Create, key1, key2); - } + return new XtsAes256(Aes.Create, key1, key2); + } - /// - /// Creates a new implementation - /// - /// Key to use - /// Xts implementation - /// Keys need to be 512 bits long (i.e. 64 bytes) - public static Xts Create(byte[] key) - { - VerifyKey(KeyLength*2, key); + /// + /// Creates a new implementation + /// + /// Key to use + /// Xts implementation + /// Keys need to be 512 bits long (i.e. 64 bytes) + public static Xts Create(byte[] key) + { + VerifyKey(KeyLength * 2, key); - var key1 = new byte[KeyByteLength]; - var key2 = new byte[KeyByteLength]; + var key1 = new byte[KeyByteLength]; + var key2 = new byte[KeyByteLength]; - Buffer.BlockCopy(key, 0, key1, 0, KeyByteLength); - Buffer.BlockCopy(key, KeyByteLength, key2, 0, KeyByteLength); + Buffer.BlockCopy(key, 0, key1, 0, KeyByteLength); + Buffer.BlockCopy(key, KeyByteLength, key2, 0, KeyByteLength); - return new XtsAes256(Aes.Create, key1, key2); - } - } -} \ No newline at end of file + return new XtsAes256(Aes.Create, key1, key2); + } +} diff --git a/MailRuCloud/MailRuCloudApi/XTSSharp/XtsCryptoTransform.cs b/MailRuCloud/MailRuCloudApi/XTSSharp/XtsCryptoTransform.cs index 2ba328ce..adfe8294 100644 --- a/MailRuCloud/MailRuCloudApi/XTSSharp/XtsCryptoTransform.cs +++ b/MailRuCloud/MailRuCloudApi/XTSSharp/XtsCryptoTransform.cs @@ -27,198 +27,197 @@ using System; using System.Security.Cryptography; -namespace YaR.Clouds.XTSSharp +namespace YaR.Clouds.XTSSharp; + +/// +/// The actual Xts cryptography transform +/// +/// +/// The reason that it doesn't implement ICryptoTransform, as the interface is different. +/// +/// Most of the logic was taken from the LibTomCrypt project - http://libtom.org and +/// converted to C# +/// +public class XtsCryptoTransform : IDisposable { - /// - /// The actual Xts cryptography transform - /// - /// - /// The reason that it doesn't implement ICryptoTransform, as the interface is different. - /// - /// Most of the logic was taken from the LibTomCrypt project - http://libtom.org and - /// converted to C# - /// - public class XtsCryptoTransform : IDisposable - { - private readonly byte[] _cc = new byte[16]; - private readonly bool _decrypting; - private readonly ICryptoTransform _key1; - private readonly ICryptoTransform _key2; - - private readonly byte[] _pp = new byte[16]; - private readonly byte[] _t = new byte[16]; - private readonly byte[] _tweak = new byte[16]; - - /// - /// Creates a new transform - /// - /// Transform 1 - /// Transform 2 - /// Is this a decryption transform? - public XtsCryptoTransform(ICryptoTransform key1, ICryptoTransform key2, bool decrypting) - { - _key1 = key1 ?? throw new ArgumentNullException(nameof(key1)); - _key2 = key2 ?? throw new ArgumentNullException(nameof(key2)); - _decrypting = decrypting; - } - - #region IDisposable Members - - /// - /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. - /// - /// 2 - public void Dispose() - { - _key1.Dispose(); - _key2.Dispose(); - } - - #endregion - - /// - /// Transforms a single block. - /// - /// The input for which to compute the transform. - /// The offset into the input byte array from which to begin using data. - /// The number of bytes in the input byte array to use as data. - /// The output to which to write the transform. - /// The offset into the output byte array from which to begin writing data. - /// The sector number of the block - /// The number of bytes written. - public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset, ulong sector) - { - FillArrayFromSector(_tweak, sector); - - int lim; - - /* get number of blocks */ - var m = inputCount >> 4; - var mo = inputCount & 15; - - /* encrypt the tweak */ - _key2.TransformBlock(_tweak, 0, _tweak.Length, _t, 0); - - /* for i = 0 to m-2 do */ - if (mo == 0) - lim = m; - else - lim = m - 1; - - for (var i = 0; i < lim; i++) - { - TweakCrypt(inputBuffer, inputOffset, outputBuffer, outputOffset, _t); - inputOffset += 16; - outputOffset += 16; - } - - /* if ptlen not divide 16 then */ - if (mo > 0) - { - if (_decrypting) - { - Buffer.BlockCopy(_t, 0, _cc, 0, 16); - MultiplyByX(_cc); - - /* CC = tweak encrypt block m-1 */ - TweakCrypt(inputBuffer, inputOffset, _pp, 0, _cc); - - /* Cm = first ptlen % 16 bytes of CC */ - int i; - for (i = 0; i < mo; i++) - { - _cc[i] = inputBuffer[16 + i + inputOffset]; - outputBuffer[16 + i + outputOffset] = _pp[i]; - } - - for (; i < 16; i++) - { - _cc[i] = _pp[i]; - } - - /* Cm-1 = Tweak encrypt PP */ - TweakCrypt(_cc, 0, outputBuffer, outputOffset, _t); - } - else - { - /* CC = tweak encrypt block m-1 */ - TweakCrypt(inputBuffer, inputOffset, _cc, 0, _t); - - /* Cm = first ptlen % 16 bytes of CC */ - int i; - for (i = 0; i < mo; i++) - { - _pp[i] = inputBuffer[16 + i + inputOffset]; - outputBuffer[16 + i + outputOffset] = _cc[i]; - } - - for (; i < 16; i++) - { - _pp[i] = _cc[i]; - } - - /* Cm-1 = Tweak encrypt PP */ - TweakCrypt(_pp, 0, outputBuffer, outputOffset, _t); - } - } - - return inputCount; - } - - /// - /// Fills a byte array from a sector number - /// - /// The destination - /// The sector number - private static void FillArrayFromSector(byte[] value, ulong sector) - { - value[7] = (byte) ((sector >> 56) & 255); - value[6] = (byte) ((sector >> 48) & 255); - value[5] = (byte) ((sector >> 40) & 255); - value[4] = (byte) ((sector >> 32) & 255); - value[3] = (byte) ((sector >> 24) & 255); - value[2] = (byte) ((sector >> 16) & 255); - value[1] = (byte) ((sector >> 8) & 255); - value[0] = (byte) (sector & 255); - } - - /// - /// Performs the Xts TweakCrypt operation - /// - private void TweakCrypt(byte[] inputBuffer, int inputOffset, byte[] outputBuffer, int outputOffset, byte[] t) - { - for (var x = 0; x < 16; x++) - { - outputBuffer[x + outputOffset] = (byte) (inputBuffer[x + inputOffset] ^ t[x]); - } - - _key1.TransformBlock(outputBuffer, outputOffset, 16, outputBuffer, outputOffset); - - for (var x = 0; x < 16; x++) - { - outputBuffer[x + outputOffset] = (byte) (outputBuffer[x + outputOffset] ^ t[x]); - } - - MultiplyByX(t); - } - - /// - /// Multiply by x - /// - /// The value to multiply by x (LFSR shift) - private static void MultiplyByX(byte[] i) - { - byte t = 0, tt = 0; - - for (var x = 0; x < 16; x++) - { - tt = (byte) (i[x] >> 7); - i[x] = (byte) (((i[x] << 1) | t) & 0xFF); - t = tt; - } - - if (tt > 0) - i[0] ^= 0x87; - } - } -} \ No newline at end of file + private readonly byte[] _cc = new byte[16]; + private readonly bool _decrypting; + private readonly ICryptoTransform _key1; + private readonly ICryptoTransform _key2; + + private readonly byte[] _pp = new byte[16]; + private readonly byte[] _t = new byte[16]; + private readonly byte[] _tweak = new byte[16]; + + /// + /// Creates a new transform + /// + /// Transform 1 + /// Transform 2 + /// Is this a decryption transform? + public XtsCryptoTransform(ICryptoTransform key1, ICryptoTransform key2, bool decrypting) + { + _key1 = key1 ?? throw new ArgumentNullException(nameof(key1)); + _key2 = key2 ?? throw new ArgumentNullException(nameof(key2)); + _decrypting = decrypting; + } + + #region IDisposable Members + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + /// 2 + public void Dispose() + { + _key1.Dispose(); + _key2.Dispose(); + } + + #endregion + + /// + /// Transforms a single block. + /// + /// The input for which to compute the transform. + /// The offset into the input byte array from which to begin using data. + /// The number of bytes in the input byte array to use as data. + /// The output to which to write the transform. + /// The offset into the output byte array from which to begin writing data. + /// The sector number of the block + /// The number of bytes written. + public int TransformBlock(byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset, ulong sector) + { + FillArrayFromSector(_tweak, sector); + + int lim; + + /* get number of blocks */ + var m = inputCount >> 4; + var mo = inputCount & 15; + + /* encrypt the tweak */ + _key2.TransformBlock(_tweak, 0, _tweak.Length, _t, 0); + + /* for i = 0 to m-2 do */ + if (mo == 0) + lim = m; + else + lim = m - 1; + + for (var i = 0; i < lim; i++) + { + TweakCrypt(inputBuffer, inputOffset, outputBuffer, outputOffset, _t); + inputOffset += 16; + outputOffset += 16; + } + + /* if ptlen not divide 16 then */ + if (mo > 0) + { + if (_decrypting) + { + Buffer.BlockCopy(_t, 0, _cc, 0, 16); + MultiplyByX(_cc); + + /* CC = tweak encrypt block m-1 */ + TweakCrypt(inputBuffer, inputOffset, _pp, 0, _cc); + + /* Cm = first ptlen % 16 bytes of CC */ + int i; + for (i = 0; i < mo; i++) + { + _cc[i] = inputBuffer[16 + i + inputOffset]; + outputBuffer[16 + i + outputOffset] = _pp[i]; + } + + for (; i < 16; i++) + { + _cc[i] = _pp[i]; + } + + /* Cm-1 = Tweak encrypt PP */ + TweakCrypt(_cc, 0, outputBuffer, outputOffset, _t); + } + else + { + /* CC = tweak encrypt block m-1 */ + TweakCrypt(inputBuffer, inputOffset, _cc, 0, _t); + + /* Cm = first ptlen % 16 bytes of CC */ + int i; + for (i = 0; i < mo; i++) + { + _pp[i] = inputBuffer[16 + i + inputOffset]; + outputBuffer[16 + i + outputOffset] = _cc[i]; + } + + for (; i < 16; i++) + { + _pp[i] = _cc[i]; + } + + /* Cm-1 = Tweak encrypt PP */ + TweakCrypt(_pp, 0, outputBuffer, outputOffset, _t); + } + } + + return inputCount; + } + + /// + /// Fills a byte array from a sector number + /// + /// The destination + /// The sector number + private static void FillArrayFromSector(byte[] value, ulong sector) + { + value[7] = (byte)((sector >> 56) & 255); + value[6] = (byte)((sector >> 48) & 255); + value[5] = (byte)((sector >> 40) & 255); + value[4] = (byte)((sector >> 32) & 255); + value[3] = (byte)((sector >> 24) & 255); + value[2] = (byte)((sector >> 16) & 255); + value[1] = (byte)((sector >> 8) & 255); + value[0] = (byte)(sector & 255); + } + + /// + /// Performs the Xts TweakCrypt operation + /// + private void TweakCrypt(byte[] inputBuffer, int inputOffset, byte[] outputBuffer, int outputOffset, byte[] t) + { + for (var x = 0; x < 16; x++) + { + outputBuffer[x + outputOffset] = (byte)(inputBuffer[x + inputOffset] ^ t[x]); + } + + _key1.TransformBlock(outputBuffer, outputOffset, 16, outputBuffer, outputOffset); + + for (var x = 0; x < 16; x++) + { + outputBuffer[x + outputOffset] = (byte)(outputBuffer[x + outputOffset] ^ t[x]); + } + + MultiplyByX(t); + } + + /// + /// Multiply by x + /// + /// The value to multiply by x (LFSR shift) + private static void MultiplyByX(byte[] i) + { + byte t = 0, tt = 0; + + for (var x = 0; x < 16; x++) + { + tt = (byte)(i[x] >> 7); + i[x] = (byte)(((i[x] << 1) | t) & 0xFF); + t = tt; + } + + if (tt > 0) + i[0] ^= 0x87; + } +} diff --git a/MailRuCloud/MailRuCloudApi/XTSSharp/XtsSectorStream.cs b/MailRuCloud/MailRuCloudApi/XTSSharp/XtsSectorStream.cs index 8e61b297..0dd9a3bf 100644 --- a/MailRuCloud/MailRuCloudApi/XTSSharp/XtsSectorStream.cs +++ b/MailRuCloud/MailRuCloudApi/XTSSharp/XtsSectorStream.cs @@ -26,115 +26,114 @@ using System.IO; -namespace YaR.Clouds.XTSSharp +namespace YaR.Clouds.XTSSharp; + +/// +/// Xts sector-based +/// +public class XtsSectorStream : SectorStream { - /// - /// Xts sector-based - /// - public class XtsSectorStream : SectorStream - { - /// - /// The default sector size - /// - public const int DefaultSectorSize = 512; - - private readonly byte[] _tempBuffer; - private readonly Xts _xts; - private XtsCryptoTransform _decryptor; - private XtsCryptoTransform _encryptor; - - /// - /// Creates a new stream - /// - /// The base stream - /// The xts transform - /// Sector size - public XtsSectorStream(Stream baseStream, Xts xts, int sectorSize = DefaultSectorSize) - : this(baseStream, xts, sectorSize, 0) - { - } - - /// - /// Creates a new stream - /// - /// The base stream - /// The xts transform - /// Sector size - /// Offset to start counting sectors - public XtsSectorStream(Stream baseStream, Xts xts, int sectorSize, long offset) - : base(baseStream, sectorSize, offset) - { - _xts = xts; - _tempBuffer = new byte[sectorSize]; - } - - /// - /// Releases the unmanaged resources used by the and optionally releases the managed resources. - /// - /// true to release both managed and unmanaged resources; false to release only unmanaged resources. - protected override void Dispose(bool disposing) - { - base.Dispose(disposing); - - _encryptor?.Dispose(); - _decryptor?.Dispose(); - } - - /// - /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. - /// - /// An array of bytes. This method copies bytes from to the current stream. - /// The zero-based byte offset in at which to begin copying bytes to the current stream. - /// The number of bytes to be written to the current stream. - public override void Write(byte[] buffer, int offset, int count) - { - ValidateSize(count); - - if (count == 0) - return; - - //get the current sector - var currentSector = CurrentSector; - - _encryptor ??= _xts.CreateEncryptor(); - - //encrypt the sector - int transformedCount = _encryptor.TransformBlock(buffer, offset, count, _tempBuffer, 0, currentSector); - - //Console.WriteLine("Encrypting sector {0}", currentSector); - - //write it to the base stream - base.Write(_tempBuffer, 0, transformedCount); - } - - /// - /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. - /// - /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached. - /// An array of bytes. When this method returns, the buffer contains the specified byte array with the values between and ( + - 1) replaced by the bytes read from the current source. - /// The zero-based byte offset in at which to begin storing the data read from the current stream. - /// The maximum number of bytes to be read from the current stream. - public override int Read(byte[] buffer, int offset, int count) - { - ValidateSize(count); - - //get the current sector - var currentSector = CurrentSector; - - //read the sector from the base stream - var ret = base.Read(_tempBuffer, 0, count); - - if (ret == 0) - return 0; - - _decryptor ??= _xts.CreateDecryptor(); - - //decrypt the sector - var retV = _decryptor.TransformBlock(_tempBuffer, 0, ret, buffer, offset, currentSector); - - //Console.WriteLine("Decrypting sector {0} == {1} bytes", currentSector, retV); - - return retV; - } - } -} \ No newline at end of file + /// + /// The default sector size + /// + public const int DefaultSectorSize = 512; + + private readonly byte[] _tempBuffer; + private readonly Xts _xts; + private XtsCryptoTransform _decryptor; + private XtsCryptoTransform _encryptor; + + /// + /// Creates a new stream + /// + /// The base stream + /// The xts transform + /// Sector size + public XtsSectorStream(Stream baseStream, Xts xts, int sectorSize = DefaultSectorSize) + : this(baseStream, xts, sectorSize, 0) + { + } + + /// + /// Creates a new stream + /// + /// The base stream + /// The xts transform + /// Sector size + /// Offset to start counting sectors + public XtsSectorStream(Stream baseStream, Xts xts, int sectorSize, long offset) + : base(baseStream, sectorSize, offset) + { + _xts = xts; + _tempBuffer = new byte[sectorSize]; + } + + /// + /// Releases the unmanaged resources used by the and optionally releases the managed resources. + /// + /// true to release both managed and unmanaged resources; false to release only unmanaged resources. + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + _encryptor?.Dispose(); + _decryptor?.Dispose(); + } + + /// + /// Writes a sequence of bytes to the current stream and advances the current position within this stream by the number of bytes written. + /// + /// An array of bytes. This method copies bytes from to the current stream. + /// The zero-based byte offset in at which to begin copying bytes to the current stream. + /// The number of bytes to be written to the current stream. + public override void Write(byte[] buffer, int offset, int count) + { + ValidateSize(count); + + if (count == 0) + return; + + //get the current sector + var currentSector = CurrentSector; + + _encryptor ??= _xts.CreateEncryptor(); + + //encrypt the sector + int transformedCount = _encryptor.TransformBlock(buffer, offset, count, _tempBuffer, 0, currentSector); + + //Console.WriteLine("Encrypting sector {0}", currentSector); + + //write it to the base stream + base.Write(_tempBuffer, 0, transformedCount); + } + + /// + /// Reads a sequence of bytes from the current stream and advances the position within the stream by the number of bytes read. + /// + /// The total number of bytes read into the buffer. This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached. + /// An array of bytes. When this method returns, the buffer contains the specified byte array with the values between and ( + - 1) replaced by the bytes read from the current source. + /// The zero-based byte offset in at which to begin storing the data read from the current stream. + /// The maximum number of bytes to be read from the current stream. + public override int Read(byte[] buffer, int offset, int count) + { + ValidateSize(count); + + //get the current sector + var currentSector = CurrentSector; + + //read the sector from the base stream + var ret = base.Read(_tempBuffer, 0, count); + + if (ret == 0) + return 0; + + _decryptor ??= _xts.CreateDecryptor(); + + //decrypt the sector + var retV = _decryptor.TransformBlock(_tempBuffer, 0, ret, buffer, offset, currentSector); + + //Console.WriteLine("Decrypting sector {0} == {1} bytes", currentSector, retV); + + return retV; + } +} diff --git a/MailRuCloud/MailRuCloudApi/XTSSharp/XtsStream.cs b/MailRuCloud/MailRuCloudApi/XTSSharp/XtsStream.cs index 7879c494..66e981fc 100644 --- a/MailRuCloud/MailRuCloudApi/XTSSharp/XtsStream.cs +++ b/MailRuCloud/MailRuCloudApi/XTSSharp/XtsStream.cs @@ -26,35 +26,34 @@ using System.IO; -namespace YaR.Clouds.XTSSharp +namespace YaR.Clouds.XTSSharp; + +/// +/// A random access, xts encrypted stream +/// +public class XtsStream : RandomAccessSectorStream { - /// - /// A random access, xts encrypted stream - /// - public class XtsStream : RandomAccessSectorStream - { - /// - /// Creates a new stream - /// - /// The base stream - /// Xts implementation to use - /// Sector size - public XtsStream(Stream baseStream, Xts xts, int sectorSize = XtsSectorStream.DefaultSectorSize) - : base(new XtsSectorStream(baseStream, xts, sectorSize), true) - { - } - - - /// - /// Creates a new stream - /// - /// The base stream - /// Xts implementation to use - /// Sector size - /// Offset to start counting sectors - public XtsStream(Stream baseStream, Xts xts, int sectorSize, long offset) - : base(new XtsSectorStream(baseStream, xts, sectorSize, offset), true) - { - } - } -} \ No newline at end of file + /// + /// Creates a new stream + /// + /// The base stream + /// Xts implementation to use + /// Sector size + public XtsStream(Stream baseStream, Xts xts, int sectorSize = XtsSectorStream.DefaultSectorSize) + : base(new XtsSectorStream(baseStream, xts, sectorSize), true) + { + } + + + /// + /// Creates a new stream + /// + /// The base stream + /// Xts implementation to use + /// Sector size + /// Offset to start counting sectors + public XtsStream(Stream baseStream, Xts xts, int sectorSize, long offset) + : base(new XtsSectorStream(baseStream, xts, sectorSize, offset), true) + { + } +} From ec5373518739183b2ed074710b8771108fff6593 Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Mon, 4 Nov 2024 16:35:15 +0300 Subject: [PATCH 73/77] =?UTF-8?q?=D0=97=D0=B0=D0=BC=D0=B5=D0=BD=D0=B0=20?= =?UTF-8?q?=D1=80=D0=B0=D0=B7=D1=80=D0=B5=D1=88=D0=B5=D0=BD=D0=BD=D1=8B?= =?UTF-8?q?=D1=85=20=D0=B2=20Ext4,=20=D0=BD=D0=BE=20=D0=B7=D0=B0=D0=BF?= =?UTF-8?q?=D1=80=D0=B5=D1=89=D0=B5=D0=BD=D0=BD=D1=8B=D1=85=20=D0=B2=20?= =?UTF-8?q?=D0=BE=D0=B1=D0=BB=D0=B0=D0=BA=D0=B5=20=D0=B2=D0=B8=D0=BC=D0=B2?= =?UTF-8?q?=D0=BE=D0=BB=D0=BE=D0=B2:=20*=20->=20=E2=80=A2=20(\u2022),=20:?= =?UTF-8?q?=20->=20=E2=81=9E=20(\u205e),=20<=20->=20=C2=AB=20(\u00ab),=20>?= =?UTF-8?q?=20->=20=C2=BB=20(\u00bb),=20=3F->=20=E2=80=BD=20(\u203d),=20|?= =?UTF-8?q?=20->=20=E2=94=82=20(\u2502),=20/=20->=20~,=20\=20->=20~.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../MailRuCloud/WebBin/WebBinRequestRepo.cs | 34 ------------- MailRuCloud/MailRuCloudApi/Cloud.cs | 49 ++++++++++++++++--- 2 files changed, 42 insertions(+), 41 deletions(-) diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebBin/WebBinRequestRepo.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebBin/WebBinRequestRepo.cs index 81825031..2a9552bf 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebBin/WebBinRequestRepo.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/MailRuCloud/WebBin/WebBinRequestRepo.cs @@ -385,12 +385,6 @@ public void CleanTrash() public async Task CreateFolder(string path) { - /* - * В названии папок нельзя использовать символы «" * / : < > ? \ |». - * Также название не может состоять только из точки «.» или из двух точек «..» - */ - path = StripBadSymbols(path); - //return (await new CreateFolderRequest(HttpSettings, Authenticator, path).MakeRequestAsync()) // .ToCreateFolderResult(); @@ -400,12 +394,6 @@ public async Task CreateFolder(string path) public async Task AddFile(string fileFullPath, IFileHash fileHash, FileSize fileSize, DateTime dateTime, ConflictResolver? conflictResolver) { - /* - * В названии папок нельзя использовать символы «" * / : < > ? \ |». - * Также название не может состоять только из точки «.» или из двух точек «..» - */ - fileFullPath = StripBadSymbols(fileFullPath); - //var res = await new CreateFileRequest(Proxy, Authenticator, fileFullPath, fileHash, fileSize, conflictResolver) // .MakeRequestAsync(_connectionLimiter); //return res.ToAddFileResult(); @@ -420,27 +408,5 @@ public async Task AddFile(string fileFullPath, IFileHash fileHash return res; } - public string StripBadSymbols(string fullPath) - { - /* - * В названии папок нельзя использовать символы «" * / : < > ? \ |». - * Также название не может состоять только из точки «.» или из двух точек «..» - */ - string name = WebDavPath.Name(fullPath); - string newName = name - .Replace("*", "\u2022") // • - .Replace(":", "\u205e") // ⁞ - .Replace("<", "\u00ab") // « - .Replace(">", "\u00bb") // » - .Replace("?", "\u203d") // ‽ - .Replace("|", "\u2502") // │ - .Replace("/", "~") - .Replace("\\", "~") - ; - return name != newName - ? WebDavPath.Combine(WebDavPath.Parent(fullPath), newName) - : name; - } - public async Task DetectOutsideChanges() => await Task.FromResult(null); } diff --git a/MailRuCloud/MailRuCloudApi/Cloud.cs b/MailRuCloud/MailRuCloudApi/Cloud.cs index 571552c2..74743312 100644 --- a/MailRuCloud/MailRuCloudApi/Cloud.cs +++ b/MailRuCloud/MailRuCloudApi/Cloud.cs @@ -1247,7 +1247,19 @@ public void AbortAllAsyncThreads() CancelToken.Cancel(false); } - /// + + public bool CreateFolder(string name, string basePath) + { + /* + * В названии папок нельзя использовать символы «" * / : < > ? \ |». + * Также название не может состоять только из точки «.» или из двух точек «..» + */ + name = ReplaceBadSymbols(name); + + return CreateFolderAsync(name, basePath).Result; + } + + /// /// Create folder on the server. /// /// New path name. @@ -1258,12 +1270,7 @@ public async Task CreateFolderAsync(string name, string basePath) return await CreateFolderAsync(WebDavPath.Combine(basePath, name)); } - public bool CreateFolder(string name, string basePath) - { - return CreateFolderAsync(name, basePath).Result; - } - - public async Task CreateFolderAsync(string fullPath) + public async Task CreateFolderAsync(string fullPath) { try { @@ -1475,6 +1482,15 @@ public async void RemoveDeadLinks() public async Task AddFile(IFileHash hash, string fullFilePath, long size, ConflictResolver? conflict = null) { + /* + * В названии папок нельзя использовать символы «" * / : < > ? \ |». + * Также название не может состоять только из точки «.» или из двух точек «..» + */ + string name = WebDavPath.Name(fullFilePath); + string newName = ReplaceBadSymbols(name); + if (newName != name) + fullFilePath = WebDavPath.Combine(WebDavPath.Parent(fullFilePath), newName); + try { DateTime timestampBeforeOperation = DateTime.Now; @@ -1559,4 +1575,23 @@ public void CleanTrash() { RequestRepo.CleanTrash(); } + + public string ReplaceBadSymbols(string name) + { + /* + * В названии папок нельзя использовать символы «" * / : < > ? \ |». + * Также название не может состоять только из точки «.» или из двух точек «..» + */ + string newName = name + .Replace("*", "\u2022") // • + .Replace(":", "\u205e") // ⁞ + .Replace("<", "\u00ab") // « + .Replace(">", "\u00bb") // » + .Replace("?", "\u203d") // ‽ + .Replace("|", "\u2502") // │ + .Replace("/", "~") + .Replace("\\", "~") + ; + return newName; + } } From d4e9511512d028df5bf91873788cc43d1086cd0c Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Mon, 4 Nov 2024 16:36:15 +0300 Subject: [PATCH 74/77] =?UTF-8?q?=D0=92=D0=B5=D1=80=D1=81=D0=B8=D1=8F=20->?= =?UTF-8?q?=201.24.11.04?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 7z release.cmd | 2 +- Common.targets | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/7z release.cmd b/7z release.cmd index 06269276..9e0a76e7 100644 --- a/7z release.cmd +++ b/7z release.cmd @@ -1,4 +1,4 @@ -set ver=1.24.10.31 +set ver=1.24.11.04 set options=-tzip -mx9 -r -sse -x!*.pdb -x!*dev* "C:\Program Files\7-Zip\7z.exe" a %options% "BrowserAuthenticator\bin\Release\BrowserAuthenticator-%ver%-net7.0-windows.zip" ".\BrowserAuthenticator\bin\Release\net7.0-windows\*" diff --git a/Common.targets b/Common.targets index 0f0bd04c..606b7a3b 100644 --- a/Common.targets +++ b/Common.targets @@ -2,6 +2,6 @@ net48;net5.0;net6.0;net7.0;net7.0-windows;net8.0;net8.0-windows latest - 1.24.10.31 + 1.24.11.04 \ No newline at end of file From 49e1ac6e84f17370789cae9be8b72e777c4b48ba Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Wed, 18 Dec 2024 22:02:45 +0300 Subject: [PATCH 75/77] =?UTF-8?q?JournalCounters:=20=D0=9F=D0=BE=D1=81?= =?UTF-8?q?=D0=BB=D0=B5=20=D0=BE=D0=B1=D0=BD=D0=BE=D0=B2=D0=BB=D0=B5=D0=BD?= =?UTF-8?q?=D0=B8=D1=8F=20API=20=D0=BF=D0=B5=D1=80=D0=B5=D1=81=D1=82=D0=B0?= =?UTF-8?q?=D0=BB=D0=B8=20=D1=80=D0=B0=D0=B1=D0=BE=D1=82=D0=B0=D1=82=D1=8C?= =?UTF-8?q?=20=D1=81=D1=87=D0=B5=D1=82=D1=87=D0=B8=D0=BA=D0=B8=20-=20?= =?UTF-8?q?=D0=B8=D1=81=D0=BF=D1=80=D0=B0=D0=B2=D0=BB=D0=B5=D0=BD=D0=BE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Base/Repos/YandexDisk/YadWeb/Models/JournalCounters.cs | 2 +- .../Base/Repos/YandexDisk/YadWeb/YadWebRequestRepo2.cs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/JournalCounters.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/JournalCounters.cs index d654eae2..c5dc0dc4 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/JournalCounters.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/Models/JournalCounters.cs @@ -7,7 +7,7 @@ internal class JournalCountersV2 : YadModelV2 { public JournalCountersV2() { - APIMethod = "cloud/virtual-disk-journal-counters"; + APIMethod = "intapi/journal-counters"; ResultType = typeof(YadJournalCountersV2); RequestParameter = new YadRequestV2JournalCounters() { diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebRequestRepo2.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebRequestRepo2.cs index bc5af63c..6057f812 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebRequestRepo2.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/YandexDisk/YadWeb/YadWebRequestRepo2.cs @@ -18,6 +18,7 @@ // Yandex has API version 378.1.0 // Yandex has API version 382.2.0 // Yandex has API version 383.0.0 +// Yandex has API version 389.3.0 namespace YaR.Clouds.Base.Repos.YandexDisk.YadWeb { From 8f1ee1e561ade54032c8ffb92912e53920cac86f Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Wed, 18 Dec 2024 23:02:22 +0300 Subject: [PATCH 76/77] =?UTF-8?q?=D0=92=D0=B5=D1=80=D1=81=D0=B8=D1=8F=201.?= =?UTF-8?q?24.12.18?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 7z release.cmd | 2 +- Common.targets | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/7z release.cmd b/7z release.cmd index 9e0a76e7..11fd2b59 100644 --- a/7z release.cmd +++ b/7z release.cmd @@ -1,4 +1,4 @@ -set ver=1.24.11.04 +set ver=1.24.12.18 set options=-tzip -mx9 -r -sse -x!*.pdb -x!*dev* "C:\Program Files\7-Zip\7z.exe" a %options% "BrowserAuthenticator\bin\Release\BrowserAuthenticator-%ver%-net7.0-windows.zip" ".\BrowserAuthenticator\bin\Release\net7.0-windows\*" diff --git a/Common.targets b/Common.targets index 606b7a3b..0e2c46c8 100644 --- a/Common.targets +++ b/Common.targets @@ -2,6 +2,6 @@ net48;net5.0;net6.0;net7.0;net7.0-windows;net8.0;net8.0-windows latest - 1.24.11.04 + 1.24.12.18 \ No newline at end of file From 5299d8e2ae9b86c6c48943f6afaa7a9174bdbba6 Mon Sep 17 00:00:00 2001 From: ZZZConsulting Date: Wed, 18 Dec 2024 23:05:03 +0300 Subject: [PATCH 77/77] =?UTF-8?q?=D0=9A=D0=BE=D1=80=D1=80=D0=B5=D0=BA?= =?UTF-8?q?=D1=82=D0=B8=D1=80=D0=BE=D0=B2=D0=BA=D0=B0=20=D0=B4=D0=BB=D1=8F?= =?UTF-8?q?=20=D0=B7=D0=B0=D1=88=D0=B8=D1=84=D1=80=D0=BE=D0=B2=D0=B0=D0=BD?= =?UTF-8?q?=D0=BD=D1=8B=D1=85=20=D1=84=D0=B0=D0=B9=D0=BB=D0=BE=D0=B2,=20?= =?UTF-8?q?=D1=87=D1=82=D0=BE=D0=B1=D1=8B=20=D0=BD=D0=B5=20=D0=BF=D0=BE?= =?UTF-8?q?=D0=BA=D0=B0=D0=B7=D1=8B=D0=B2=D0=B0=D0=BB=D0=B8=D1=81=D1=8C=20?= =?UTF-8?q?=D1=82=D0=B5=D1=85=D0=BD=D0=B8=D1=87=D0=B5=D1=81=D0=BA=D0=B8?= =?UTF-8?q?=D0=B5=20=D1=84=D0=B0=D0=B9=D0=BB=D1=8B=20=D1=81=20=D1=80=D0=B0?= =?UTF-8?q?=D1=81=D1=88=D0=B8=D1=80=D0=B5=D0=BD=D0=B8=D1=8F=D0=BC=D0=B8=20?= =?UTF-8?q?=D0=B2=D0=B8=D0=B4=D0=B0=20.wdmrc.001c?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MailRuCloud/MailRuCloudApi/Base/File.cs | 11 ++++++++++- MailRuCloud/MailRuCloudApi/Base/Folder.cs | 9 +++++++++ .../MailRuCloudApi/Base/Repos/DtoImportExtensions.cs | 7 +++---- MailRuCloud/MailRuCloudApi/Common/EntryCache.cs | 6 +++++- .../MailRuCloudApi/Streams/UploadStreamFabric.cs | 4 +++- 5 files changed, 30 insertions(+), 7 deletions(-) diff --git a/MailRuCloud/MailRuCloudApi/Base/File.cs b/MailRuCloud/MailRuCloudApi/Base/File.cs index 902e1a98..1a65a06a 100644 --- a/MailRuCloud/MailRuCloudApi/Base/File.cs +++ b/MailRuCloud/MailRuCloudApi/Base/File.cs @@ -26,6 +26,14 @@ public File(string fullPath, long size, IFileHash hash = null) _originalSize = size; _hash = hash; + + //if (Name?.StartsWith(".") ?? false) + //{ + // if (Attributes.HasFlag(FileAttributes.Normal)) + // Attributes = FileAttributes.Hidden; + // else + // Attributes |= FileAttributes.Hidden; + //} } public File(string fullPath, long size, params PublicLinkInfo[] links) @@ -175,7 +183,8 @@ public IEnumerable GetPublicLinks(Cloud cloud) public void SetName(string destinationName) { FullPath = WebDavPath.Combine(Path, destinationName); - if (ServiceInfo != null) ServiceInfo.CleanName = Name; + if (ServiceInfo != null) + ServiceInfo.CleanName = Name; if (Files.Count <= 1) return; diff --git a/MailRuCloud/MailRuCloudApi/Base/Folder.cs b/MailRuCloud/MailRuCloudApi/Base/Folder.cs index 295e5b7b..9e3e1727 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Folder.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Folder.cs @@ -21,6 +21,14 @@ public Folder(string fullPath) { FullPath = WebDavPath.Clean(fullPath); Name = WebDavPath.Name(FullPath); + + //if (Name?.StartsWith(".") ?? false) + //{ + // if (Attributes.HasFlag(FileAttributes.Normal)) + // Attributes = FileAttributes.Hidden; + // else + // Attributes |= FileAttributes.Hidden; + //} } /// @@ -33,6 +41,7 @@ public Folder(FileSize size, string fullPath, IEnumerable public : this(fullPath) { Size = size; + if (publicLinks != null) { foreach (var link in publicLinks) diff --git a/MailRuCloud/MailRuCloudApi/Base/Repos/DtoImportExtensions.cs b/MailRuCloud/MailRuCloudApi/Base/Repos/DtoImportExtensions.cs index 0db137c5..d7c7f025 100644 --- a/MailRuCloud/MailRuCloudApi/Base/Repos/DtoImportExtensions.cs +++ b/MailRuCloud/MailRuCloudApi/Base/Repos/DtoImportExtensions.cs @@ -44,10 +44,9 @@ internal static File ToFile(this FsFile data) internal static IEnumerable ToGroupedFiles(this IEnumerable list) { var groupedFiles = list - .GroupBy(f => f.ServiceInfo.CleanName, - file => file) - //TODO: DIRTY: if group contains header file, than make SplittedFile, else do not group - .SelectMany(group => group.Count() == 1 + .GroupBy(f => f.ServiceInfo.CleanName, file => file) + //TODO: DIRTY: if group contains header file, than make SplittedFile, else do not group + .SelectMany(group => group.Count() == 1 ? group.Take(1) : group.Any(f => f.Name == f.ServiceInfo.CleanName) ? Enumerable.Repeat(new SplittedFile(group.ToList()), 1) diff --git a/MailRuCloud/MailRuCloudApi/Common/EntryCache.cs b/MailRuCloud/MailRuCloudApi/Common/EntryCache.cs index 3b1f9eaf..d7d163ca 100644 --- a/MailRuCloud/MailRuCloudApi/Common/EntryCache.cs +++ b/MailRuCloud/MailRuCloudApi/Common/EntryCache.cs @@ -807,7 +807,11 @@ public void OnCreate(DateTime operationStartTimestamp, // Добавить новый if (createdEntry is File file) - AddInternal(file, DateTime.Now); + { + // Проверка чтобы не добавлять в кэш файлы вида test.csv.wdmrc.001b, создаваемые в зашифрованных папках + if (file.ServiceInfo.CleanName == file.Name) + AddInternal(file, DateTime.Now); + } else if (createdEntry is Folder folder) { diff --git a/MailRuCloud/MailRuCloudApi/Streams/UploadStreamFabric.cs b/MailRuCloud/MailRuCloudApi/Streams/UploadStreamFabric.cs index 0518252c..b1039ceb 100644 --- a/MailRuCloud/MailRuCloudApi/Streams/UploadStreamFabric.cs +++ b/MailRuCloud/MailRuCloudApi/Streams/UploadStreamFabric.cs @@ -36,7 +36,9 @@ public async Task Create(File file, FileUploadedDelegate onUploaded = nu throw new Exception($"Cannot upload {file.FullPath} to crypt folder without additional password!"); // #142 remove crypted file parts if size changed - await _cloud.Remove(file.FullPath); + IEntry checkExistance = await _cloud.GetItemAsync(file.FullPath, Cloud.ItemType.File); + if(checkExistance is not null) + await _cloud.Remove(file.FullPath); stream = GetCryptoStream(file, onUploaded); }