From 0da096d765da350d956a3059e442171d72ca4a7b Mon Sep 17 00:00:00 2001 From: j4587698 Date: Sun, 5 Feb 2017 16:11:10 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=B0=E5=BB=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BaiduYunSync.sln | 28 + BaiduYunSync/BaiduYunSync.csproj | 134 ++++ BaiduYunSync/Encrypt3DS.cs | 75 ++ BaiduYunSync/LiteDbHelper.cs | 210 ++++++ BaiduYunSync/LogSettings.cs | 49 ++ BaiduYunSync/Program.cs | 21 + BaiduYunSync/Properties/AssemblyInfo.cs | 36 + BaiduYunSync/Properties/Resources.Designer.cs | 71 ++ BaiduYunSync/Properties/Resources.resx | 117 +++ BaiduYunSync/Properties/Settings.Designer.cs | 30 + BaiduYunSync/Properties/Settings.settings | 7 + BaiduYunSync/Sync.cs | 294 ++++++++ BaiduYunSync/SyncItemsDao.cs | 16 + BaiduYunSync/UpdateInfoDao.cs | 61 ++ BaiduYunSync/UserInfoDao.cs | 31 + BaiduYunSync/frmLogin.Designer.cs | 153 ++++ BaiduYunSync/frmLogin.cs | 91 +++ BaiduYunSync/frmLogin.resx | 120 ++++ BaiduYunSync/frmMain.Designer.cs | 179 +++++ BaiduYunSync/frmMain.cs | 174 +++++ BaiduYunSync/frmMain.resx | 126 ++++ BaiduYunSync/frmReadLog.Designer.cs | 63 ++ BaiduYunSync/frmReadLog.cs | 35 + BaiduYunSync/frmReadLog.resx | 120 ++++ BaiduYunSync/frmUploadList.Designer.cs | 140 ++++ BaiduYunSync/frmUploadList.cs | 173 +++++ BaiduYunSync/frmUploadList.resx | 123 ++++ BaiduYunSync/packages.config | 5 + NetDisk/Authentication.cs | 104 +++ NetDisk/CRC32.cs | 119 ++++ NetDisk/CookieAwareWebClient.cs | 54 ++ NetDisk/Credential.cs | 74 ++ NetDisk/Extension.cs | 28 + NetDisk/NetDisk.csproj | 79 ++ NetDisk/Operation.cs | 674 ++++++++++++++++++ NetDisk/PatientWebClient.cs | 22 + NetDisk/Properties/AssemblyInfo.cs | 36 + NetDisk/Result.cs | 155 ++++ NetDisk/UploadHelper.cs | 70 ++ NetDisk/packages.config | 4 + 40 files changed, 4101 insertions(+) create mode 100644 BaiduYunSync.sln create mode 100644 BaiduYunSync/BaiduYunSync.csproj create mode 100644 BaiduYunSync/Encrypt3DS.cs create mode 100644 BaiduYunSync/LiteDbHelper.cs create mode 100644 BaiduYunSync/LogSettings.cs create mode 100644 BaiduYunSync/Program.cs create mode 100644 BaiduYunSync/Properties/AssemblyInfo.cs create mode 100644 BaiduYunSync/Properties/Resources.Designer.cs create mode 100644 BaiduYunSync/Properties/Resources.resx create mode 100644 BaiduYunSync/Properties/Settings.Designer.cs create mode 100644 BaiduYunSync/Properties/Settings.settings create mode 100644 BaiduYunSync/Sync.cs create mode 100644 BaiduYunSync/SyncItemsDao.cs create mode 100644 BaiduYunSync/UpdateInfoDao.cs create mode 100644 BaiduYunSync/UserInfoDao.cs create mode 100644 BaiduYunSync/frmLogin.Designer.cs create mode 100644 BaiduYunSync/frmLogin.cs create mode 100644 BaiduYunSync/frmLogin.resx create mode 100644 BaiduYunSync/frmMain.Designer.cs create mode 100644 BaiduYunSync/frmMain.cs create mode 100644 BaiduYunSync/frmMain.resx create mode 100644 BaiduYunSync/frmReadLog.Designer.cs create mode 100644 BaiduYunSync/frmReadLog.cs create mode 100644 BaiduYunSync/frmReadLog.resx create mode 100644 BaiduYunSync/frmUploadList.Designer.cs create mode 100644 BaiduYunSync/frmUploadList.cs create mode 100644 BaiduYunSync/frmUploadList.resx create mode 100644 BaiduYunSync/packages.config create mode 100644 NetDisk/Authentication.cs create mode 100644 NetDisk/CRC32.cs create mode 100644 NetDisk/CookieAwareWebClient.cs create mode 100644 NetDisk/Credential.cs create mode 100644 NetDisk/Extension.cs create mode 100644 NetDisk/NetDisk.csproj create mode 100644 NetDisk/Operation.cs create mode 100644 NetDisk/PatientWebClient.cs create mode 100644 NetDisk/Properties/AssemblyInfo.cs create mode 100644 NetDisk/Result.cs create mode 100644 NetDisk/UploadHelper.cs create mode 100644 NetDisk/packages.config diff --git a/BaiduYunSync.sln b/BaiduYunSync.sln new file mode 100644 index 0000000..d36c34b --- /dev/null +++ b/BaiduYunSync.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.21005.1 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BaiduYunSync", "BaiduYunSync\BaiduYunSync.csproj", "{F063180E-E354-4DB4-96F8-154D1D949240}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "NetDisk", "NetDisk\NetDisk.csproj", "{74ECA1BF-6508-417D-BC57-DB00DAECA618}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {F063180E-E354-4DB4-96F8-154D1D949240}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F063180E-E354-4DB4-96F8-154D1D949240}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F063180E-E354-4DB4-96F8-154D1D949240}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F063180E-E354-4DB4-96F8-154D1D949240}.Release|Any CPU.Build.0 = Release|Any CPU + {74ECA1BF-6508-417D-BC57-DB00DAECA618}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {74ECA1BF-6508-417D-BC57-DB00DAECA618}.Debug|Any CPU.Build.0 = Debug|Any CPU + {74ECA1BF-6508-417D-BC57-DB00DAECA618}.Release|Any CPU.ActiveCfg = Release|Any CPU + {74ECA1BF-6508-417D-BC57-DB00DAECA618}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/BaiduYunSync/BaiduYunSync.csproj b/BaiduYunSync/BaiduYunSync.csproj new file mode 100644 index 0000000..24d70bc --- /dev/null +++ b/BaiduYunSync/BaiduYunSync.csproj @@ -0,0 +1,134 @@ + + + + + Debug + AnyCPU + {F063180E-E354-4DB4-96F8-154D1D949240} + WinExe + Properties + BaiduYunSync + BaiduYunSync + v4.0 + 512 + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + ..\packages\LiteDB.2.0.4\lib\net35\LiteDB.dll + True + + + ..\packages\log4net.2.0.7\lib\net40-full\log4net.dll + True + + + + + + + + + + + + + + + + Form + + + frmLogin.cs + + + Form + + + frmMain.cs + + + Form + + + frmReadLog.cs + + + Form + + + frmUploadList.cs + + + + + + + + + + + frmLogin.cs + + + frmMain.cs + + + frmReadLog.cs + + + frmUploadList.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + Designer + + + True + Resources.resx + + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + True + Settings.settings + True + + + + + {74ECA1BF-6508-417D-BC57-DB00DAECA618} + NetDisk + + + + + \ No newline at end of file diff --git a/BaiduYunSync/Encrypt3DS.cs b/BaiduYunSync/Encrypt3DS.cs new file mode 100644 index 0000000..0dfd8b2 --- /dev/null +++ b/BaiduYunSync/Encrypt3DS.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.Cryptography; +using System.Text; + +namespace BaiduYunSync +{ + class Encrypt3DS + { + //密钥 + private const string sKey = "qJzGEh6hESZDVJeCnFPGuxzaiB7NLQM3"; + //矢量,矢量可以为空 + private const string sIV = "qcDY6X+aPLw="; + //构造一个对称算法 + private SymmetricAlgorithm mCSP = new TripleDESCryptoServiceProvider(); + + #region public string EncryptString(string Value) + /// + /// 加密字符串 + /// + /// 输入的字符串 + /// 加密后的字符串 + public string EncryptString(string Value) + { + ICryptoTransform ct; + MemoryStream ms; + CryptoStream cs; + byte[] byt; + mCSP.Key = Convert.FromBase64String(sKey); + mCSP.IV = Convert.FromBase64String(sIV); + //指定加密的运算模式 + mCSP.Mode = System.Security.Cryptography.CipherMode.ECB; + //获取或设置加密算法的填充模式 + mCSP.Padding = System.Security.Cryptography.PaddingMode.PKCS7; + ct = mCSP.CreateEncryptor(mCSP.Key, mCSP.IV); + byt = Encoding.UTF8.GetBytes(Value); + ms = new MemoryStream(); + cs = new CryptoStream(ms, ct, CryptoStreamMode.Write); + cs.Write(byt, 0, byt.Length); + cs.FlushFinalBlock(); + cs.Close(); + return Convert.ToBase64String(ms.ToArray()); + } + #endregion + #region public string DecryptString(string Value) + /// + /// 解密字符串 + /// + /// 加过密的字符串 + /// 解密后的字符串 + public string DecryptString(string Value) + { + ICryptoTransform ct; + MemoryStream ms; + CryptoStream cs; + byte[] byt; + mCSP.Key = Convert.FromBase64String(sKey); + mCSP.IV = Convert.FromBase64String(sIV); + mCSP.Mode = System.Security.Cryptography.CipherMode.ECB; + mCSP.Padding = System.Security.Cryptography.PaddingMode.PKCS7; + ct = mCSP.CreateDecryptor(mCSP.Key, mCSP.IV); + byt = Convert.FromBase64String(Value); + ms = new MemoryStream(); + cs = new CryptoStream(ms, ct, CryptoStreamMode.Write); + cs.Write(byt, 0, byt.Length); + cs.FlushFinalBlock(); + cs.Close(); + return Encoding.UTF8.GetString(ms.ToArray()); + } + #endregion + + } +} diff --git a/BaiduYunSync/LiteDbHelper.cs b/BaiduYunSync/LiteDbHelper.cs new file mode 100644 index 0000000..7e3534a --- /dev/null +++ b/BaiduYunSync/LiteDbHelper.cs @@ -0,0 +1,210 @@ +using LiteDB; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace BaiduYunSync +{ + class LiteDbHelper + { + + private static string dbUrl = "db.db"; + + /// + /// 本地保存更新信息,防止频繁读写文件 + /// + private static List updateInfoList; + + /// + /// 同步文件锁 + /// + private static object updateInfoLocker = new object(); + + static LiteDbHelper() + { + Init(); + } + + /// + /// 初始化数据库相关内容 + /// + public static void Init() + { + if (updateInfoList == null) + { + using (var db = new LiteDatabase(dbUrl)) + { + var col = db.GetCollection("updateinfo"); + var list = col.FindAll(); + updateInfoList = (list == null ? new List() : list.ToList()); + } + } + } + + private static UserInfoDao userInfo; + + /// + /// 获取或设置用户信息 + /// + public static UserInfoDao UserInfo + { + get + { + if (userInfo == null) + { + using (var db = new LiteDatabase(dbUrl)) + { + var col = db.GetCollection("userinfo"); + userInfo = col.FindOne(Query.All()); + if (userInfo == null) + { + userInfo = new UserInfoDao(); + } + } + } + return userInfo; + } + set + { + using (var db = new LiteDatabase(dbUrl)) + { + var col = db.GetCollection("userinfo"); + if (value.Id == 0) + { + col.Insert(value); + } + else + { + col.Update(value); + } + userInfo = value; + } + } + } + + /// + /// 插入或更新需要同步的文件夹 + /// + /// + public static void UpdateSyncItem(SyncItemsDao dao) + { + using (var db = new LiteDatabase(dbUrl)) + { + var col = db.GetCollection("syncitem"); + if (dao.Id == 0) + { + col.Insert(dao); + } + else + { + col.Update(dao); + } + } + } + + /// + /// 获取需要同步的文件夹列表 + /// + /// 同步文件夹信息 + public static List GetAllSyncItems() + { + using (var db = new LiteDatabase(dbUrl)) + { + var col = db.GetCollection("syncitem"); + return col.FindAll().ToList(); + } + } + + /// + /// 删除同步文件夹 + /// + /// + public static void DeleteSyncItem(SyncItemsDao dao) + { + using (var db = new LiteDatabase(dbUrl)) + { + var col = db.GetCollection("syncitem"); + col.Delete(dao.Id); + } + } + + /// + /// 获取所有的更新信息 + /// + /// + public static UpdateInfoDao[] GetAllUpdateInfos() + { + lock (updateInfoLocker) + { + return updateInfoList.ToArray(); + } + } + + /// + /// 插入新的更新信息 + /// + /// + public static void InsertUpdateInfo(UpdateInfoDao dao) + { + using (var db = new LiteDatabase(dbUrl)) + { + var col = db.GetCollection("updateinfo"); + col.Insert(dao); + } + lock (updateInfoLocker) + updateInfoList.Add(dao); + } + + /// + /// 删除更新信息 + /// + /// + public static void DeleteUpdateInfo(UpdateInfoDao dao) + { + using (var db = new LiteDatabase(dbUrl)) + { + var col = db.GetCollection("updateinfo"); + col.Delete(dao.Id); + } + lock (updateInfoLocker) + updateInfoList.Remove(dao); + } + + /// + /// 获取需更新数量 + /// + /// 数量 + public static int GetUpdateInfoCount() + { + lock (updateInfoLocker) + return updateInfoList.Count(); + } + + /// + /// 判断文件是否已存在 + /// + /// 文件全路径 + /// + public static bool ContainsPath(string path) + { + lock (updateInfoLocker) + return updateInfoList.Select(x => x.Path).Contains(path); + } + + /// + /// 根据路径获取对应的更新信息 + /// + /// + /// + public static UpdateInfoDao GetUpdateInfoFormPath(string path) + { + if (!ContainsPath(path)) + { + return null; + } + lock (updateInfoLocker) + return updateInfoList.Where(x => x.Path == path).First(); + } + } +} diff --git a/BaiduYunSync/LogSettings.cs b/BaiduYunSync/LogSettings.cs new file mode 100644 index 0000000..9412a7d --- /dev/null +++ b/BaiduYunSync/LogSettings.cs @@ -0,0 +1,49 @@ +using log4net; +using log4net.Appender; +using log4net.Layout; +using log4net.Repository.Hierarchy; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace BaiduYunSync +{ + class LogSettings + { + static LogSettings() + { + string LOG_PATTERN = "%d [%t] %-5p %c [%x] - %m%n"; + string LOG_FILE_PATH = "log/job.log"; + Hierarchy hierarchy = (Hierarchy)LogManager.GetRepository(); + hierarchy.Name = "stdAdapter"; + TraceAppender tracer = new TraceAppender(); + + PatternLayout patternLayout = new PatternLayout(); + patternLayout.ConversionPattern = LOG_PATTERN; + patternLayout.ActivateOptions(); + + tracer.Layout = patternLayout; + tracer.ActivateOptions(); + hierarchy.Root.AddAppender(tracer); + + RollingFileAppender roller = new RollingFileAppender(); + roller.Layout = patternLayout; + roller.AppendToFile = true; + roller.RollingStyle = RollingFileAppender.RollingMode.Size; + roller.MaxSizeRollBackups = 10; + roller.MaximumFileSize = "1MB"; + roller.StaticLogFileName = true; + roller.File = LOG_FILE_PATH; + roller.ActivateOptions(); + hierarchy.Root.AddAppender(roller); + hierarchy.Root.Level = log4net.Core.Level.All; + hierarchy.Configured = true; + } + + public static void StdInfo(string msg) + { + LogManager.GetLogger("stdAdapter").Info(msg); + } + } +} diff --git a/BaiduYunSync/Program.cs b/BaiduYunSync/Program.cs new file mode 100644 index 0000000..b1f3e40 --- /dev/null +++ b/BaiduYunSync/Program.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Windows.Forms; + +namespace BaiduYunSync +{ + static class Program + { + /// + /// 应用程序的主入口点。 + /// + [STAThread] + static void Main() + { + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new frmMain()); + } + } +} diff --git a/BaiduYunSync/Properties/AssemblyInfo.cs b/BaiduYunSync/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..1ee8bf9 --- /dev/null +++ b/BaiduYunSync/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// 有关程序集的常规信息通过以下 +// 特性集控制。更改这些特性值可修改 +// 与程序集关联的信息。 +[assembly: AssemblyTitle("BaiduYunSync")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("BaiduYunSync")] +[assembly: AssemblyCopyright("Copyright © 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// 将 ComVisible 设置为 false 使此程序集中的类型 +// 对 COM 组件不可见。 如果需要从 COM 访问此程序集中的类型, +// 则将该类型上的 ComVisible 特性设置为 true。 +[assembly: ComVisible(false)] + +// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID +[assembly: Guid("4e80c253-7d19-44cc-a24b-8f92154fbc6e")] + +// 程序集的版本信息由下面四个值组成: +// +// 主版本 +// 次版本 +// 生成号 +// 修订号 +// +// 可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值, +// 方法是按如下所示使用“*”: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/BaiduYunSync/Properties/Resources.Designer.cs b/BaiduYunSync/Properties/Resources.Designer.cs new file mode 100644 index 0000000..8db3a6f --- /dev/null +++ b/BaiduYunSync/Properties/Resources.Designer.cs @@ -0,0 +1,71 @@ +//------------------------------------------------------------------------------ +// +// 此代码由工具生成。 +// 运行时版本: 4.0.30319.42000 +// +// 对此文件的更改可能会导致不正确的行为,并且如果 +// 重新生成代码,这些更改将丢失。 +// +//------------------------------------------------------------------------------ + +namespace BaiduYunSync.Properties +{ + + + /// + /// 一个强类型的资源类,用于查找本地化的字符串等。 + /// + // 此类是由 StronglyTypedResourceBuilder + // 类通过类似于 ResGen 或 Visual Studio 的工具自动生成的。 + // 若要添加或移除成员,请编辑 .ResX 文件,然后重新运行 ResGen + // (以 /str 作为命令选项),或重新生成 VS 项目。 + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.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() + { + } + + /// + /// 返回此类使用的、缓存的 ResourceManager 实例。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if ((resourceMan == null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("BaiduYunSync.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// 为所有资源查找重写当前线程的 CurrentUICulture 属性, + /// 方法是使用此强类型资源类。 + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + } +} diff --git a/BaiduYunSync/Properties/Resources.resx b/BaiduYunSync/Properties/Resources.resx new file mode 100644 index 0000000..af7dbeb --- /dev/null +++ b/BaiduYunSync/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/BaiduYunSync/Properties/Settings.Designer.cs b/BaiduYunSync/Properties/Settings.Designer.cs new file mode 100644 index 0000000..14d6801 --- /dev/null +++ b/BaiduYunSync/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// 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 BaiduYunSync.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "11.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/BaiduYunSync/Properties/Settings.settings b/BaiduYunSync/Properties/Settings.settings new file mode 100644 index 0000000..3964565 --- /dev/null +++ b/BaiduYunSync/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + diff --git a/BaiduYunSync/Sync.cs b/BaiduYunSync/Sync.cs new file mode 100644 index 0000000..9f9c51c --- /dev/null +++ b/BaiduYunSync/Sync.cs @@ -0,0 +1,294 @@ +using NetDisk; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading; + +namespace BaiduYunSync +{ + /// + /// 同步类 + /// + class Sync + { + + static Sync() + { + Operation.InitUploadFile += Operation_InitUploadFile; + Operation.StartUploadBlock += Operation_StartUploadBlock; + Operation.ComplateUploadBlock += Operation_ComplateUploadBlock; + Operation.UpdateComplate += Operation_UpdateComplate; + + } + + private static void Operation_UpdateComplate(string filePath) + { + lock (locker) + { + LiteDbHelper.DeleteUpdateInfo(LiteDbHelper.GetAllUpdateInfos().Where(x => x.Path == filePath).First()); + } + + LogSettings.StdInfo(filePath + "上传完成,从队列中删除"); + if (StatusModify != null) + StatusModify(filePath, StatusEnum.Delete); + mre.Set(); + } + + private static void Operation_ComplateUploadBlock(string filePath, int endBlockNum, int totalBlockNum) + { + LiteDbHelper.GetAllUpdateInfos().Where(x => x.Path == filePath).ToList().ForEach( + x => + { + x.ComplateBlockNum = endBlockNum; + }); + LogSettings.StdInfo(string.Format("{0}完成第{1}个块,共{2}个块", filePath, endBlockNum, totalBlockNum)); + if (StatusModify != null) + StatusModify(filePath, StatusEnum.Modify); + } + + private static void Operation_StartUploadBlock(string filePath, int startBlockNum, int totalBlockNum) + { + LogSettings.StdInfo(string.Format("{0}开始上传第{1}个块,共{2}个块", filePath, startBlockNum, totalBlockNum)); + } + + private static void Operation_InitUploadFile(string filePath, int fileBlockNum, int needUploadBlockNum) + { + LiteDbHelper.GetAllUpdateInfos().Where(x => x.Path == filePath).ToList().ForEach( + x => + { + x.Status = "正在上传"; + x.TotalBlockNum = fileBlockNum; + }); + LogSettings.StdInfo(filePath + "准备开始上传"); + if (StatusModify != null) + StatusModify(filePath, StatusEnum.Start); + } + + public static AutoResetEvent mre = new AutoResetEvent(false); + + /// + /// 任务 + /// + private static AutoResetEvent taskmre = new AutoResetEvent(false); + + + private static object locker = new object(); + + /// + /// 开始新同步时发生 + /// + /// 文件夹名称 + public delegate void StartSyncHandle(string folder); + + public static event StartSyncHandle StartSync; + + /// + /// 同步结束时发生 + /// + /// + public delegate void EndSyncHandle(SyncItemsDao folder); + + public static event EndSyncHandle EndSync; + + /// + /// 文件上传状态改变 + /// + /// + /// + public delegate void StatusModifyHandle(string path, StatusEnum status); + + public static event StatusModifyHandle StatusModify; + + /// + /// 文件状态 + /// + public enum StatusEnum + { + /// + /// 添加新项 + /// + Add, + /// + /// 开始上传 + /// + Start, + /// + /// 上传状态改变 + /// + Modify, + /// + /// 结束删除 + /// + Delete + } + + /// + /// 启动同步线程 + /// + /// 身份信息 + public static void SyncNow( Credential credential) + { + // 启动5个上传线程进行上传 + for (int i = 0; i < 5; i++) + { + var thread = new Thread(() => UpdateFile(credential)); + thread.IsBackground = true; + thread.Start(); + } + while (true) + { + // 如果队列中存在未完成的任务,则不进行下一轮扫描 + while (LiteDbHelper.GetUpdateInfoCount() > 0) + { + mre.WaitOne(); + } + LogSettings.StdInfo("开始文件夹同步"); + // 查找距离上次同步时间超过6小时的文件夹 + var folderList = LiteDbHelper.GetAllSyncItems().Where(x => (DateTime.Now - x.LastSync).TotalHours > 6); + foreach (var folder in folderList) + { + LogSettings.StdInfo("需要同步的文件夹为:" + folder.Folder); + if (StartSync != null) + { + StartSync(folder.Folder); + } + SearchAllFile(folder.Folder, "/", credential); + folder.LastSync = DateTime.Now; + LiteDbHelper.UpdateSyncItem(folder); + LogSettings.StdInfo(folder.Folder + "同步完成"); + if (EndSync != null) + { + EndSync(folder); + } + } + LogSettings.StdInfo("同步结束"); + mre.WaitOne(new TimeSpan(6, 0, 0)); + } + + } + + /// + /// 查找所有需要同步的文件 + /// + /// 本地文件夹路径 + /// 服务器路径(父路径) + /// 身份信息 + private static void SearchAllFile(string folderPath, string serverPath, Credential credential) + { + // 获取服务器路径下文件列表 + var fileList = Operation.GetFileList(serverPath, credential); + // 获取需要同步的文件夹名 + var folderName = Path.GetFileName(folderPath); + // 组合成新服务器路径,由于windows文件路径为\分割,服务器为/分割,所以需要转换 + var newServerPath = Path.Combine(serverPath, folderName).Replace('\\', '/'); + if (fileList.list == null || !fileList.list.Select(x => x.server_filename).Contains(folderName)) + { + Operation.CreateFolder(newServerPath, credential); + LogSettings.StdInfo("创建文件夹" + newServerPath); + } + fileList = Operation.GetFileList(newServerPath, credential); + var localFileList = Directory.GetFiles(folderPath); + var localDirectoryList = Directory.GetDirectories(folderPath); + foreach (var file in localFileList) + { + // 如果任务中存在该文件则放弃添加 + if (LiteDbHelper.ContainsPath(file)) + { + continue; + } + var updateInfo = new UpdateInfoDao(); + updateInfo.FileName = Path.GetFileName(file); + updateInfo.FileLength = new FileInfo(file).Length; + updateInfo.Path = file; + if (updateInfo.FileLength == 0) + { + LogSettings.StdInfo(file + "大小为0,暂不支持上传"); + continue; + } + var fileEnumberable = fileList.list == null ? null : fileList.list.Where(x => x.server_filename == Path.GetFileName(file)); + FileInfo fi = new FileInfo(file); + //如果有文件,因为文件名不能相同,所以只需要判断第一个文件是否与服务器相同即可 + if (fileEnumberable != null && fileEnumberable.Count() > 0) + { + // 服务器返回的MD5有时候好像有问题,会导致部分文件反复同步,所以删除 + if (fileEnumberable.ElementAt(0).size == fi.Length)// && fileEnumberable.ElementAt(0).md5 == GetMD5HashFromFile(file)) + { + LogSettings.StdInfo("文件已存在:" + fileEnumberable.ElementAt(0).server_filename); + continue; + } + updateInfo.NeedDelete = true; + } + if (!updateInfo.NeedDelete) + { + updateInfo.ServerPath = Path.Combine(newServerPath, Path.GetFileName(file)).Replace('\\', '/'); + } + lock (locker) + { + LiteDbHelper.InsertUpdateInfo(updateInfo); + LogSettings.StdInfo("添加文件:" + updateInfo.Path); + } + if (StatusModify != null) + StatusModify(updateInfo.Path, StatusEnum.Add); + taskmre.Set(); + } + foreach (var directory in localDirectoryList) + { + SearchAllFile(directory, newServerPath, credential); + } + } + + /// + /// 上传文件 + /// + /// 身份信息 + private static void UpdateFile(Credential credential) + { + while (true) + { + UpdateInfoDao updateInfo; + lock (locker) + { + // 查找第一个未开始同步的文件 + updateInfo = LiteDbHelper.GetAllUpdateInfos().Where(x => !x.IsUsed).FirstOrDefault(); + if (updateInfo != null) + { + updateInfo.IsUsed = true; + } + + } + // 如果没有找到,则证明 + if (updateInfo == null) + { + taskmre.Reset(); + taskmre.WaitOne(); + continue; + } + if (updateInfo.NeedDelete) + { + var fileOperationResult = Operation.Delete(updateInfo.ServerPath, credential); + if (!fileOperationResult.success) + { + + } + LogSettings.StdInfo("删除文件:" + updateInfo.ServerPath); + } + for (int i = 0; i < 3; i++) + { + try + { + Operation.ChunkedUpload(updateInfo.Path, updateInfo.ServerPath, credential); + LogSettings.StdInfo("上传文件成功:" + updateInfo.FileName); + break; + } + catch (Exception ex) + { + LogSettings.StdInfo(string.Format("第{0}次上传失败,文件名:{1},失败原因:{2}", i, updateInfo.Path, ex)); + } + } + } + + } + } +} diff --git a/BaiduYunSync/SyncItemsDao.cs b/BaiduYunSync/SyncItemsDao.cs new file mode 100644 index 0000000..e2bb83c --- /dev/null +++ b/BaiduYunSync/SyncItemsDao.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace BaiduYunSync +{ + public class SyncItemsDao + { + public int Id { get; set; } + + public string Folder { get; set; } + + public DateTime LastSync { get; set; } + } +} diff --git a/BaiduYunSync/UpdateInfoDao.cs b/BaiduYunSync/UpdateInfoDao.cs new file mode 100644 index 0000000..7281d29 --- /dev/null +++ b/BaiduYunSync/UpdateInfoDao.cs @@ -0,0 +1,61 @@ +using NetDisk; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace BaiduYunSync +{ + /// + /// 上传信息类 + /// + public class UpdateInfoDao + { + public int Id { get; set; } + + /// + /// 总共完成数量 + /// + public int ComplateBlockNum { get; set; } + + /// + /// 总块数 + /// + public int TotalBlockNum { get; set; } + + /// + /// 文件路径 + /// + public string Path { get; set; } + + /// + /// 服务器路径 + /// + public string ServerPath { get; set; } + + /// + /// 文件名 + /// + public string FileName { get; set; } + + /// + /// 当前状态 + /// + public string Status { get; set; } + + /// + /// 文件大小 + /// + public long FileLength { get; set; } + + /// + /// 是否需要先删除文件 + /// + public bool NeedDelete { get; set; } + + /// + /// 是否已经被使用 + /// + public bool IsUsed { get; set; } + } +} diff --git a/BaiduYunSync/UserInfoDao.cs b/BaiduYunSync/UserInfoDao.cs new file mode 100644 index 0000000..2d1b6be --- /dev/null +++ b/BaiduYunSync/UserInfoDao.cs @@ -0,0 +1,31 @@ +using NetDisk; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace BaiduYunSync +{ + /// + /// 用户信息类 + /// + public class UserInfoDao + { + public int Id { get; set; } + + /// + /// 用户名 + /// + public string UserName { get; set; } + + /// + /// 密码 + /// + public string Password { get; set; } + + /// + /// 用户凭据 + /// + public string UserCredentialStr { get; set; } + } +} diff --git a/BaiduYunSync/frmLogin.Designer.cs b/BaiduYunSync/frmLogin.Designer.cs new file mode 100644 index 0000000..833cd28 --- /dev/null +++ b/BaiduYunSync/frmLogin.Designer.cs @@ -0,0 +1,153 @@ +namespace BaiduYunSync +{ + partial class frmLogin + { + /// + /// 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() + { + this.btnLogin = new System.Windows.Forms.Button(); + this.pbVcode = new System.Windows.Forms.PictureBox(); + this.txtVCode = new System.Windows.Forms.TextBox(); + this.txtPassword = new System.Windows.Forms.TextBox(); + this.txtUserName = new System.Windows.Forms.TextBox(); + this.lbVcode = new System.Windows.Forms.Label(); + this.label2 = new System.Windows.Forms.Label(); + this.label1 = new System.Windows.Forms.Label(); + ((System.ComponentModel.ISupportInitialize)(this.pbVcode)).BeginInit(); + this.SuspendLayout(); + // + // btnLogin + // + this.btnLogin.Location = new System.Drawing.Point(15, 146); + this.btnLogin.Name = "btnLogin"; + this.btnLogin.Size = new System.Drawing.Size(324, 28); + this.btnLogin.TabIndex = 12; + this.btnLogin.Text = "登录"; + this.btnLogin.UseVisualStyleBackColor = true; + this.btnLogin.Click += new System.EventHandler(this.btnLogin_Click); + // + // pbVcode + // + this.pbVcode.Location = new System.Drawing.Point(241, 91); + this.pbVcode.Name = "pbVcode"; + this.pbVcode.Size = new System.Drawing.Size(99, 44); + this.pbVcode.SizeMode = System.Windows.Forms.PictureBoxSizeMode.StretchImage; + this.pbVcode.TabIndex = 9; + this.pbVcode.TabStop = false; + this.pbVcode.Visible = false; + this.pbVcode.Click += new System.EventHandler(this.pbVcode_Click); + // + // txtVCode + // + this.txtVCode.Location = new System.Drawing.Point(90, 97); + this.txtVCode.Name = "txtVCode"; + this.txtVCode.Size = new System.Drawing.Size(119, 25); + this.txtVCode.TabIndex = 11; + this.txtVCode.Visible = false; + // + // txtPassword + // + this.txtPassword.Location = new System.Drawing.Point(90, 56); + this.txtPassword.Name = "txtPassword"; + this.txtPassword.PasswordChar = '*'; + this.txtPassword.Size = new System.Drawing.Size(251, 25); + this.txtPassword.TabIndex = 10; + // + // txtUserName + // + this.txtUserName.Location = new System.Drawing.Point(90, 14); + this.txtUserName.Name = "txtUserName"; + this.txtUserName.Size = new System.Drawing.Size(251, 25); + this.txtUserName.TabIndex = 8; + this.txtUserName.Leave += new System.EventHandler(this.txtUserName_Leave); + // + // lbVcode + // + this.lbVcode.AutoSize = true; + this.lbVcode.Location = new System.Drawing.Point(12, 100); + this.lbVcode.Name = "lbVcode"; + this.lbVcode.Size = new System.Drawing.Size(67, 15); + this.lbVcode.TabIndex = 5; + this.lbVcode.Text = "验证码:"; + this.lbVcode.Visible = false; + // + // label2 + // + this.label2.AutoSize = true; + this.label2.Location = new System.Drawing.Point(12, 59); + this.label2.Name = "label2"; + this.label2.Size = new System.Drawing.Size(52, 15); + this.label2.TabIndex = 6; + this.label2.Text = "密码:"; + // + // label1 + // + this.label1.AutoSize = true; + this.label1.Location = new System.Drawing.Point(12, 18); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(67, 15); + this.label1.TabIndex = 7; + this.label1.Text = "用户名:"; + // + // frmLogin + // + this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 15F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(362, 189); + this.Controls.Add(this.btnLogin); + this.Controls.Add(this.pbVcode); + this.Controls.Add(this.txtVCode); + this.Controls.Add(this.txtPassword); + this.Controls.Add(this.txtUserName); + this.Controls.Add(this.lbVcode); + this.Controls.Add(this.label2); + this.Controls.Add(this.label1); + this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.FixedDialog; + this.MaximizeBox = false; + this.MinimizeBox = false; + this.Name = "frmLogin"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "登录"; + this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.frmLogin_FormClosing); + this.Load += new System.EventHandler(this.frmLogin_Load); + ((System.ComponentModel.ISupportInitialize)(this.pbVcode)).EndInit(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.Button btnLogin; + private System.Windows.Forms.PictureBox pbVcode; + private System.Windows.Forms.TextBox txtVCode; + private System.Windows.Forms.TextBox txtPassword; + private System.Windows.Forms.TextBox txtUserName; + private System.Windows.Forms.Label lbVcode; + private System.Windows.Forms.Label label2; + private System.Windows.Forms.Label label1; + } +} \ No newline at end of file diff --git a/BaiduYunSync/frmLogin.cs b/BaiduYunSync/frmLogin.cs new file mode 100644 index 0000000..e72574b --- /dev/null +++ b/BaiduYunSync/frmLogin.cs @@ -0,0 +1,91 @@ +using NetDisk; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Text; +using System.Windows.Forms; + +namespace BaiduYunSync +{ + public partial class frmLogin : Form + { + public frmLogin() + { + InitializeComponent(); + } + + /// + /// 返回内容 + /// + public LoginResult result; + + private LoginCheckResult lcr; + + private void txtUserName_Leave(object sender, EventArgs e) + { + if (txtUserName.Text == "") + { + lcr = null; + return; + } + lcr = Authentication.LoginCheck(txtUserName.Text); + if (!lcr.success) + { + MessageBox.Show("错误:" + lcr.exception); + return; + } + lbVcode.Visible = txtVCode.Visible = pbVcode.Visible = lcr.needVCode; + txtVCode.Text = ""; + if (lcr.needVCode) + { + pbVcode.Image = ByteToImage(lcr.image); + } + } + + private static Bitmap ByteToImage(byte[] blob) + { + MemoryStream mStream = new MemoryStream(); + byte[] pData = blob; + mStream.Write(pData, 0, Convert.ToInt32(pData.Length)); + Bitmap bm = new Bitmap(mStream, false); + mStream.Dispose(); + return bm; + } + + private void frmLogin_Load(object sender, EventArgs e) + { + txtUserName.Focus(); + } + + private void btnLogin_Click(object sender, EventArgs e) + { + if (lcr == null) return; + lcr.verifyCode = txtVCode.Text; + result = Authentication.Login(txtUserName.Text, txtPassword.Text, lcr); + if (!result.success) + { + MessageBox.Show("出现错误: " + result.exception); + txtUserName_Leave(null, null); + return; + } + var userInfo = LiteDbHelper.UserInfo; + userInfo.UserName = txtUserName.Text; + userInfo.UserCredentialStr = result.credential.Serialize(); + LiteDbHelper.UserInfo = userInfo; + DialogResult = DialogResult.OK; + } + + private void pbVcode_Click(object sender, EventArgs e) + { + txtUserName_Leave(null, null); + } + + private void frmLogin_FormClosing(object sender, FormClosingEventArgs e) + { + } + } +} diff --git a/BaiduYunSync/frmLogin.resx b/BaiduYunSync/frmLogin.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/BaiduYunSync/frmLogin.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + \ No newline at end of file diff --git a/BaiduYunSync/frmMain.Designer.cs b/BaiduYunSync/frmMain.Designer.cs new file mode 100644 index 0000000..4739124 --- /dev/null +++ b/BaiduYunSync/frmMain.Designer.cs @@ -0,0 +1,179 @@ +namespace BaiduYunSync +{ + partial class frmMain + { + /// + /// 必需的设计器变量。 + /// + private System.ComponentModel.IContainer components = null; + + /// + /// 清理所有正在使用的资源。 + /// + /// 如果应释放托管资源,为 true;否则为 false。 + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows 窗体设计器生成的代码 + + /// + /// 设计器支持所需的方法 - 不要 + /// 使用代码编辑器修改此方法的内容。 + /// + private void InitializeComponent() + { + this.statusStrip1 = new System.Windows.Forms.StatusStrip(); + this.tsslQuota = new System.Windows.Forms.ToolStripStatusLabel(); + this.btnAddSync = new System.Windows.Forms.Button(); + this.lvSyncFolder = new System.Windows.Forms.ListView(); + this.columnHeader1 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.columnHeader2 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.columnHeader3 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.btnDelSync = new System.Windows.Forms.Button(); + this.fbdSyncFloder = new System.Windows.Forms.FolderBrowserDialog(); + this.txtReadLog = new System.Windows.Forms.Button(); + this.btnUploadList = new System.Windows.Forms.Button(); + this.statusStrip1.SuspendLayout(); + this.SuspendLayout(); + // + // statusStrip1 + // + this.statusStrip1.ImageScalingSize = new System.Drawing.Size(20, 20); + this.statusStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.tsslQuota}); + this.statusStrip1.Location = new System.Drawing.Point(0, 361); + this.statusStrip1.Name = "statusStrip1"; + this.statusStrip1.Padding = new System.Windows.Forms.Padding(1, 0, 10, 0); + this.statusStrip1.Size = new System.Drawing.Size(620, 22); + this.statusStrip1.TabIndex = 3; + this.statusStrip1.Text = "statusStrip1"; + // + // tsslQuota + // + this.tsslQuota.Name = "tsslQuota"; + this.tsslQuota.Size = new System.Drawing.Size(131, 17); + this.tsslQuota.Text = "toolStripStatusLabel1"; + this.tsslQuota.Click += new System.EventHandler(this.tsslQuota_Click); + // + // btnAddSync + // + this.btnAddSync.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.btnAddSync.Location = new System.Drawing.Point(535, 10); + this.btnAddSync.Name = "btnAddSync"; + this.btnAddSync.Size = new System.Drawing.Size(75, 23); + this.btnAddSync.TabIndex = 0; + this.btnAddSync.Text = "添加新同步"; + this.btnAddSync.UseVisualStyleBackColor = true; + this.btnAddSync.Click += new System.EventHandler(this.btnAddSync_Click); + // + // lvSyncFolder + // + this.lvSyncFolder.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.lvSyncFolder.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { + this.columnHeader1, + this.columnHeader2, + this.columnHeader3}); + this.lvSyncFolder.FullRowSelect = true; + this.lvSyncFolder.GridLines = true; + this.lvSyncFolder.Location = new System.Drawing.Point(0, 0); + this.lvSyncFolder.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); + this.lvSyncFolder.Name = "lvSyncFolder"; + this.lvSyncFolder.Size = new System.Drawing.Size(531, 362); + this.lvSyncFolder.TabIndex = 4; + this.lvSyncFolder.UseCompatibleStateImageBehavior = false; + this.lvSyncFolder.View = System.Windows.Forms.View.Details; + // + // columnHeader1 + // + this.columnHeader1.Text = "地址"; + this.columnHeader1.Width = 265; + // + // columnHeader2 + // + this.columnHeader2.Text = "同步状态"; + this.columnHeader2.Width = 139; + // + // columnHeader3 + // + this.columnHeader3.Text = "最后同步时间"; + this.columnHeader3.Width = 116; + // + // btnDelSync + // + this.btnDelSync.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.btnDelSync.Location = new System.Drawing.Point(535, 53); + this.btnDelSync.Name = "btnDelSync"; + this.btnDelSync.Size = new System.Drawing.Size(75, 23); + this.btnDelSync.TabIndex = 5; + this.btnDelSync.Text = "删除同步"; + this.btnDelSync.UseVisualStyleBackColor = true; + this.btnDelSync.Click += new System.EventHandler(this.btnDelSync_Click); + // + // txtReadLog + // + this.txtReadLog.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.txtReadLog.Location = new System.Drawing.Point(535, 137); + this.txtReadLog.Name = "txtReadLog"; + this.txtReadLog.Size = new System.Drawing.Size(75, 23); + this.txtReadLog.TabIndex = 6; + this.txtReadLog.Text = "查看日志"; + this.txtReadLog.UseVisualStyleBackColor = true; + this.txtReadLog.Click += new System.EventHandler(this.txtReadLog_Click); + // + // btnUploadList + // + this.btnUploadList.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right))); + this.btnUploadList.Location = new System.Drawing.Point(535, 97); + this.btnUploadList.Name = "btnUploadList"; + this.btnUploadList.Size = new System.Drawing.Size(75, 23); + this.btnUploadList.TabIndex = 7; + this.btnUploadList.Text = "传输列表"; + this.btnUploadList.UseVisualStyleBackColor = true; + this.btnUploadList.Click += new System.EventHandler(this.btnUploadList_Click); + // + // frmMain + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(620, 383); + this.Controls.Add(this.btnUploadList); + this.Controls.Add(this.txtReadLog); + this.Controls.Add(this.btnDelSync); + this.Controls.Add(this.lvSyncFolder); + this.Controls.Add(this.statusStrip1); + this.Controls.Add(this.btnAddSync); + this.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); + this.Name = "frmMain"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; + this.Text = "百度云自动同步"; + this.Load += new System.EventHandler(this.frmMain_Load); + this.statusStrip1.ResumeLayout(false); + this.statusStrip1.PerformLayout(); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + private System.Windows.Forms.StatusStrip statusStrip1; + private System.Windows.Forms.ToolStripStatusLabel tsslQuota; + private System.Windows.Forms.Button btnAddSync; + private System.Windows.Forms.ListView lvSyncFolder; + private System.Windows.Forms.ColumnHeader columnHeader1; + private System.Windows.Forms.ColumnHeader columnHeader2; + private System.Windows.Forms.Button btnDelSync; + private System.Windows.Forms.FolderBrowserDialog fbdSyncFloder; + private System.Windows.Forms.ColumnHeader columnHeader3; + private System.Windows.Forms.Button txtReadLog; + private System.Windows.Forms.Button btnUploadList; + } +} + diff --git a/BaiduYunSync/frmMain.cs b/BaiduYunSync/frmMain.cs new file mode 100644 index 0000000..0010972 --- /dev/null +++ b/BaiduYunSync/frmMain.cs @@ -0,0 +1,174 @@ +using NetDisk; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Threading; +using System.Windows.Forms; + +namespace BaiduYunSync +{ + public partial class frmMain : Form + { + public frmMain() + { + InitializeComponent(); + } + + private void frmMain_Load(object sender, EventArgs e) + { + //var checkResult = Authentication.LoginCheck("2000208"); + //MessageBox.Show(checkResult.needVCode.ToString()); + if (!SetQuota()) + { + var login = new frmLogin(); + if (login.ShowDialog() == DialogResult.OK) + { + if (!SetQuota()) + { + Application.Exit(); + } + } + else + { + Application.Exit(); + } + } + var items = LiteDbHelper.GetAllSyncItems(); + foreach (var item in items) + { + addListViewItem(item); + } + Sync.StartSync += Sync_StartSync; + Sync.EndSync += Sync_EndSync; + Thread thread = new Thread(() => { Sync.SyncNow(Credential.Deserialize(LiteDbHelper.UserInfo.UserCredentialStr)); }); + thread.IsBackground = true; + thread.Start(); + } + + private void Sync_EndSync(SyncItemsDao folder) + { + lvSyncFolder.BeginInvoke(new Action( + () => + { + var items = lvSyncFolder.Items.Cast().Where(x => x.Text == folder.Folder); + if (items.Count() > 0) + { + items.ElementAt(0).SubItems[1].Text = "分析完成"; + items.ElementAt(0).SubItems[2].Text = folder.LastSync.ToString(); + } + } + ), null); + } + + private void Sync_StartSync(string folder) + { + lvSyncFolder.BeginInvoke(new Action( + () => + { + var items = lvSyncFolder.Items.Cast().Where(x => x.Text == folder); + if (items.Count() > 0) + { + items.ElementAt(0).SubItems[1].Text = "开始分析"; + } + } + ), null); + } + + private bool SetQuota() + { + var userInfo = LiteDbHelper.UserInfo; + if (userInfo == null || userInfo.UserCredentialStr == null) + { + return false; + } + var quotaResult = Operation.GetQuota(Credential.Deserialize(userInfo.UserCredentialStr)); + if (!quotaResult.success) + { + MessageBox.Show("Error:" + quotaResult.exception); + return false; + } + else if (quotaResult.errno != 0) + { + return false; + } + tsslQuota.Text = "空间使用:已用" + GetGB(quotaResult.used) + "/总共" + GetGB(quotaResult.total); + return true; + } + + /// + /// 将B转换为GB + /// + /// + /// + private string GetGB(long b) + { + var bb = (double)b; + for (int i = 0; i < 3; i++) + { + bb /= 1024; + } + return bb.ToString("f2") + "GB"; + } + + private void btnAddSync_Click(object sender, EventArgs e) + { + if (fbdSyncFloder.ShowDialog() == DialogResult.OK) + { + if (lvSyncFolder.Items.Cast().Select(x => (x.Tag as SyncItemsDao).Folder).Contains(fbdSyncFloder.SelectedPath)) + { + MessageBox.Show("当前文件夹已在同步列表中,无需再次添加"); + return; + } + var dao = new SyncItemsDao(); + dao.Folder = fbdSyncFloder.SelectedPath; + dao.LastSync = new DateTime(1970, 1, 1); + LiteDbHelper.UpdateSyncItem(dao); + addListViewItem(dao); + Sync.mre.Set(); + } + } + + private void addListViewItem(SyncItemsDao dao) + { + var item = new ListViewItem(dao.Folder); + item.SubItems.Add("准备同步"); + item.SubItems.Add(dao.LastSync.ToString()); + item.Tag = dao; + item.ToolTipText = dao.Folder; + lvSyncFolder.Items.Add(item); + } + + private void txtReadLog_Click(object sender, EventArgs e) + { + new frmReadLog().ShowDialog(); + } + + private void tsslQuota_Click(object sender, EventArgs e) + { + SetQuota(); + } + + private void btnDelSync_Click(object sender, EventArgs e) + { + foreach (ListViewItem item in lvSyncFolder.SelectedItems) + { + LiteDbHelper.DeleteSyncItem(item.Tag as SyncItemsDao); + lvSyncFolder.Items.Remove(item); + } + } + + frmUploadList updateListForm; + private void btnUploadList_Click(object sender, EventArgs e) + { + if (updateListForm == null || updateListForm.IsDisposed) + { + updateListForm = new frmUploadList(); + } + updateListForm.Show(); + } + } +} diff --git a/BaiduYunSync/frmMain.resx b/BaiduYunSync/frmMain.resx new file mode 100644 index 0000000..05f4ca0 --- /dev/null +++ b/BaiduYunSync/frmMain.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + 17, 17 + + + 161, 17 + + \ No newline at end of file diff --git a/BaiduYunSync/frmReadLog.Designer.cs b/BaiduYunSync/frmReadLog.Designer.cs new file mode 100644 index 0000000..3873b21 --- /dev/null +++ b/BaiduYunSync/frmReadLog.Designer.cs @@ -0,0 +1,63 @@ +namespace BaiduYunSync +{ + partial class frmReadLog + { + /// + /// 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() + { + this.txtLog = new System.Windows.Forms.TextBox(); + this.SuspendLayout(); + // + // txtLog + // + this.txtLog.Dock = System.Windows.Forms.DockStyle.Fill; + this.txtLog.Location = new System.Drawing.Point(0, 0); + this.txtLog.Multiline = true; + this.txtLog.Name = "txtLog"; + this.txtLog.ReadOnly = true; + this.txtLog.ScrollBars = System.Windows.Forms.ScrollBars.Both; + this.txtLog.Size = new System.Drawing.Size(554, 490); + this.txtLog.TabIndex = 0; + // + // frmReadLog + // + this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 15F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(554, 490); + this.Controls.Add(this.txtLog); + this.Name = "frmReadLog"; + this.Text = "frmReadLog"; + this.Load += new System.EventHandler(this.frmReadLog_Load); + this.ResumeLayout(false); + this.PerformLayout(); + + } + + #endregion + + private System.Windows.Forms.TextBox txtLog; + } +} \ No newline at end of file diff --git a/BaiduYunSync/frmReadLog.cs b/BaiduYunSync/frmReadLog.cs new file mode 100644 index 0000000..d170397 --- /dev/null +++ b/BaiduYunSync/frmReadLog.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.IO; +using System.Linq; +using System.Text; +using System.Windows.Forms; + +namespace BaiduYunSync +{ + public partial class frmReadLog : Form + { + public frmReadLog() + { + InitializeComponent(); + } + + private void frmReadLog_Load(object sender, EventArgs e) + { + if (File.Exists("log/job.log")) + { + using (var fs = new FileStream("log/job.log", FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + { + int fsLen = (int)fs.Length; + byte[] heByte = new byte[fsLen]; + int r = fs.Read(heByte, 0, heByte.Length); + string myStr = System.Text.Encoding.Default.GetString(heByte); + txtLog.Text = myStr; + } + } + } + } +} diff --git a/BaiduYunSync/frmReadLog.resx b/BaiduYunSync/frmReadLog.resx new file mode 100644 index 0000000..1af7de1 --- /dev/null +++ b/BaiduYunSync/frmReadLog.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + \ No newline at end of file diff --git a/BaiduYunSync/frmUploadList.Designer.cs b/BaiduYunSync/frmUploadList.Designer.cs new file mode 100644 index 0000000..139b184 --- /dev/null +++ b/BaiduYunSync/frmUploadList.Designer.cs @@ -0,0 +1,140 @@ +namespace BaiduYunSync +{ + partial class frmUploadList + { + /// + /// 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() + { + this.components = new System.ComponentModel.Container(); + this.lvList = new System.Windows.Forms.ListView(); + this.columnHeader1 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.columnHeader2 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.columnHeader3 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.columnHeader4 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.columnHeader5 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.columnHeader6 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); + this.cmsUploadList = new System.Windows.Forms.ContextMenuStrip(this.components); + this.删除选中任务ToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.cmsUploadList.SuspendLayout(); + this.SuspendLayout(); + // + // lvList + // + this.lvList.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { + this.columnHeader1, + this.columnHeader2, + this.columnHeader3, + this.columnHeader4, + this.columnHeader5, + this.columnHeader6}); + this.lvList.ContextMenuStrip = this.cmsUploadList; + this.lvList.Dock = System.Windows.Forms.DockStyle.Fill; + this.lvList.FullRowSelect = true; + this.lvList.GridLines = true; + this.lvList.Location = new System.Drawing.Point(0, 0); + this.lvList.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); + this.lvList.Name = "lvList"; + this.lvList.ShowItemToolTips = true; + this.lvList.Size = new System.Drawing.Size(1023, 571); + this.lvList.TabIndex = 0; + this.lvList.UseCompatibleStateImageBehavior = false; + this.lvList.View = System.Windows.Forms.View.Details; + // + // columnHeader1 + // + this.columnHeader1.Text = "文件名"; + this.columnHeader1.Width = 138; + // + // columnHeader2 + // + this.columnHeader2.Text = "传输状态"; + this.columnHeader2.Width = 129; + // + // columnHeader3 + // + this.columnHeader3.Text = "文件大小"; + this.columnHeader3.Width = 128; + // + // columnHeader4 + // + this.columnHeader4.Text = "分块信息"; + this.columnHeader4.Width = 137; + // + // columnHeader5 + // + this.columnHeader5.Text = "传输信息"; + this.columnHeader5.Width = 140; + // + // columnHeader6 + // + this.columnHeader6.Text = "绝对路径"; + this.columnHeader6.Width = 346; + // + // cmsUploadList + // + this.cmsUploadList.ImageScalingSize = new System.Drawing.Size(20, 20); + this.cmsUploadList.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { + this.删除选中任务ToolStripMenuItem}); + this.cmsUploadList.Name = "cmsUploadList"; + this.cmsUploadList.Size = new System.Drawing.Size(149, 26); + // + // 删除选中任务ToolStripMenuItem + // + this.删除选中任务ToolStripMenuItem.Name = "删除选中任务ToolStripMenuItem"; + this.删除选中任务ToolStripMenuItem.Size = new System.Drawing.Size(148, 22); + this.删除选中任务ToolStripMenuItem.Text = "删除选中任务"; + this.删除选中任务ToolStripMenuItem.Click += new System.EventHandler(this.删除选中任务ToolStripMenuItem_Click); + // + // frmUploadList + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(1023, 571); + this.Controls.Add(this.lvList); + this.Margin = new System.Windows.Forms.Padding(2, 2, 2, 2); + this.Name = "frmUploadList"; + this.StartPosition = System.Windows.Forms.FormStartPosition.CenterParent; + this.Text = "frmUploadList"; + this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.frmUploadList_FormClosing); + this.Load += new System.EventHandler(this.frmUploadList_Load); + this.cmsUploadList.ResumeLayout(false); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.ListView lvList; + private System.Windows.Forms.ColumnHeader columnHeader1; + private System.Windows.Forms.ColumnHeader columnHeader2; + private System.Windows.Forms.ColumnHeader columnHeader3; + private System.Windows.Forms.ColumnHeader columnHeader4; + private System.Windows.Forms.ColumnHeader columnHeader5; + private System.Windows.Forms.ColumnHeader columnHeader6; + private System.Windows.Forms.ContextMenuStrip cmsUploadList; + private System.Windows.Forms.ToolStripMenuItem 删除选中任务ToolStripMenuItem; + } +} \ No newline at end of file diff --git a/BaiduYunSync/frmUploadList.cs b/BaiduYunSync/frmUploadList.cs new file mode 100644 index 0000000..6fad032 --- /dev/null +++ b/BaiduYunSync/frmUploadList.cs @@ -0,0 +1,173 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Data; +using System.Drawing; +using System.Linq; +using System.Text; +using System.Windows.Forms; + +namespace BaiduYunSync +{ + public partial class frmUploadList : Form + { + public frmUploadList() + { + InitializeComponent(); + } + + private void modifyTitle(int listCount, int totalCount) + { + Text = string.Format("传输文件列表:当前显示{0}条/共{1}条", listCount, totalCount); + } + + /// + /// 将B转换为MB + /// + /// + /// + private string getMB(long b) + { + var bb = (double)b; + for (int i = 0; i < 2; i++) + { + bb /= 1024; + } + return bb.ToString("f2") + "MB"; + } + + private void initList() + { + lvList.Items.Clear(); + var updateInfoList = LiteDbHelper.GetAllUpdateInfos(); + var count = updateInfoList.Length > 99 ? 99 : updateInfoList.Length; + for (int i = 0; i < count; i++) + { + var updateInfo = updateInfoList[i]; + addItem(updateInfo); + } + modifyTitle(count, updateInfoList.Length); + } + + private void addItem(UpdateInfoDao updateInfo, int position = -1) + { + ListViewItem item = new ListViewItem(updateInfo.FileName); + item.Tag = updateInfo; + item.ToolTipText = updateInfo.Path; + item.SubItems.Add(updateInfo.Status == null ? "准备传输" : updateInfo.Status); + item.SubItems.Add(getMB(updateInfo.FileLength)); + item.SubItems.Add(updateInfo.Status == null ? "" : + string.Format("已完成{0}块/共{1}块", updateInfo.ComplateBlockNum, updateInfo.TotalBlockNum)); + item.SubItems.Add(updateInfo.Status == null ? "" : + string.Format("已完成{0}/共{1}", updateInfo.ComplateBlockNum == updateInfo.TotalBlockNum ? + getMB(updateInfo.FileLength) : updateInfo.ComplateBlockNum * 4 + "MB", getMB(updateInfo.FileLength))); + item.SubItems.Add(updateInfo.Path); + if (position == -1) + { + lvList.Items.Add(item); + } + else + { + lvList.Items.Insert(position, item); + } + } + + private void removeItem(string path) + { + if (lvList.Items.Cast().Select(x => x.SubItems[5].Text).Contains(path)) + { + lvList.Items.Cast().Where(x => x.SubItems[5].Text == path).First().Remove(); + } + } + + private void modifyItem(UpdateInfoDao updateInfo) + { + if (lvList.Items.Cast().Select(x => x.SubItems[5].Text).Contains(updateInfo.Path)) + { + var item = lvList.Items.Cast().Where(x => x.SubItems[5].Text == updateInfo.Path).First(); + item.SubItems[1].Text = updateInfo.Status; + item.SubItems[3].Text = updateInfo.Status == null ? "" : + string.Format("已完成{0}块/共{1}块", updateInfo.ComplateBlockNum, updateInfo.TotalBlockNum); + item.SubItems[4].Text = updateInfo.Status == null ? "" : + string.Format("已完成{0}/共{1}", updateInfo.ComplateBlockNum == updateInfo.TotalBlockNum ? + getMB(updateInfo.FileLength) : updateInfo.ComplateBlockNum * 4 + "MB", getMB(updateInfo.FileLength)); + item.Tag = updateInfo; + } + } + + private void frmUploadList_Load(object sender, EventArgs e) + { + initList(); + Sync.StatusModify += Sync_StatusModify; + } + + private void Sync_StatusModify(string path, Sync.StatusEnum status) + { + lvList.BeginInvoke(new Action( + () => + { + UpdateInfoDao updateInfo; + switch (status) + { + case Sync.StatusEnum.Add: + if (lvList.Items.Count < 99) + { + updateInfo = LiteDbHelper.GetUpdateInfoFormPath(path); + if (updateInfo != null) + { + addItem(LiteDbHelper.GetUpdateInfoFormPath(path)); + } + + } + break; + case Sync.StatusEnum.Start: + case Sync.StatusEnum.Modify: + updateInfo = LiteDbHelper.GetUpdateInfoFormPath(path); + if (updateInfo != null) + { + modifyItem(updateInfo); + } + + break; + case Sync.StatusEnum.Delete: + removeItem(path); + var count = LiteDbHelper.GetUpdateInfoCount(); + if (count > 98) + { + addItem(LiteDbHelper.GetAllUpdateInfos()[98]); + } + modifyTitle(count > 99 ? 99 : lvList.Items.Count, count); + break; + default: + break; + } + } + ), null); + + } + + private void 删除选中任务ToolStripMenuItem_Click(object sender, EventArgs e) + { + foreach (ListViewItem item in lvList.SelectedItems) + { + var updateInfo = item.Tag as UpdateInfoDao; + if (updateInfo.Status != null) + { + MessageBox.Show(updateInfo.FileName + "正在传输,暂不支持移除", "提示"); + return; + } + LiteDbHelper.DeleteUpdateInfo(updateInfo); + } + initList(); + if (lvList.Items.Count == 0) + { + Sync.mre.Set(); + } + } + + private void frmUploadList_FormClosing(object sender, FormClosingEventArgs e) + { + Sync.StatusModify -= Sync_StatusModify; + } + } +} diff --git a/BaiduYunSync/frmUploadList.resx b/BaiduYunSync/frmUploadList.resx new file mode 100644 index 0000000..48a459e --- /dev/null +++ b/BaiduYunSync/frmUploadList.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 + + + 17, 17 + + \ No newline at end of file diff --git a/BaiduYunSync/packages.config b/BaiduYunSync/packages.config new file mode 100644 index 0000000..55a9c3d --- /dev/null +++ b/BaiduYunSync/packages.config @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/NetDisk/Authentication.cs b/NetDisk/Authentication.cs new file mode 100644 index 0000000..2cd050c --- /dev/null +++ b/NetDisk/Authentication.cs @@ -0,0 +1,104 @@ +using System; +using System.Net; +using System.Text; +using System.Text.RegularExpressions; + +namespace NetDisk +{ + public static class Authentication + { + public static bool IsLoggedIn(Credential credential) + { + var res = Operation.GetQuota(credential); + if (res == null || res.errno != 0) return false; + else return true; + } + public static LoginResult Login(string username, string password, LoginCheckResult checkResult) + { + var result = new LoginResult(); + try + { + using (var wc = new CookieAwareWebClient()) + { + wc.Cookies.Add(checkResult.baiduid); + var ltoken = checkResult.ltoken; + var lstr = "loginmerge=true&token=" + ltoken + "&tpl=netdisk&username=" + Uri.EscapeDataString(username) + "&password=" + Uri.EscapeDataString(password); + if (checkResult.needVCode) lstr += "&codestring=" + checkResult.codeString + "&verifycode=" + Uri.EscapeDataString(checkResult.verifyCode); + wc.Headers.Add(HttpRequestHeader.ContentType, "application/x-www-form-urlencoded"); + var str = Encoding.UTF8.GetString(wc.UploadData("https://passport.baidu.com/v2/api/?login", Encoding.UTF8.GetBytes(lstr))); + var match = Regex.Match(str, "error=(\\d+)"); + var errno = match.Success ? int.Parse(match.Groups[1].Value) : 0; + if (errno != 0) + { + result.exception = new Exception("Login returned error = " + errno); + result.errno = errno; + } + else + { + str = wc.DownloadString("https://passport.baidu.com/v3/login/api/auth/?return_type=3&tpl=netdisk&u=http%3A%2F%2Fpan.baidu.com%2Fdisk%2Fhome"); + long uid = 0; + match = Regex.Match(str, "\"uk\"\\s*:\\s*(\\d+)"); + if (match.Success) long.TryParse(match.Groups[1].Value, out uid); + string baiduid = null, bduss = null, stoken = null; + foreach (Cookie cookie in wc.Cookies.GetAllCookies()) + { + if (cookie.Name.ToLower() == "baiduid") baiduid = cookie.Value; + else if (cookie.Name.ToLower() == "bduss") bduss = cookie.Value; + else if (cookie.Name.ToLower() == "stoken" && cookie.Domain.ToLower().Contains("pan.")) stoken = cookie.Value; + } + if (baiduid != null && bduss != null && stoken != null) + { + result.credential = new Credential(baiduid, bduss, stoken, uid); + result.success = true; + } + else result.exception = new Exception("Cannot find required cookies."); + } + } + } + catch (Exception ex) + { + result.exception = ex; + } + return result; + } + public static LoginCheckResult LoginCheck(string username) + { + var result = new LoginCheckResult(); + try + { + using (var wc = new CookieAwareWebClient()) + { + wc.DownloadData("http://pan.baidu.com/"); + Cookie baiduid = null; + foreach (Cookie cookie in wc.Cookies.GetAllCookies()) + { + if (cookie.Name.ToLower() == "baiduid") baiduid = cookie; + } + if (baiduid == null) throw new Exception("Cannot obtain BAIDUID."); + result.baiduid = baiduid; + var str = wc.DownloadString("https://passport.baidu.com/v2/api/?getapi&tpl=netdisk&subpro=netdisk_web&apiver=v3"); + var ltoken = Regex.Match(str, "\"token\"\\s*:\\s*\"(.+?)\"").Groups[1].Value; + result.ltoken = ltoken; + str = wc.DownloadString("https://passport.baidu.com/v2/api/?logincheck&token=" + ltoken + "&tpl=netdisk&subpro=netdisk_web&apiver=v3&username=" + Uri.EscapeDataString(username)); + var codeString = Regex.Match(str, "\"codeString\"\\s*:\\s*\"(.*?)\"").Groups[1].Value; + if (codeString == "") + { + result.success = true; + } + else + { + result.image = wc.DownloadData("https://passport.baidu.com/cgi-bin/genimage?" + codeString); + result.success = true; + result.needVCode = true; + result.codeString = codeString; + } + } + } + catch (Exception ex) + { + result.exception = ex; + } + return result; + } + } +} diff --git a/NetDisk/CRC32.cs b/NetDisk/CRC32.cs new file mode 100644 index 0000000..0da824c --- /dev/null +++ b/NetDisk/CRC32.cs @@ -0,0 +1,119 @@ +// Copyright (c) Damien Guard. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. +// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 +// Originally published at http://damieng.com/blog/2006/08/08/calculating_crc32_in_c_and_net + +using System; +using System.Collections.Generic; +using System.Security.Cryptography; + +namespace DamienG.Security.Cryptography +{ + /// + /// Implements a 32-bit CRC hash algorithm compatible with Zip etc. + /// + /// + /// Crc32 should only be used for backward compatibility with older file formats + /// and algorithms. It is not secure enough for new applications. + /// If you need to call multiple times for the same data either use the HashAlgorithm + /// interface or remember that the result of one Compute call needs to be ~ (XOR) before + /// being passed in as the seed for the next Compute call. + /// + public sealed class Crc32 : HashAlgorithm + { + public const UInt32 DefaultPolynomial = 0xedb88320u; + public const UInt32 DefaultSeed = 0xffffffffu; + + static UInt32[] defaultTable; + + readonly UInt32 seed; + readonly UInt32[] table; + UInt32 hash; + + public Crc32() + : this(DefaultPolynomial, DefaultSeed) + { + } + + public Crc32(UInt32 polynomial, UInt32 seed) + { + table = InitializeTable(polynomial); + this.seed = hash = seed; + } + + public override void Initialize() + { + hash = seed; + } + + protected override void HashCore(byte[] array, int ibStart, int cbSize) + { + hash = CalculateHash(table, hash, array, ibStart, cbSize); + } + + protected override byte[] HashFinal() + { + var hashBuffer = UInt32ToBigEndianBytes(~hash); + HashValue = hashBuffer; + return hashBuffer; + } + + public override int HashSize { get { return 32; } } + + public static UInt32 Compute(byte[] buffer) + { + return Compute(DefaultSeed, buffer); + } + + public static UInt32 Compute(UInt32 seed, byte[] buffer) + { + return Compute(DefaultPolynomial, seed, buffer); + } + + public static UInt32 Compute(UInt32 polynomial, UInt32 seed, byte[] buffer) + { + return ~CalculateHash(InitializeTable(polynomial), seed, buffer, 0, buffer.Length); + } + + static UInt32[] InitializeTable(UInt32 polynomial) + { + if (polynomial == DefaultPolynomial && defaultTable != null) + return defaultTable; + + var createTable = new UInt32[256]; + for (var i = 0; i < 256; i++) + { + var entry = (UInt32)i; + for (var j = 0; j < 8; j++) + if ((entry & 1) == 1) + entry = (entry >> 1) ^ polynomial; + else + entry = entry >> 1; + createTable[i] = entry; + } + + if (polynomial == DefaultPolynomial) + defaultTable = createTable; + + return createTable; + } + + static UInt32 CalculateHash(UInt32[] table, UInt32 seed, IList buffer, int start, int size) + { + var hash = seed; + for (var i = start; i < start + size; i++) + hash = (hash >> 8) ^ table[buffer[i] ^ hash & 0xff]; + return hash; + } + + static byte[] UInt32ToBigEndianBytes(UInt32 uint32) + { + var result = BitConverter.GetBytes(uint32); + + if (BitConverter.IsLittleEndian) + Array.Reverse(result); + + return result; + } + } +} \ No newline at end of file diff --git a/NetDisk/CookieAwareWebClient.cs b/NetDisk/CookieAwareWebClient.cs new file mode 100644 index 0000000..2196052 --- /dev/null +++ b/NetDisk/CookieAwareWebClient.cs @@ -0,0 +1,54 @@ +using System; +using System.Net; + +namespace NetDisk +{ + public class CookieAwareWebClient : WebClient + { + public CookieContainer Cookies { get; set; } + public Uri Uri { get; set; } + Uri _responseUri; + public Uri ResponseUri + { + get { return _responseUri; } + } + public CookieAwareWebClient() + : this(new CookieContainer()) + { + } + + public CookieAwareWebClient(CookieContainer cookies) + { + this.Cookies = cookies; + } + + protected override WebRequest GetWebRequest(Uri address) + { + WebRequest request = base.GetWebRequest(address); + if (request is HttpWebRequest) + { + ClearCookiesVersion(); + (request as HttpWebRequest).CookieContainer = this.Cookies; + } + HttpWebRequest httpRequest = (HttpWebRequest)request; + httpRequest.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; + return httpRequest; + } + protected override WebResponse GetWebResponse(WebRequest request) + { + WebResponse response = base.GetWebResponse(request); + _responseUri = response.ResponseUri; + return response; + } + private void ClearCookiesVersion() + { + var cc = new CookieContainer(); + foreach (Cookie cookie in Cookies.GetAllCookies()) + { + cookie.Version = 0; + cc.Add(cookie); + } + Cookies = cc; + } + } +} diff --git a/NetDisk/Credential.cs b/NetDisk/Credential.cs new file mode 100644 index 0000000..92bce50 --- /dev/null +++ b/NetDisk/Credential.cs @@ -0,0 +1,74 @@ +using System.Net; + +namespace NetDisk +{ + public class Credential + { + private string baiduid; + public string Baiduid + { + get + { + return baiduid; + } + } + + private string bduss; + public string Bduss + { + get + { + return bduss; + } + } + + private string stoken; + public string Stoken + { + get + { + return stoken; + } + } + + private long uid; + public long Uid + { + get + { + return uid; + } + } + + private string cookieString; + public Credential(string baiduid, string bduss, string stoken, long uid) + { + this.baiduid = baiduid; + this.bduss = bduss; + this.stoken = stoken; + this.uid = uid; + cookieString = "BAIDUID=" + baiduid + "; BDUSS=" + bduss + "; STOKEN=" + stoken; + } + public static implicit operator string(Credential credential) + { + return credential.cookieString; + } + public static implicit operator CookieCollection(Credential credential) + { + var c = new CookieCollection(); + c.Add(new Cookie("BAIDUID", credential.baiduid, "/", ".baidu.com")); + c.Add(new Cookie("BDUSS", credential.bduss, "/", ".baidu.com")); + c.Add(new Cookie("STOKEN", credential.stoken, "/", ".pan.baidu.com")); + return c; + } + public string Serialize() + { + return baiduid + "$" + bduss + "$" + stoken + "$" + uid; + } + public static Credential Deserialize(string str) + { + var tokens = str.Split('$'); + return new Credential(tokens[0], tokens[1], tokens[2], long.Parse(tokens[3])); + } + } +} diff --git a/NetDisk/Extension.cs b/NetDisk/Extension.cs new file mode 100644 index 0000000..5f8aaf3 --- /dev/null +++ b/NetDisk/Extension.cs @@ -0,0 +1,28 @@ +using System.Collections; +using System.Linq; +using System.Net; +using System.Reflection; + +namespace NetDisk +{ + public static class Extension + { + public static CookieCollection GetAllCookies(this CookieContainer container) + { + var allCookies = new CookieCollection(); + var domainTableField = container.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static).FirstOrDefault(x => x.Name == "m_domainTable"); + var domains = (IDictionary)domainTableField.GetValue(container); + + foreach (var val in domains.Values) + { + var type = val.GetType().GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static).First(x => x.Name == "m_list"); + var values = (IDictionary)type.GetValue(val); + foreach (CookieCollection cookies in values.Values) + { + allCookies.Add(cookies); + } + } + return allCookies; + } + } +} diff --git a/NetDisk/NetDisk.csproj b/NetDisk/NetDisk.csproj new file mode 100644 index 0000000..fe1e988 --- /dev/null +++ b/NetDisk/NetDisk.csproj @@ -0,0 +1,79 @@ + + + + + Debug + AnyCPU + {74ECA1BF-6508-417D-BC57-DB00DAECA618} + Library + Properties + NetDisk + NetDisk + v4.0 + 512 + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + ..\packages\Newtonsoft.Json.9.0.1\lib\net40\Newtonsoft.Json.dll + True + + + + + + + + + + + + + + + + Component + + + + + + + Component + + + + + + + + + + + \ No newline at end of file diff --git a/NetDisk/Operation.cs b/NetDisk/Operation.cs new file mode 100644 index 0000000..0eee553 --- /dev/null +++ b/NetDisk/Operation.cs @@ -0,0 +1,674 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Runtime.Serialization; +using System.Text; +using Newtonsoft.Json; +using System.Text.RegularExpressions; +using System.IO; +using System.Web; + +namespace NetDisk +{ + public static class Operation + { + /// + /// 块完成事件(顺序提交) + /// + /// 文件名 + /// 当前完成的块 + /// 总块数 + public delegate void ComplateUploadBlockHandle(string filePath, int endBlockNum, int totalBlockNum); + + public static event ComplateUploadBlockHandle ComplateUploadBlock; + + /// + /// 初始化上传 + /// + /// 文件名 + /// 文件所拥有的块数 + /// 服务器需要上传的块数 + public delegate void InitUploadFileHandle(string filePath, int fileBlockNum, int needUploadBlockNum); + + public static event InitUploadFileHandle InitUploadFile; + + /// + /// 开始上传块 + /// + /// 文件名 + /// 当前开始的块 + /// 总块数 + public delegate void StartUploadBlockHandle(string filePath, int startBlockNum, int totalBlockNum); + + public static event StartUploadBlockHandle StartUploadBlock; + + /// + /// 上传完成 + /// + /// 文件名 + public delegate void UpdateComplateHandle(string filePath); + + public static event UpdateComplateHandle UpdateComplate; + + + public static QuotaResult GetQuota(Credential credential) + { + try + { + using (var wc = new WebClient()) + { + wc.Headers.Add(HttpRequestHeader.Cookie, credential); + var res = wc.DownloadString("http://pan.baidu.com/api/quota?checkexpire=1&checkfree=1"); + var obj = JsonConvert.DeserializeObject(res); + obj.success = true; + return obj; + } + } + catch (Exception ex) + { + return new QuotaResult() { exception = ex }; + } + } + public static UserInfoResult GetUserInfo(Credential credential) + { + try + { + if (credential.Uid <= 0) throw new Exception("Invalid uid."); + using (var wc = new WebClient()) + { + wc.Headers.Add(HttpRequestHeader.Cookie, credential); + var res = wc.DownloadString("http://pan.baidu.com/api/user/getinfo?user_list=[" + credential.Uid + "]"); + var obj = JsonConvert.DeserializeObject(res); + obj.success = true; + return obj; + } + } + catch (Exception ex) + { + return new UserInfoResult() { exception = ex }; + } + } + public static FileListResult GetFileList(string path, Credential credential) + { + try + { + using (var wc = new WebClient()) + { + wc.Headers.Add(HttpRequestHeader.Cookie, credential); + var res = wc.DownloadString("http://pan.baidu.com/api/list?page=1&num=10000000&dir=" + HttpUtility.UrlEncode(path)); + var obj = JsonConvert.DeserializeObject(res); + obj.success = true; + return obj; + } + } + catch (Exception ex) + { + return new FileListResult() { exception = ex }; + } + } + public static ThumbnailResult GetThumbnail(string path, Credential credential, int width = 125, int height = 90, int quality = 100) + { + try + { + using (var wc = new WebClient()) + { + wc.Headers.Add(HttpRequestHeader.Cookie, credential); + var res = wc.DownloadData("http://pcsdata.baidu.com/rest/2.0/pcs/thumbnail?app_id=250528&method=generate&path=" + HttpUtility.UrlEncode(path) + "&quality=" + quality + "&height=" + height + "&width=" + width); + return new ThumbnailResult() { success = true, image = res }; + } + } + catch (Exception ex) + { + return new ThumbnailResult() { exception = ex }; + } + } + public static GetDownloadResult GetDownload(string path, Credential credential) + { + try + { + using (var wc = new WebClient()) + { + wc.Headers.Add(HttpRequestHeader.Cookie, credential); + wc.Headers.Add(HttpRequestHeader.UserAgent, "netdisk;5.4.5.4;PC;PC-Windows;10.0.14393;WindowsBaiduYunGuanJia"); + var res = wc.DownloadString("http://d.pcs.baidu.com/rest/2.0/pcs/file?app_id=250528&method=locatedownload&ver=4.0&path=" + HttpUtility.UrlEncode(path)); + var obj = JsonConvert.DeserializeObject(res); + obj.success = true; + return obj; + } + } + catch (Exception ex) + { + return new GetDownloadResult() { exception = ex }; + } + } + public static FileOperationResult CreateFolder(string path, Credential credential) + { + try + { + using (var wc = new WebClient()) + { + wc.Headers.Add(HttpRequestHeader.Cookie, credential); + wc.Headers.Add(HttpRequestHeader.ContentType, "application/x-www-form-urlencoded"); + var str = "isdir=1&path=" + HttpUtility.UrlEncode(path); + var res = wc.UploadData("http://pan.baidu.com/api/create?a=commit", Encoding.UTF8.GetBytes(str)); + var obj = JsonConvert.DeserializeObject(Encoding.UTF8.GetString(res)); + obj.success = true; + return obj; + } + } + catch (Exception ex) + { + return new FileOperationResult() { exception = ex }; + } + } + public static FileOperationResult Copy(string path, string dest, string newname, Credential credential) + { + var str = "filelist=[{\"path\":\"" + HttpUtility.UrlEncode(path) + "\",\"dest\":\"" + HttpUtility.UrlEncode(dest) + "\",\"newname\":\"" + HttpUtility.UrlEncode(newname) + "\"}]"; + return FileOp("http://pan.baidu.com/api/filemanager?opera=copy&clienttype=8", str, credential); + } + public static FileOperationResult Delete(string path, Credential credential) + { + var str = "filelist=[\"" + HttpUtility.UrlEncode(path) + "\"]"; + return FileOp("http://pan.baidu.com/api/filemanager?opera=delete&clienttype=8", str, credential); + } + public static FileOperationResult Move(string path, string dest, string newname, Credential credential) + { + var str = "filelist=[{\"path\":\"" + HttpUtility.UrlEncode(path) + "\",\"dest\":\"" + HttpUtility.UrlEncode(dest) + "\",\"newname\":\"" + HttpUtility.UrlEncode(newname) + "\"}]"; + return FileOp("http://pan.baidu.com/api/filemanager?opera=move&clienttype=8", str, credential); + } + public static FileOperationResult Rename(string path, string newname, Credential credential) + { + var str = "filelist=[{\"path\":\"" + HttpUtility.UrlEncode(path) + "\",\"newname\":\"" + HttpUtility.UrlEncode(newname) + "\"}]"; + return FileOp("http://pan.baidu.com/api/filemanager?opera=rename&clienttype=8", str, credential); + } + private static FileOperationResult FileOp(string url, string str, Credential credential) + { + try + { + using (var wc = new WebClient()) + { + wc.Headers.Add(HttpRequestHeader.Cookie, credential); + wc.Headers.Add(HttpRequestHeader.ContentType, "application/x-www-form-urlencoded"); + var res = wc.UploadData(url, Encoding.UTF8.GetBytes(str)); + var obj = JsonConvert.DeserializeObject(Encoding.UTF8.GetString(res)); + if (obj.info.Length == 0 && obj.errno != 0) + return new FileOperationResult() { success = true, errno = obj.errno }; + else if (obj.info.Length == 0 || obj.errno != 0 && obj.info[0].errno == 0) + throw new Exception("Response data malformat."); + else + return new FileOperationResult() { success = true, errno = obj.info[0].errno, path = obj.info[0].path }; + } + } + catch (Exception ex) + { + return new FileOperationResult() { exception = ex }; + } + } + public static OfflineListResult GetOfflineList(Credential credential) + { + try + { + using (var wc = new WebClient()) + { + wc.Headers.Add(HttpRequestHeader.Cookie, credential); + var res1 = wc.DownloadString("http://pan.baidu.com/rest/2.0/services/cloud_dl?app_id=250528&method=list_task&need_task_info=1&status=255"); + var ltr = JsonConvert.DeserializeObject(res1); + if (ltr.task_info.Length == 0) return new OfflineListResult() { success = true, tasks = new OfflineListResult.Entry[0] }; + var str = "method=query_task&op_type=1&task_ids=" + HttpUtility.UrlEncode(string.Join(",", ltr.task_info.Select(e => e.task_id.ToString()))); + wc.Headers.Add(HttpRequestHeader.ContentType, "application/x-www-form-urlencoded"); + var res2 = wc.UploadData("http://pan.baidu.com/rest/2.0/services/cloud_dl?app_id=250528", Encoding.UTF8.GetBytes(str)); + var qtr = JsonConvert.DeserializeObject(Encoding.UTF8.GetString(res2)); + return new OfflineListResult() + { + tasks = ltr.task_info.Select(e => + { + var ai = qtr.task_info[e.task_id.ToString()]; + return new OfflineListResult.Entry() + { + create_time = e.create_time, + od_type = e.od_type, + save_path = e.save_path, + source_url = e.source_url, + task_id = e.task_id, + task_name = e.task_name, + file_size = ai.file_size, + finished_size = ai.finished_size, + status = ai.status + }; + }).ToArray(), + success = true + }; + } + } + catch (Exception ex) + { + return new OfflineListResult() { exception = ex }; + } + } + public static Result CancelOfflineTask(long taskid, Credential credential) + { + return OfflineTaskOp(taskid, "cancel_task", credential); + } + public static Result DeleteOfflineTask(long taskid, Credential credential) + { + return OfflineTaskOp(taskid, "delete_task", credential); + } + public static Result ClearOfflineTask(Credential credential) + { + return OfflineTaskOp(0, "clear_task", credential); + } + private static Result OfflineTaskOp(long taskid, string oper, Credential credential) + { + try + { + using (var wc = new WebClient()) + { + wc.Headers.Add(HttpRequestHeader.Cookie, credential); + wc.DownloadData("http://pan.baidu.com/rest/2.0/services/cloud_dl?app_id=250528&method=" + oper + "&task_id=" + taskid); + return new Result() { success = true }; + } + } + catch (Exception ex) + { + return new Result() { exception = ex }; + } + } + public static QueryLinkResult QueryLinkFiles(string link, Credential credential) + { + try + { + using (var wc = new WebClient()) + { + wc.Headers.Add(HttpRequestHeader.Cookie, credential); + wc.Headers.Add(HttpRequestHeader.UserAgent, "netdisk;5.4.5.4;PC;PC-Windows;10.0.14393;WindowsBaiduYunGuanJia"); + if (link.StartsWith("magnet:", StringComparison.OrdinalIgnoreCase)) + { + var res = wc.DownloadString("http://pan.baidu.com/rest/2.0/services/cloud_dl?app_id=250528&clienttype=8&method=query_magnetinfo&source_url=" + HttpUtility.UrlEncode(link)); + var obj = JsonConvert.DeserializeObject(res); + return new QueryLinkResult() { success = true, files = obj.magnet_info }; + } + else if (link.EndsWith(".torrent", StringComparison.OrdinalIgnoreCase)) + { + var res = wc.DownloadString("http://pan.baidu.com/rest/2.0/services/cloud_dl?app_id=250528&clienttype=8&method=query_sinfo&type=2&source_path=" + HttpUtility.UrlEncode(link)); + var obj = JsonConvert.DeserializeObject(res); + return new QueryLinkResult() { success = true, files = obj.torrent_info.file_info }; + } + else throw new Exception("Not a magnet link or a torrent file."); + } + } + catch (Exception ex) + { + return new QueryLinkResult() { exception = ex }; + } + } + public static AddOfflineTaskResult AddOfflineTask(string link, string savepath, Credential credential, int[] selected = null, string sha1 = "") + { + try + { + var str = "method=add_task&save_path=" + HttpUtility.UrlEncode(savepath) + "&"; + if (link.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || link.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) + str += "type=0&source_url=" + HttpUtility.UrlEncode(link); + else if (link.StartsWith("ed2k://", StringComparison.OrdinalIgnoreCase)) + str += "type=3&source_url=" + HttpUtility.UrlEncode(link); + else if (link.StartsWith("magnet:", StringComparison.OrdinalIgnoreCase)) + str += "type=4&task_from=5&source_url=" + HttpUtility.UrlEncode(link) + "&selected_idx=" + string.Join(",", selected.Select(i => i.ToString())); + else if (link.EndsWith(".torrent", StringComparison.OrdinalIgnoreCase)) + str += "type=2&task_from=5&file_sha1=" + sha1 + "&source_path=" + HttpUtility.UrlEncode(link) + "&selected_idx=" + string.Join(",", selected.Select(i => i.ToString())); + else throw new Exception("Link invalid."); + using (var wc = new WebClient()) + { + wc.Headers.Add(HttpRequestHeader.Cookie, credential); + wc.Headers.Add(HttpRequestHeader.ContentType, "application/x-www-form-urlencoded"); + wc.Headers.Add(HttpRequestHeader.UserAgent, "netdisk;5.4.5.4;PC;PC-Windows;10.0.14393;WindowsBaiduYunGuanJia"); + var res = wc.UploadData("http://pan.baidu.com/rest/2.0/services/cloud_dl?app_id=250528&clienttype=8", Encoding.UTF8.GetBytes(str)); + var obj = JsonConvert.DeserializeObject(Encoding.UTF8.GetString(res)); + obj.success = true; + return obj; + } + } + catch (Exception ex) + { + return new AddOfflineTaskResult() { exception = ex }; + } + } + public static ShareResult Share(string[] pathlist, Credential credential, string pwd = null) + { + try + { + if (pwd != null && pwd.Length != 4) throw new Exception("Length of pwd must be 4."); + var str = "path_list=[" + string.Join(",", pathlist.Select(p => '"' + HttpUtility.UrlEncode(p) + '"')) + "]&channel_list=[]&shorturl=1&"; + if (pwd == null) str += "public=1&schannel=0"; + else str += "public=0&schannel=4&pwd=" + pwd; + var rand = new Random(); + var logid = new string(Enumerable.Range(0, 100).Select(i => (char)('a' + rand.Next(26))).ToArray()); + using (var wc = new WebClient()) + { + wc.Headers.Add(HttpRequestHeader.Cookie, credential); + wc.Headers.Add(HttpRequestHeader.ContentType, "application/x-www-form-urlencoded"); + var res = wc.UploadData("http://pan.baidu.com/share/pset?clienttype=8&channel=00000000000000000000000000000000&version=5.4.5.4&devuid=123456&logid=" + logid, Encoding.UTF8.GetBytes(str)); + var obj = JsonConvert.DeserializeObject(Encoding.UTF8.GetString(res)); + obj.success = true; + return obj; + } + } + catch (Exception ex) + { + return new ShareResult() { exception = ex }; + } + } + public static TransferResult Transfer(string url, string path, Credential credential, string pwd = null) + { + try + { + using (var wc = new CookieAwareWebClient()) + { + wc.Cookies.Add(credential); + var str = wc.DownloadString(url); + var rurl = wc.ResponseUri.ToString(); + string shareid = null, uk = null; + if (rurl.Contains("/share/init")) + { + if (pwd == null) throw new Exception("Need password."); + shareid = Regex.Match(rurl, "shareid=(\\d+)").Groups[1].Value; + uk = Regex.Match(rurl, "uk=(\\d+)").Groups[1].Value; + wc.Headers.Add(HttpRequestHeader.Referer, rurl); + wc.Headers.Add(HttpRequestHeader.ContentType, "application/x-www-form-urlencoded"); + var res = wc.UploadData("http://pan.baidu.com/share/verify?shareid=" + shareid + "&uk=" + uk, Encoding.UTF8.GetBytes("vcode=&vcode_str=&pwd=" + pwd)); + var obj = JsonConvert.DeserializeObject(Encoding.UTF8.GetString(res)); + if (obj.errno != 0) throw new Exception("Password verification returned errno = " + obj.errno); + str = wc.DownloadString(url); + } + str = Regex.Match(str, "yunData.setData(.*)").Groups[1].Value.Trim(); + str = str.Substring(1, str.Length - 3); + var obj2 = JsonConvert.DeserializeObject(str); + str = "path=" + HttpUtility.UrlEncode(path) + "&filelist=[" + string.Join(",", obj2.file_list.list.Select(e => "\"" + HttpUtility.UrlEncode(e.path) + "\"")) + "]"; + wc.Headers.Add(HttpRequestHeader.ContentType, "application/x-www-form-urlencoded"); + wc.Headers.Add(HttpRequestHeader.Referer, url); + var rand = new Random(); + var logid = new string(Enumerable.Range(0, 100).Select(i => (char)('a' + rand.Next(26))).ToArray()); + var res2 = wc.UploadData("http://pan.baidu.com/share/transfer?channel=chunlei&clienttype=0&web=1&app_id=250528&ondup=newcopy&async=1&shareid=" + shareid + "&from=" + uk + "&logid=" + logid + "&bdstoken=" + obj2.bdstoken, Encoding.UTF8.GetBytes(str)); + var obj3 = JsonConvert.DeserializeObject(Encoding.UTF8.GetString(res2)); + obj3.success = true; + return obj3; + } + } + catch (Exception ex) + { + return new TransferResult() { exception = ex }; + } + } + public static CommitUploadResult SimpleUpload(string localpath, string remotepath, Credential credential, string host = "c.pcs.baidu.com") + { + try + { + var size = new FileInfo(localpath).Length; + var mtime = (long)(new FileInfo(localpath).LastAccessTime.Subtract(new DateTime(1970, 1, 1))).TotalSeconds; + var md5 = UploadHelper.GetMD5HashFromFile(localpath); + var str = "path=" + remotepath + "&size=" + size + "&isdir=0&block_list=[\"" + md5 + "\"]&autoinit=1&local_mtime=" + mtime + "&method=post"; + using (var wc = new PatientWebClient()) + { + wc.Headers.Add(HttpRequestHeader.Cookie, credential); + wc.Headers.Add(HttpRequestHeader.ContentType, "application/x-www-form-urlencoded"); + var res = wc.UploadData("http://pan.baidu.com/api/precreate?clienttype=8", Encoding.UTF8.GetBytes(str)); + var obj = JsonConvert.DeserializeObject(Encoding.UTF8.GetString(res)); + if (obj.errno != 0) throw new Exception("precreate had errno = " + obj.errno); + var boundary = GetBoundary(); + wc.Headers.Add(HttpRequestHeader.ContentType, "multipart/form-data; boundary=" + boundary); + str = "--" + boundary + "\r\nContent-Disposition: form-data; name=\"filename\"; filename=\"name\"\r\nContent-Type: application/octet-stream\r\n\r\n"; + var str2 = "\r\n--" + boundary + "--\r\n"; + var data = Encoding.UTF8.GetBytes(str).Concat(File.ReadAllBytes(localpath)).Concat(Encoding.UTF8.GetBytes(str2)).ToArray(); + res = wc.UploadData("http://" + host + "/rest/2.0/pcs/superfile2?app_id=250528&method=upload&path=" + HttpUtility.UrlEncode(remotepath) + "&uploadid=" + HttpUtility.UrlEncode(obj.uploadid) + "&partseq=0&partoffset=0", data); + str = "path=" + remotepath + "&size=" + size + "&isdir=0&uploadid=" + HttpUtility.UrlEncode(obj.uploadid) + "&block_list=[\"" + md5 + "\"]&method=post&rtype=2&sequence=1&mode=1&local_mtime=" + mtime; + res = wc.UploadData("http://pan.baidu.com/api/create?a=commit&clienttype=8", Encoding.UTF8.GetBytes(str)); + var obj2 = JsonConvert.DeserializeObject(Encoding.UTF8.GetString(res)); + obj2.success = true; + return obj2; + } + } + catch (Exception ex) + { + return new CommitUploadResult() { exception = ex }; + } + } + public static RapidUploadResult RapidUpload(FileProperty prop, string path, Credential credential) + { + try + { + using (var wc = new WebClient()) + { + wc.Headers.Add(HttpRequestHeader.Cookie, credential); + wc.Headers.Add(HttpRequestHeader.ContentType, "application/x-www-form-urlencoded"); + var str = "path=" + HttpUtility.UrlEncode(path) + "&content-length=" + prop.size + "&content-md5=" + prop.md5 + "&slice-md5=" + prop.slice_md5 + "&content-crc32=" + prop.crc32 + "&local_mtime=" + prop.mtime + "&block_list=[" + string.Join(",", prop.blocks.Select(h => '"' + h + '"')) + "]&rtype=2"; + var res = wc.UploadData("http://pan.baidu.com/api/rapidupload?clienttype=8", Encoding.UTF8.GetBytes(str)); + var obj = JsonConvert.DeserializeObject(Encoding.UTF8.GetString(res)); + obj.success = true; + return obj; + } + } + catch (Exception ex) + { + return new RapidUploadResult() { exception = ex }; + } + } + public static InitUploadResult InitUpload(FileProperty prop, string path, Credential credential) + { + try + { + using (var wc = new WebClient()) + { + wc.Headers.Add(HttpRequestHeader.Cookie, credential); + wc.Headers.Add(HttpRequestHeader.ContentType, "application/x-www-form-urlencoded"); + var str = "path=" + HttpUtility.UrlEncode(path) + "&size=" + prop.size + "&isdir=0&local_mtime=" + prop.mtime + "&block_list=[" + string.Join(",", prop.blocks.Select(h => '"' + h + '"')) + "]&autoinit=1&method=post"; + var res = wc.UploadData("http://pan.baidu.com/api/precreate?clienttype=8", Encoding.UTF8.GetBytes(str)); + var obj = JsonConvert.DeserializeObject(Encoding.UTF8.GetString(res)); + obj.success = true; + if (InitUploadFile != null && obj.errno == 0) + { + InitUploadFile(prop.path, prop.blocks.Length, obj.block_list.Length); + } + return obj; + } + } + catch (Exception ex) + { + return new InitUploadResult() { exception = ex }; + } + } + public static Result UploadBlock(FileProperty prop, string path, InitUploadResult session, FileStream stream, int blockid, string host, Credential credential) + { + try + { + using (var wc = new PatientWebClient()) + { + if (StartUploadBlock != null) + { + StartUploadBlock(prop.path, blockid + 1, prop.blocks.Length); + } + var boundary = GetBoundary(); + wc.Headers.Add(HttpRequestHeader.Cookie, credential); + wc.Headers.Add(HttpRequestHeader.ContentType, "multipart/form-data; boundary=" + boundary); + var str = "--" + boundary + "\r\nContent-Disposition: form-data; name=\"filename\"; filename=\"name\"\r\nContent-Type: application/octet-stream\r\n\r\n"; + var str2 = "\r\n--" + boundary + "--\r\n"; + stream.Seek((long)blockid * 4 * 1024 * 1024, SeekOrigin.Begin); + var fdata = new byte[4 * 1024 * 1024]; + var len = stream.Read(fdata, 0, fdata.Length); + if (len < fdata.Length) + { + var arr = new byte[len]; + Array.Copy(fdata, arr, len); + fdata = arr; + } + var data = Encoding.UTF8.GetBytes(str).Concat(fdata).Concat(Encoding.UTF8.GetBytes(str2)).ToArray(); + var res = wc.UploadData("http://" + host + "/rest/2.0/pcs/superfile2?app_id=250528&method=upload&path=" + HttpUtility.UrlEncode(path) + "&uploadid=" + HttpUtility.UrlEncode(session.uploadid) + "&partseq=" + blockid + "&partoffset=" + (long)blockid * 4 * 1024 * 1024, data); + var obj = JsonConvert.DeserializeObject(Encoding.UTF8.GetString(res)); + if (obj.md5 != prop.blocks[blockid]) throw new Exception("MD5 mismatch."); + if (ComplateUploadBlock != null) + { + ComplateUploadBlock(prop.path, blockid + 1, prop.blocks.Length); + } + return new Result() { success = true }; + } + } + catch (Exception ex) + { + return new Result() { exception = ex }; + } + } + public static CommitUploadResult CommitUpload(FileProperty prop, string path, InitUploadResult session, Credential credential) + { + try + { + using (var wc = new WebClient()) + { + wc.Headers.Add(HttpRequestHeader.Cookie, credential); + wc.Headers.Add(HttpRequestHeader.ContentType, "application/x-www-form-urlencoded"); + var str = "path=" + HttpUtility.UrlEncode(path) + "&size=" + prop.size + "&isdir=0&uploadid=" + HttpUtility.UrlEncode(session.uploadid) + "&block_list=[" + string.Join(",", prop.blocks.Select(h => '"' + h + '"')) + "]&method=post&rtype=2&sequence=1&mode=1&local_mtime=" + prop.mtime; + var res = wc.UploadData("http://pan.baidu.com/api/create?a=commit&clienttype=8", Encoding.UTF8.GetBytes(str)); + var obj = JsonConvert.DeserializeObject(Encoding.UTF8.GetString(res)); + obj.success = true; + if (UpdateComplate != null) + { + UpdateComplate(prop.path); + } + return obj; + } + } + catch (Exception ex) + { + return new CommitUploadResult() { exception = ex }; + } + } + public static CommitUploadResult ChunkedUpload(string localpath, string remotepath, Credential credential) + { + try + { + + var servers = GetUploadServers(credential); + if (!servers.success) throw servers.exception; + var prop = UploadHelper.GetFileProperty(localpath); + var session = InitUpload(prop, remotepath, credential); + if (!session.success) throw session.exception; + if (session.errno != 0) throw new Exception("Init upload returned errno = " + session.errno); + using (var fs = new FileStream(localpath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + { + for(int i = 0; i < prop.blocks.Length; i++) + { + var res = UploadBlock(prop, remotepath, session, fs, i, servers.servers[0], credential); + if (!res.success) throw res.exception; + } + } + var comres = CommitUpload(prop, remotepath, session, credential); + if (!comres.success) throw comres.exception; + if (comres.errno != 0) throw new Exception("Commit upload returned errno = " + comres.errno); + return comres; + } + catch (Exception ex) + { + throw ex; + } + } + public static GetUploadServersResult GetUploadServers(Credential credential) + { + try + { + using (var wc = new WebClient()) + { + wc.Headers.Add(HttpRequestHeader.Cookie, credential); + wc.Headers.Add(HttpRequestHeader.UserAgent, "netdisk;5.4.5.4;PC;PC-Windows;10.0.14393;WindowsBaiduYunGuanJia"); + var res = wc.DownloadString("http://d.pcs.baidu.com/rest/2.0/pcs/file?app_id=250528&method=locateupload&esl=1&ehps=0&upload_version=2.0"); + var obj = JsonConvert.DeserializeObject(res); + return new GetUploadServersResult() { success = true, servers = obj.servers.Select(s => Regex.Match(s.server, ":\\/\\/(.+)$").Groups[1].Value).ToArray() }; + } + } + catch (Exception ex) + { + return new GetUploadServersResult() { exception = ex }; + } + } + private static string GetBoundary() + { + var rand = new Random(); + var sb = new StringBuilder(); + for (int i = 0; i < 28; i++) sb.Append('-'); + for (int i = 0; i < 15; i++) sb.Append((char)(rand.Next(0, 26) + 'a')); + var boundary = sb.ToString(); + return boundary; + } + private class FileOpResult + { + public int errno; + public Entry[] info; + public class Entry + { + public int errno; + public string path; + } + } + private class ListTaskResult + { + public Entry[] task_info; + public class Entry + { + public long create_time; + public int od_type; + public string save_path; + public string source_url; + public long task_id; + public string task_name; + } + } + private class QueryTaskResult + { + public Dictionary task_info; + public class Entry + { + public long file_size; + public long finished_size; + public int status; + } + } + private class QueryTorrentResult + { + public TorrentInfo torrent_info; + public class TorrentInfo + { + public QueryLinkResult.Entry[] file_info; + public string sha1; + } + } + private class QueryMagnetResult + { + public QueryLinkResult.Entry[] magnet_info; + } + private class VerifyPwdResult + { + public int errno; + } + private class SharePageData + { + public string bdstoken; + public FileList file_list; + public class FileList + { + public Entry[] list; + public class Entry + { + public string path; + } + } + } + private class SuperFileResponse + { + public string md5; + } + private class LocateUploadResponse + { + public Entry[] servers; + public class Entry + { + public string server; + } + } + } +} diff --git a/NetDisk/PatientWebClient.cs b/NetDisk/PatientWebClient.cs new file mode 100644 index 0000000..c88e2af --- /dev/null +++ b/NetDisk/PatientWebClient.cs @@ -0,0 +1,22 @@ +using System; +using System.Net; + +class PatientWebClient : WebClient +{ + public int Timeout = 300000; + public PatientWebClient(int Timeout): base() + { + this.Timeout = Timeout; + } + public PatientWebClient() : base() + { + + } + protected override WebRequest GetWebRequest(Uri uri) + { + WebRequest w = base.GetWebRequest(uri); + w.Timeout = Timeout; + (w as HttpWebRequest).ReadWriteTimeout = Timeout; + return w; + } +} diff --git a/NetDisk/Properties/AssemblyInfo.cs b/NetDisk/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..590cb02 --- /dev/null +++ b/NetDisk/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// 有关程序集的一般信息由以下 +// 控制。更改这些特性值可修改 +// 与程序集关联的信息。 +[assembly: AssemblyTitle("NetDisk")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("NetDisk")] +[assembly: AssemblyCopyright("Copyright © 2016")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +//将 ComVisible 设置为 false 将使此程序集中的类型 +//对 COM 组件不可见。 如果需要从 COM 访问此程序集中的类型, +//请将此类型的 ComVisible 特性设置为 true。 +[assembly: ComVisible(false)] + +// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID +[assembly: Guid("74eca1bf-6508-417d-bc57-db00daeca618")] + +// 程序集的版本信息由下列四个值组成: +// +// 主版本 +// 次版本 +// 生成号 +// 修订号 +// +//可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值, +// 方法是按如下所示使用“*”: : +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/NetDisk/Result.cs b/NetDisk/Result.cs new file mode 100644 index 0000000..49c52e0 --- /dev/null +++ b/NetDisk/Result.cs @@ -0,0 +1,155 @@ +using System; +using System.Net; + +namespace NetDisk +{ + public class Result + { + public bool success; + public Exception exception; + } + public class QuotaResult : Result + { + public int errno; + public long total; + public long free; + public bool expire; + public long used; + } + public class UserInfoResult : Result + { + public int errno; + public Entry[] records; + public class Entry + { + public string avatar_url; + public string uname; + public string priority_name; + } + } + public class FileListResult : Result + { + public int errno; + public Entry[] list; + public class Entry + { + public int isdir; + public string path; + public string server_filename; + public long size; + public long server_mtime; + public string md5; + } + } + public class ThumbnailResult : Result + { + public byte[] image; + } + public class GetDownloadResult : Result + { + public Entry[] urls; + public class Entry + { + public int rank; + public string url; + } + } + public class FileOperationResult : Result + { + public int errno; + public string path; + } + public class OfflineListResult : Result + { + public Entry[] tasks; + public class Entry + { + public long create_time; + public int od_type; + public string save_path; + public string source_url; + public long task_id; + public string task_name; + public long file_size; + public long finished_size; + public int status; + } + } + public class QueryLinkResult : Result + { + public Entry[] files; + public string sha1; + public class Entry + { + public string file_name; + public long size; + } + } + public class AddOfflineTaskResult : Result + { + public int rapid_download; + public long task_id; + } + public class ShareResult : Result + { + public int errno; + public long shareid; + public string link; + public string shorturl; + } + public class TransferResult : Result + { + public int errno; + public Extra extra; + public class Extra + { + public Entry[] list; + public class Entry + { + public string from; + public string to; + } + } + } + public class InitUploadResult : Result + { + public int[] block_list; + public int errno; + public string uploadid; + } + public class CommitUploadResult : Result + { + public long ctime; + public int errno; + public long fs_id; + public int isdir; + public string md5; + public long mtime; + public string name; + public string path; + public long size; + } + public class RapidUploadResult : Result + { + public int errno; + public FileListResult.Entry info; + } + public class LoginResult : Result + { + public int errno; + public Credential credential; + } + public class LoginCheckResult : Result + { + public bool needVCode; + public string codeString; + public string verifyCode; + public byte[] image; + public Cookie baiduid; + public string ltoken; + } + public class GetUploadServersResult: Result + { + public string[] servers; + } +} diff --git a/NetDisk/UploadHelper.cs b/NetDisk/UploadHelper.cs new file mode 100644 index 0000000..f2e88df --- /dev/null +++ b/NetDisk/UploadHelper.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Security.Cryptography; +using System.Text; +using DamienG.Security.Cryptography; + +namespace NetDisk +{ + public static class UploadHelper + { + public static FileProperty GetFileProperty(string path) + { + var ret = new FileProperty() { path = path }; + var info = new FileInfo(path); + ret.size = info.Length; + ret.mtime= (long)(info.LastAccessTime.Subtract(new DateTime(1970, 1, 1))).TotalSeconds; + ret.md5 = GetMD5HashFromFile(path); + using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + { + var md5 = new MD5CryptoServiceProvider(); + var arr = new byte[4 * 1024 * 1024]; + var len = fs.Read(arr, 0, 256 * 1024); + ret.slice_md5 = ByteArrayToHexString(md5.ComputeHash(arr, 0, len)); + fs.Seek(0, SeekOrigin.Begin); + var blocks = new List(); + while (true) + { + len = fs.Read(arr, 0, 4 * 1024 * 1024); + if (len <= 0) break; + blocks.Add(ByteArrayToHexString(md5.ComputeHash(arr, 0, len))); + } + ret.blocks = blocks.ToArray(); + fs.Seek(0, SeekOrigin.Begin); + var crc32 = new Crc32(); + ret.crc32 = string.Empty; + foreach (byte b in crc32.ComputeHash(fs)) ret.crc32 += b.ToString("x2").ToLower(); + } + return ret; + } + public static string GetMD5HashFromFile(string fileName) + { + using (var file = new FileStream(fileName, FileMode.Open)) + { + var md5 = new MD5CryptoServiceProvider(); + var retVal = md5.ComputeHash(file); + return ByteArrayToHexString(retVal); + } + } + private static string ByteArrayToHexString(byte[] arr) + { + var sb = new StringBuilder(); + for (int i = 0; i < arr.Length; i++) + { + sb.Append(arr[i].ToString("x2")); + } + return sb.ToString(); + } + } + public class FileProperty + { + public string path; + public long size; + public string md5; + public string slice_md5; + public string crc32; + public long mtime; + public string[] blocks; + } +} diff --git a/NetDisk/packages.config b/NetDisk/packages.config new file mode 100644 index 0000000..7c276ed --- /dev/null +++ b/NetDisk/packages.config @@ -0,0 +1,4 @@ + + + + \ No newline at end of file