├── .gitignore ├── FrmFilterTester.Designer.cs ├── FrmFilterTester.cs ├── FrmFilterTester.resx ├── FrmMain.Designer.cs ├── FrmMain.cs ├── FrmMain.resx ├── LICENSE ├── Program.cs ├── Properties ├── AssemblyInfo.cs ├── Resources.Designer.cs ├── Resources.resx ├── Settings.Designer.cs └── Settings.settings ├── QiNiuDrive.csproj ├── QiNiuDrive.csproj.DotSettings ├── README.md ├── Resources ├── FrmMain │ ├── qiniu_logo.png │ ├── tab_button_setting_chosen.png │ └── unknown.jpg └── Icons │ ├── icon.ico │ └── logo.png ├── VERSION.json ├── app.config ├── imgs └── SyncSetting.png └── libs.rar /.gitignore: -------------------------------------------------------------------------------- 1 | # Build Folders (you can keep bin if you'd like, to store dlls and pdbs) 2 | [Bb]in/ 3 | [Oo]bj/ 4 | 5 | # mstest test results 6 | TestResults 7 | 8 | ## Ignore Visual Studio temporary files, build results, and 9 | ## files generated by popular Visual Studio add-ons. 10 | 11 | # User-specific files 12 | *.suo 13 | *.user 14 | *.sln.docstates 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Rr]elease/ 19 | x64/ 20 | *_i.c 21 | *_p.c 22 | *.ilk 23 | *.meta 24 | *.obj 25 | *.pch 26 | *.pdb 27 | *.pgc 28 | *.pgd 29 | *.rsp 30 | *.sbr 31 | *.tlb 32 | *.tli 33 | *.tlh 34 | *.tmp 35 | *.log 36 | *.vspscc 37 | *.vssscc 38 | .builds 39 | 40 | # Visual C++ cache files 41 | ipch/ 42 | *.aps 43 | *.ncb 44 | *.opensdf 45 | *.sdf 46 | 47 | # Visual Studio profiler 48 | *.psess 49 | *.vsp 50 | *.vspx 51 | 52 | # Guidance Automation Toolkit 53 | *.gpState 54 | 55 | # ReSharper is a .NET coding add-in 56 | _ReSharper* 57 | 58 | # NCrunch 59 | *.ncrunch* 60 | .*crunch*.local.xml 61 | 62 | # Installshield output folder 63 | [Ee]xpress 64 | 65 | # DocProject is a documentation generator add-in 66 | DocProject/buildhelp/ 67 | DocProject/Help/*.HxT 68 | DocProject/Help/*.HxC 69 | DocProject/Help/*.hhc 70 | DocProject/Help/*.hhk 71 | DocProject/Help/*.hhp 72 | DocProject/Help/Html2 73 | DocProject/Help/html 74 | 75 | # Click-Once directory 76 | publish 77 | 78 | # Publish Web Output 79 | *.Publish.xml 80 | 81 | # NuGet Packages Directory 82 | packages 83 | 84 | # Windows Azure Build Output 85 | csx 86 | *.build.csdef 87 | 88 | # Windows Store app package directory 89 | AppPackages/ 90 | 91 | # Others 92 | [Bb]in 93 | [Oo]bj 94 | sql 95 | TestResults 96 | [Tt]est[Rr]esult* 97 | *.Cache 98 | ClientBin 99 | [Ss]tyle[Cc]op.* 100 | ~$* 101 | *.dbmdl 102 | Generated_Code #added for RIA/Silverlight projects 103 | 104 | # Backup & report files from converting an old project file to a newer 105 | # Visual Studio version. Backup files are not needed, because we have git ;-) 106 | _UpgradeReport_Files/ 107 | Backup*/ 108 | UpgradeLog*.XML 109 | 110 | *.sln 111 | libs/ -------------------------------------------------------------------------------- /FrmFilterTester.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace QiNiuDrive 2 | { 3 | partial class FrmFilterTester 4 | { 5 | /// 6 | /// Required designer variable. 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// Clean up any resources being used. 12 | /// 13 | /// true if managed resources should be disposed; otherwise, false. 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows Form Designer generated code 24 | 25 | /// 26 | /// Required method for Designer support - do not modify 27 | /// the contents of this method with the code editor. 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.lsvFiles = new System.Windows.Forms.ListView(); 32 | this.columnHeader1 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); 33 | this.columnHeader2 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); 34 | this.columnHeader3 = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader())); 35 | this.SuspendLayout(); 36 | // 37 | // lsvFiles 38 | // 39 | this.lsvFiles.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] { 40 | this.columnHeader1, 41 | this.columnHeader2, 42 | this.columnHeader3}); 43 | this.lsvFiles.FullRowSelect = true; 44 | this.lsvFiles.Location = new System.Drawing.Point(6, 76); 45 | this.lsvFiles.Name = "lsvFiles"; 46 | this.lsvFiles.ShowItemToolTips = true; 47 | this.lsvFiles.Size = new System.Drawing.Size(550, 345); 48 | this.lsvFiles.TabIndex = 0; 49 | this.lsvFiles.UseCompatibleStateImageBehavior = false; 50 | this.lsvFiles.View = System.Windows.Forms.View.Details; 51 | // 52 | // columnHeader1 53 | // 54 | this.columnHeader1.Text = "文件名"; 55 | this.columnHeader1.Width = 378; 56 | // 57 | // columnHeader2 58 | // 59 | this.columnHeader2.Text = "过滤"; 60 | this.columnHeader2.TextAlign = System.Windows.Forms.HorizontalAlignment.Center; 61 | this.columnHeader2.Width = 50; 62 | // 63 | // columnHeader3 64 | // 65 | this.columnHeader3.Text = "应用规则"; 66 | this.columnHeader3.Width = 100; 67 | // 68 | // FrmFilterTester 69 | // 70 | this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 17F); 71 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 72 | this.ClientSize = new System.Drawing.Size(563, 471); 73 | this.Controls.Add(this.lsvFiles); 74 | this.Font = new System.Drawing.Font("Microsoft YaHei", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 75 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None; 76 | this.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); 77 | this.Name = "FrmFilterTester"; 78 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; 79 | this.Text = "FrmFilterTester"; 80 | this.MouseClick += new System.Windows.Forms.MouseEventHandler(this.FrmFilterTester_MouseClick); 81 | this.MouseDown += new System.Windows.Forms.MouseEventHandler(this.FrmFilterTester_MouseDown); 82 | this.MouseMove += new System.Windows.Forms.MouseEventHandler(this.FrmFilterTester_MouseMove); 83 | this.MouseUp += new System.Windows.Forms.MouseEventHandler(this.FrmFilterTester_MouseUp); 84 | this.ResumeLayout(false); 85 | 86 | } 87 | 88 | #endregion 89 | 90 | private System.Windows.Forms.ListView lsvFiles; 91 | private System.Windows.Forms.ColumnHeader columnHeader1; 92 | private System.Windows.Forms.ColumnHeader columnHeader2; 93 | private System.Windows.Forms.ColumnHeader columnHeader3; 94 | } 95 | } -------------------------------------------------------------------------------- /FrmFilterTester.cs: -------------------------------------------------------------------------------- 1 | #region 文档说明 2 | /* ****************************************************************************************************** 3 | * 文档作者:无闻 4 | * 创建日期:2013 年 10 月 20 日 5 | * 文档用途:七牛云盘过滤规则测试窗口 6 | * ------------------------------------------------------------------------------------------------------ 7 | * 修改记录: 8 | * ------------------------------------------------------------------------------------------------------ 9 | * 参考文献: 10 | * ******************************************************************************************************/ 11 | #endregion 12 | 13 | #region 命名空间引用 14 | using System.IO; 15 | using System.Windows.Forms; 16 | using System.Drawing; 17 | using System.Collections.Generic; 18 | 19 | using CharmCommonMethod; 20 | using CharmControlLibrary; 21 | #endregion 22 | 23 | namespace QiNiuDrive 24 | { 25 | // 七牛云盘过滤规则测试窗口 26 | public partial class FrmFilterTester : Form 27 | { 28 | #region 常量 29 | private const int TITLE_HEIGHT = 30; // 标题栏高度 30 | #endregion 31 | 32 | #region 字段 33 | // * 用户控件 * 34 | private List mControls; // 控件集合 35 | // 0-测试目录文本框 36 | private List mCharmControls; // Charm 控件集合 37 | private readonly ToolTip mToolTip = new ToolTip(); // 工具提示文本控件 38 | #endregion 39 | 40 | #region 窗体事件 41 | // 窗体构造方法 42 | public FrmFilterTester() 43 | { 44 | // 初始化组件 45 | InitializeComponent(); 46 | // 设置双缓冲模式 47 | this.SetStyle(ControlStyles.AllPaintingInWmPaint | //不擦除背景 ,减少闪烁 48 | ControlStyles.OptimizedDoubleBuffer | //双缓冲 49 | ControlStyles.UserPaint, //使用自定义的重绘事件,减少闪烁 50 | true); 51 | this.UpdateStyles(); 52 | // 初始化设置 53 | InitializeSetting(); 54 | } 55 | 56 | // 窗体鼠标单击事件 57 | private void FrmFilterTester_MouseClick(object sender, MouseEventArgs e) 58 | { 59 | // 调用事件 60 | CharmControl.MouseClickEvent(e, mCharmControls); 61 | } 62 | 63 | // 窗体鼠标按下事件 64 | private void FrmFilterTester_MouseDown(object sender, MouseEventArgs e) 65 | { 66 | // 调用事件 67 | if (!CharmControl.MouseDownEvent(e, mCharmControls, this)) 68 | APIOperation.MoveNoBorderForm(this, e); 69 | } 70 | 71 | // 窗体鼠标移动事件 72 | private void FrmFilterTester_MouseMove(object sender, MouseEventArgs e) 73 | { 74 | // 调用事件 75 | CharmControl.MouseMoveEvent(e, mCharmControls, this, mToolTip); 76 | } 77 | 78 | // 窗体鼠标弹起事件 79 | private void FrmFilterTester_MouseUp(object sender, MouseEventArgs e) 80 | { 81 | // 调用事件 82 | CharmControl.MouseUpEvent(e, mCharmControls, this); 83 | } 84 | #endregion 85 | 86 | #region 重载事件 87 | // 控件重绘事件 88 | protected override void OnPaint(PaintEventArgs e) 89 | { 90 | // 获取绘制对象 91 | Graphics g = e.Graphics; 92 | 93 | // 测试目录 94 | g.DrawString("模拟同步目录:", this.Font, Brushes.Black, 20, TITLE_HEIGHT + 15); 95 | 96 | // 绘制控件 97 | CharmControl.PaintEvent(e.Graphics, mCharmControls); 98 | 99 | // 重置重绘索引 100 | //mMultipleSettingRedrawIndex = 0; 101 | } 102 | #endregion 103 | 104 | #region 控件事件 105 | // 关闭按钮被单击事件 106 | private void btnClose_MouseClick(object sender, MouseEventArgs e) 107 | { 108 | this.Hide(); 109 | } 110 | 111 | // 浏览路径按钮被单击事件 112 | private void btnViewPath_MouseClick(object sender, MouseEventArgs e) 113 | { 114 | string testDir = ((CharmTextBox)mControls[0]).Text; 115 | // 创建并实例化文件浏览对话框 116 | FolderBrowserDialog folderBrowserFialog = new FolderBrowserDialog 117 | { 118 | Description = "请选择测试目录", 119 | SelectedPath = testDir 120 | }; 121 | 122 | // 显示对话框并判断用户是否指定新的目录 123 | if (folderBrowserFialog.ShowDialog() == DialogResult.OK) 124 | { 125 | testDir = folderBrowserFialog.SelectedPath; // 用户指定新的目录 126 | ((CharmTextBox)mControls[0]).Text = testDir; 127 | IniOperation.WriteValue(FrmMain.APP_CFG_PATH, "cache", "test_filter_dir", testDir); 128 | } 129 | } 130 | 131 | // 测试规则按钮被单击事件 132 | private void btnTest_MouseClick(object sender, MouseEventArgs e) 133 | { 134 | // 初始化 135 | lsvFiles.Items.Clear(); 136 | List filterList = new List(); 137 | 138 | // 加载过滤器列表 139 | FrmMain.LoadFilterList(filterList); 140 | 141 | // 开始过滤 142 | LoadLocalFiles(filterList, ""); 143 | } 144 | #endregion 145 | 146 | #region 方法 147 | // 初始化设置 148 | private void InitializeSetting() 149 | { 150 | // 设置窗体属性 151 | this.Icon = Properties.Resources.icon; 152 | this.Text = "过滤规则测试"; 153 | 154 | // 绘制窗体背景 155 | DrawFormBackground(); 156 | 157 | #region 创建窗体组件 158 | // 创建关闭系统按钮 159 | CharmSysButton btnClose = new CharmSysButton 160 | { 161 | SysButtonType = SysButtonType.Close, 162 | ToolTipText = "关闭", 163 | Location = new Point(this.Width - 44, 1) 164 | }; 165 | 166 | // 创建测试目录文本框 167 | CharmTextBox txtTestDir = new CharmTextBox 168 | { 169 | Location = new Point(110, 10 + TITLE_HEIGHT), 170 | Width = 330 171 | }; 172 | // 创建浏览路径按钮 173 | CharmButton btnViewPath = new CharmButton 174 | { 175 | ButtonType = ButtonType.Classic_Size_08223, 176 | Text = "浏览路径", 177 | ForeColor = Color.DarkGreen, 178 | Location = new Point(455, 12 + TITLE_HEIGHT) 179 | }; 180 | 181 | // 创建测试规则按钮 182 | CharmButton btnTest = new CharmButton 183 | { 184 | ButtonType = ButtonType.Classic_Size_12425, 185 | Text = "开始测试规则", 186 | ForeColor = Color.MediumSlateBlue, 187 | Location = new Point(420, 435), 188 | }; 189 | 190 | // 将控件添加到集合中 191 | this.Controls.Add(txtTestDir); 192 | 193 | // 创建控件集合 194 | mControls = new List { txtTestDir }; 195 | mCharmControls = new List { btnClose, btnViewPath, btnTest }; 196 | 197 | // 关联控件事件 198 | btnClose.MouseClick += btnClose_MouseClick; 199 | btnViewPath.MouseClick += btnViewPath_MouseClick; 200 | btnTest.MouseClick += btnTest_MouseClick; 201 | #endregion 202 | 203 | // 加载本地设置 204 | LoadLocalSetting(); 205 | } 206 | 207 | // 绘制窗体背景 208 | private void DrawFormBackground() 209 | { 210 | // 加载窗体背景图像资源 211 | Bitmap imgBkg = new Bitmap(this.Width, this.Height); 212 | 213 | #region 绘制窗体内容 214 | using (Graphics g = Graphics.FromImage(imgBkg)) 215 | { 216 | // 设置参数 217 | const int bottomHeight = 45; 218 | 219 | // 绘制标题栏背景 220 | SolidBrush sb = new SolidBrush(Color.DodgerBlue); 221 | g.FillRectangle(sb, 0, 0, this.Width, TITLE_HEIGHT); 222 | // 绘制窗口图标 223 | g.DrawImage(Properties.Resources.logo, new Rectangle(6, 5, 20, 20)); 224 | // 绘制窗口标题 225 | g.DrawString("过滤规则测试 - 七牛云盘", 226 | new Font("微软雅黑", 10, FontStyle.Bold), Brushes.White, new Point(30, 6)); 227 | // 绘制主面板区 228 | sb.Color = Color.FromArgb(255, Color.WhiteSmoke); 229 | g.FillRectangle(sb, 1, TITLE_HEIGHT, this.Width - 2, this.Height - TITLE_HEIGHT - bottomHeight); 230 | // 绘制横线 231 | g.DrawLine(Pens.DarkGray, 1, this.Height - bottomHeight, this.Width - 2, this.Height - bottomHeight); 232 | // 绘制按钮区 233 | sb.Color = Color.FromArgb(70, Color.LightGray); 234 | g.FillRectangle(sb, 1, this.Height - bottomHeight, this.Width - 2, bottomHeight); 235 | // 绘制边框 236 | g.DrawRectangle(new Pen(Color.DimGray), 0, 0, this.Width - 1, this.Height - 1); 237 | } 238 | #endregion 239 | 240 | // 设置背景资源 241 | this.BackgroundImage = new Bitmap(imgBkg); 242 | 243 | // 释放系统资源 244 | imgBkg.Dispose(); 245 | } 246 | 247 | // 加载本地设置 248 | private void LoadLocalSetting() 249 | { 250 | // 测试目录 251 | string testDir = IniOperation.ReadValue(FrmMain.APP_CFG_PATH, "cache", "test_filter_dir"); 252 | if (testDir.Length > 0 && Directory.Exists(testDir)) 253 | { 254 | ((CharmTextBox)mControls[0]).Text = testDir; 255 | } 256 | } 257 | 258 | // 加载本地文件(可用于递归操作) 259 | private void LoadLocalFiles(List filterList, string dirPrefix) 260 | { 261 | // 检查文件 262 | DirectoryInfo di = new DirectoryInfo(((CharmTextBox)mControls[0]).Text + "\\" + dirPrefix); 263 | try 264 | { 265 | foreach (FileInfo fi in di.GetFiles()) 266 | { 267 | string fileName = (dirPrefix.TrimStart('\\') + "\\" + fi.Name).TrimStart('\\').Replace("\\", "/"); 268 | 269 | bool isFilter = false; 270 | foreach (Filter f in filterList) 271 | { 272 | switch (f.Type) 273 | { 274 | case FilterType.Contain: 275 | if (fileName.Contains(f.Name)) 276 | { 277 | lsvFiles.Items.Add(new ListViewItem(new[] { fileName, "是", "*" + f.Name + "*" })); 278 | isFilter = true; 279 | } 280 | break; 281 | case FilterType.Prefix: 282 | if (fileName.StartsWith(f.Name)) 283 | { 284 | lsvFiles.Items.Add(new ListViewItem(new[] { fileName, "是", f.Name + "*" })); 285 | isFilter = true; 286 | } 287 | break; 288 | case FilterType.Suffix: 289 | if (fileName.EndsWith(f.Name)) 290 | { 291 | lsvFiles.Items.Add(new ListViewItem(new[] { fileName, "是", "*" + f.Name })); 292 | isFilter = true; 293 | } 294 | break; 295 | case FilterType.FullMatch: 296 | if (fileName.Equals(f.Name)) 297 | { 298 | lsvFiles.Items.Add(new ListViewItem(new[] { fileName, "是", f.Name })); 299 | isFilter = true; 300 | } 301 | break; 302 | } 303 | 304 | if (isFilter) 305 | break; 306 | } 307 | 308 | if (!isFilter) 309 | lsvFiles.Items.Add(new ListViewItem(new[] { fileName, "否", "-" })); 310 | } 311 | 312 | // 检查目录 313 | foreach (DirectoryInfo subDi in di.GetDirectories()) 314 | LoadLocalFiles(filterList, dirPrefix + "\\" + subDi.Name); 315 | } 316 | // ReSharper disable once EmptyGeneralCatchClause 317 | catch { } 318 | } 319 | #endregion 320 | } 321 | } 322 | -------------------------------------------------------------------------------- /FrmFilterTester.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /FrmMain.Designer.cs: -------------------------------------------------------------------------------- 1 | namespace QiNiuDrive 2 | { 3 | partial class FrmMain 4 | { 5 | /// 6 | /// 必需的设计器变量。 7 | /// 8 | private System.ComponentModel.IContainer components = null; 9 | 10 | /// 11 | /// 清理所有正在使用的资源。 12 | /// 13 | /// 如果应释放托管资源,为 true;否则为 false。 14 | protected override void Dispose(bool disposing) 15 | { 16 | if (disposing && (components != null)) 17 | { 18 | components.Dispose(); 19 | } 20 | base.Dispose(disposing); 21 | } 22 | 23 | #region Windows 窗体设计器生成的代码 24 | 25 | /// 26 | /// 设计器支持所需的方法 - 不要 27 | /// 使用代码编辑器修改此方法的内容。 28 | /// 29 | private void InitializeComponent() 30 | { 31 | this.SuspendLayout(); 32 | // 33 | // FrmMain 34 | // 35 | this.AutoScaleDimensions = new System.Drawing.SizeF(7F, 17F); 36 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; 37 | this.ClientSize = new System.Drawing.Size(600, 465); 38 | this.Font = new System.Drawing.Font("微软雅黑", 9F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0))); 39 | this.FormBorderStyle = System.Windows.Forms.FormBorderStyle.None; 40 | this.Margin = new System.Windows.Forms.Padding(3, 4, 3, 4); 41 | this.Name = "FrmMain"; 42 | this.StartPosition = System.Windows.Forms.FormStartPosition.CenterScreen; 43 | this.Text = "FrmMain"; 44 | this.FormClosing += new System.Windows.Forms.FormClosingEventHandler(this.frmMain_FormClosing); 45 | this.MouseClick += new System.Windows.Forms.MouseEventHandler(this.frmMain_MouseClick); 46 | this.MouseDown += new System.Windows.Forms.MouseEventHandler(this.frmMain_MouseDown); 47 | this.MouseMove += new System.Windows.Forms.MouseEventHandler(this.frmMain_MouseMove); 48 | this.MouseUp += new System.Windows.Forms.MouseEventHandler(this.frmMain_MouseUp); 49 | this.ResumeLayout(false); 50 | 51 | } 52 | 53 | #endregion 54 | } 55 | } 56 | 57 | -------------------------------------------------------------------------------- /FrmMain.cs: -------------------------------------------------------------------------------- 1 | #region 文档说明 2 | /* ****************************************************************************************************** 3 | * 文档作者:无闻 4 | * 创建日期:2013 年 10 月 16 日 5 | * 文档用途:七牛云盘主窗口 6 | * ------------------------------------------------------------------------------------------------------ 7 | * 修改记录: 8 | * ------------------------------------------------------------------------------------------------------ 9 | * 参考文献: 10 | * ******************************************************************************************************/ 11 | #endregion 12 | 13 | #region 命名空间引用 14 | using System; 15 | using System.IO; 16 | using System.Net; 17 | using System.Text; 18 | using System.Windows.Forms; 19 | using System.Drawing; 20 | using System.Threading; 21 | using System.Collections.Generic; 22 | using System.Diagnostics; 23 | 24 | using Qiniu.IO; 25 | using Qiniu.RPC; 26 | using Qiniu.RS; 27 | using Qiniu.RSF; 28 | using Qiniu.Conf; 29 | 30 | using Newtonsoft.Json; 31 | 32 | using CharmCommonMethod; 33 | using CharmControlLibrary; 34 | #endregion 35 | 36 | namespace QiNiuDrive 37 | { 38 | #region 枚举 39 | /// 40 | /// 过滤类型:前缀,后缀,全匹配 41 | /// 42 | public enum FilterType 43 | { 44 | /// 45 | /// 过滤类型:包含 46 | /// 47 | Contain, 48 | /// 49 | /// 过滤类型:前缀 50 | /// 51 | Prefix, 52 | /// 53 | /// 过滤类型:后缀 54 | /// 55 | Suffix, 56 | /// 57 | /// 过滤类型:全匹配 58 | /// 59 | FullMatch 60 | } 61 | #endregion 62 | 63 | #region 结构 64 | /// 65 | /// 同步文件 66 | /// 67 | public struct SyncFile 68 | { 69 | public string Name; 70 | public long Timestamp; 71 | 72 | public SyncFile(string name, long timestamp) 73 | { 74 | this.Name = name; 75 | this.Timestamp = timestamp; 76 | } 77 | } 78 | 79 | /// 80 | /// 过滤器 81 | /// 82 | public struct Filter 83 | { 84 | public FilterType Type; 85 | public string Name; 86 | 87 | public Filter(string name, FilterType type) 88 | { 89 | this.Name = name; 90 | this.Type = type; 91 | } 92 | } 93 | 94 | /// 95 | /// 修改文件 96 | /// 97 | public struct ChangeFile 98 | { 99 | public string OldName; 100 | public string NewName; 101 | 102 | public ChangeFile(string oldName, string newName) 103 | { 104 | this.OldName = oldName; 105 | this.NewName = newName; 106 | } 107 | } 108 | #endregion 109 | 110 | // 七牛云盘主窗口 111 | public partial class FrmMain : Form 112 | { 113 | #region 常量 114 | private const string APP_NAME = "七牛云盘"; // 软件名称 115 | private const string APP_VER = "v0.1.5"; // 软件版本 116 | private const int UPDATE_VERSION = 201312040; // 更新版本 117 | public const string APP_CFG_PATH = "Config\\app.ini"; // 软件配置路径 118 | private const int TITLE_HEIGHT = 30; // 标题栏高度 119 | private const int MENU_WIDTH = 90; // 菜单栏宽度 120 | #endregion 121 | 122 | #region 字段 123 | #region 主窗口 124 | // * 私有字段 * 125 | private string[] mMenuNames; // 菜单项名称 126 | private int mMenuSelectedIndex; // 菜单栏现行选中项索引 127 | private string mStatusText; // 状态文本 128 | private bool mIsLoadFinished; // 指示程序是否加载完毕 129 | private bool mIsUpdateChecked; // 指示是否完成检查更新 130 | 131 | // * 用户控件 * 132 | private List mCharmControls; // Charm 控件集合 133 | private readonly ToolTip mToolTip = new ToolTip(); // 工具提示文本控件 134 | private NotifyIcon mNotifyIcon; // 托盘图标 135 | private CharmButton mBtnApply; // 应用按钮 136 | private CharmMenu mTaryMenu; // 托盘菜单 137 | 138 | // * 七牛 * 139 | private RSFClient mRsfClient; 140 | private RSClient mRsClient; 141 | private List mServerFileList; // 服务器文件列表 142 | private List mLocalFileList; // 本地文件列表 143 | private List mLocalFileCache; // 本地文件缓存 144 | private List mChangeFileList; // 修改文件列表 145 | private PutRet mPutRet; // 上传返回结果 146 | #endregion 147 | 148 | #region 同步设置 149 | // * 私有字段 * 150 | private bool mIsVaildSyncDir; // 指示是否设置有效同步目录 151 | private bool mIsHasKeys; // 指示是否填写密钥 152 | private bool mIsVaildKeys; // 指示密钥是否有效 153 | private bool mIsVaildBucket; // 指示空间名称是否有效 154 | private bool mIsNeedVerifyAuth; // 指示是否需要验证授权 155 | private string mSyncDir; // 同步目录 156 | private int mSyncCycle; // 同步周期 157 | private string mBucket = string.Empty; // 空间名称 158 | private bool mIsPrivateBucket; // 指示是否为私有空间 159 | private string mPrefix = string.Empty; // 同步前缀 160 | private bool mIsCaptureChanges; // 指示是否监控变动 161 | private bool mIsDonePut; // 指示是否完成上传 162 | private bool mIsSyncNow; // 指示是否立即同步 163 | private bool mIsSyncing; // 指示是否正在同步 164 | private FileSystemWatcher mFileWatcher; // 文件监视器 165 | 166 | // * 用户控件 * 167 | private List mSyncSettingControls; // 同步设置控件集合 168 | // 0-同步目录文本框;1-同步周期文本框;2-AccessKey 文本框;3-SecretKey 文本框;4-空间名称文本框;5-同步前缀文本框 169 | private List mSyncSettingCharmControls; // 同步设置面板 Charm 控件集合 170 | // 4-私有空间检查框;5-监控变动检查框 171 | #endregion 172 | 173 | #region 高级设置 174 | // * 私有字段 * 175 | private List mFilterList; // 过滤器列表 176 | 177 | // * 用户控件 * 178 | private readonly FrmFilterTester mFrmFilterTester = new FrmFilterTester(); // 过滤规则测试窗口 179 | private List mAdvancedSettingCharmControls; // 高级设置面板 Charm 控件集合 180 | #endregion 181 | 182 | #region 关于 183 | private List mAboutCharmControls; // 关于面板 Charm 控件集合 184 | #endregion 185 | #endregion 186 | 187 | #region 窗体事件 188 | // 窗体构造方法 189 | public FrmMain() 190 | { 191 | // 初始化组件 192 | InitializeComponent(); 193 | // 设置双缓冲模式 194 | this.SetStyle(ControlStyles.AllPaintingInWmPaint | //不擦除背景 ,减少闪烁 195 | ControlStyles.OptimizedDoubleBuffer | //双缓冲 196 | ControlStyles.UserPaint, //使用自定义的重绘事件,减少闪烁 197 | true); 198 | this.UpdateStyles(); 199 | // 初始化设置 200 | InitializeSetting(); 201 | } 202 | 203 | // 窗体即将关闭事件 204 | private void frmMain_FormClosing(object sender, FormClosingEventArgs e) 205 | { 206 | // 释放系统资源 207 | mNotifyIcon.Dispose(); 208 | } 209 | 210 | // 窗体鼠标单击事件 211 | private void frmMain_MouseClick(object sender, MouseEventArgs e) 212 | { 213 | #region 主面板事件 214 | // 根据菜单现行选中项索引判断绘制哪个面板 215 | switch (mMenuSelectedIndex) 216 | { 217 | case 0: // 同步设置 218 | CharmControl.MouseClickEvent(e, mSyncSettingCharmControls); 219 | break; 220 | case 1: // 高级设置 221 | CharmControl.MouseClickEvent(e, mAdvancedSettingCharmControls); 222 | break; 223 | case 3: // 关于 224 | CharmControl.MouseClickEvent(e, mAboutCharmControls); 225 | break; 226 | } 227 | #endregion 228 | 229 | // 调用事件 230 | CharmControl.MouseClickEvent(e, mCharmControls); 231 | } 232 | 233 | // 窗体鼠标按下事件 234 | private void frmMain_MouseDown(object sender, MouseEventArgs e) 235 | { 236 | // 只响应鼠标左键 237 | if (e.Button == MouseButtons.Left) 238 | { 239 | #region 菜单栏事件处理 240 | // 轮询菜单项 241 | for (int i = 0; i < mMenuNames.Length; i++) 242 | { 243 | // 判断是否为现行选中项且未被激活 244 | if (mMenuSelectedIndex == i || e.X <= 1 || e.X >= 91 || 245 | e.Y <= TITLE_HEIGHT + 1 + i * 26 || e.Y >= TITLE_HEIGHT + 5 + (i + 1) * 26) 246 | continue; 247 | 248 | mMenuSelectedIndex = i; // 设置菜单栏现行选中项索引 249 | ShowPanels(); // 设置控件可见性 250 | 251 | // 重绘菜单栏区域 252 | this.Invalidate(); 253 | //this.Invalidate(new Rectangle(1, 23, 90, mMenuNames.Length * 26)); 254 | return; 255 | } 256 | #endregion 257 | } 258 | 259 | // 指示是否捕捉到事件 260 | bool isCaptureEvent = false; 261 | 262 | #region 主面板事件 263 | // 根据菜单现行选中项索引判断绘制哪个面板 264 | switch (mMenuSelectedIndex) 265 | { 266 | case 0: // 同步设置 267 | isCaptureEvent = CharmControl.MouseDownEvent(e, mSyncSettingCharmControls, this); 268 | break; 269 | case 1: // 高级设置 270 | isCaptureEvent = CharmControl.MouseDownEvent(e, mAdvancedSettingCharmControls, this); 271 | break; 272 | case 3: // 关于 273 | isCaptureEvent = CharmControl.MouseDownEvent(e, mAboutCharmControls, this); 274 | break; 275 | } 276 | #endregion 277 | 278 | // 判断之前是否已捕捉到事件 279 | if (isCaptureEvent) return; 280 | 281 | // 调用事件 282 | if (!CharmControl.MouseDownEvent(e, mCharmControls, this)) 283 | APIOperation.MoveNoBorderForm(this, e); 284 | } 285 | 286 | // 窗体鼠标移动事件 287 | private void frmMain_MouseMove(object sender, MouseEventArgs e) 288 | { 289 | #region 主面板事件 290 | // 根据菜单现行选中项索引判断绘制哪个面板 291 | switch (mMenuSelectedIndex) 292 | { 293 | case 0: // 同步设置 294 | CharmControl.MouseMoveEvent(e, mSyncSettingCharmControls, this, mToolTip); 295 | break; 296 | case 1: // 高级设置 297 | CharmControl.MouseMoveEvent(e, mAdvancedSettingCharmControls, this, mToolTip); 298 | break; 299 | case 3: // 关于 300 | CharmControl.MouseMoveEvent(e, mAboutCharmControls, this, mToolTip); 301 | break; 302 | } 303 | #endregion 304 | 305 | // 调用事件 306 | CharmControl.MouseMoveEvent(e, mCharmControls, this, mToolTip); 307 | } 308 | 309 | // 窗体鼠标弹起事件 310 | private void frmMain_MouseUp(object sender, MouseEventArgs e) 311 | { 312 | #region 主面板事件 313 | // 根据菜单现行选中项索引判断绘制哪个面板 314 | switch (mMenuSelectedIndex) 315 | { 316 | case 0: // 同步设置 317 | CharmControl.MouseUpEvent(e, mSyncSettingCharmControls, this); 318 | break; 319 | case 1: // 高级设置 320 | CharmControl.MouseUpEvent(e, mAdvancedSettingCharmControls, this); 321 | break; 322 | case 3: // 关于 323 | CharmControl.MouseUpEvent(e, mAboutCharmControls, this); 324 | break; 325 | } 326 | #endregion 327 | 328 | // 调用事件 329 | CharmControl.MouseUpEvent(e, mCharmControls, this); 330 | } 331 | #endregion 332 | 333 | #region 重载事件 334 | // 控件重绘事件 335 | protected override void OnPaint(PaintEventArgs e) 336 | { 337 | // 获取绘制对象 338 | Graphics g = e.Graphics; 339 | 340 | // 绘制状态文本 341 | g.DrawString("(" + mStatusText + ")", 342 | new Font("微软雅黑", 10, FontStyle.Bold), Brushes.White, new Point(150, 6)); 343 | 344 | #region 绘制菜单栏 345 | // 轮询菜单项 346 | for (int i = 0; i < mMenuNames.Length; i++) 347 | { 348 | // 判断是否为现行选中项 349 | if (mMenuSelectedIndex == i) 350 | { 351 | g.DrawImage(Properties.Resources.tab_button_setting_chosen, new Point(1, TITLE_HEIGHT + 1 + i * 26)); 352 | g.DrawString(mMenuNames[i], this.Font, Brushes.White, new Point(20, TITLE_HEIGHT + 5 + i * 26)); 353 | } 354 | else 355 | g.DrawString(mMenuNames[i], this.Font, Brushes.Black, new Point(20, TITLE_HEIGHT + 5 + i * 26)); 356 | } 357 | #endregion 358 | 359 | #region 绘制主面板 360 | // 根据菜单现行选中项索引判断绘制哪个面板 361 | switch (mMenuSelectedIndex) 362 | { 363 | case 0: // 同步设置 364 | DrawSyncSettingPanel(g); 365 | CharmControl.PaintEvent(e.Graphics, mSyncSettingCharmControls); 366 | break; 367 | case 1: // 高级设置 368 | DrawAdvancedSettingPanel(g); 369 | CharmControl.PaintEvent(e.Graphics, mAdvancedSettingCharmControls); 370 | break; 371 | case 3: // 关于 372 | DrawAboutPanel(g); 373 | CharmControl.PaintEvent(e.Graphics, mAboutCharmControls); 374 | break; 375 | } 376 | #endregion 377 | 378 | // 绘制控件 379 | CharmControl.PaintEvent(e.Graphics, mCharmControls); 380 | 381 | // 重置重绘索引 382 | //mMultipleSettingRedrawIndex = 0; 383 | } 384 | #endregion 385 | 386 | #region 控件事件 387 | #region 主窗口 388 | // 最小化按钮被单击事件 389 | private void btnMin_MouseClick(object sender, MouseEventArgs e) 390 | { 391 | this.WindowState = FormWindowState.Minimized; 392 | } 393 | 394 | // 关闭按钮被单击事件 395 | private void btnClose_MouseClick(object sender, MouseEventArgs e) 396 | { 397 | CharmMessageBox msgbox; 398 | DialogResult result; 399 | 400 | if (mBtnApply.Enabled) 401 | { 402 | msgbox = new CharmMessageBox(); 403 | result = msgbox.Show("您有修改未保存,是否保存?", 404 | "操作确认", MessageBoxButtons.YesNo, CharmMessageBoxIcon.Question); 405 | 406 | if (result == DialogResult.Yes) 407 | mBtnApply_MouseClick(sender, e); 408 | } 409 | 410 | msgbox = new CharmMessageBox(); 411 | result = msgbox.Show("您确定要退出 " + APP_NAME + " 吗?", 412 | "退出确认", MessageBoxButtons.YesNo, CharmMessageBoxIcon.Question); 413 | 414 | if (result == DialogResult.Yes) 415 | this.Close(); 416 | } 417 | 418 | // 托盘图标被单击事件 419 | private void mNotifyIcon_MouseClick(object sender, MouseEventArgs e) 420 | { 421 | // 判断鼠标按键 422 | if (e.Button == MouseButtons.Left) 423 | { 424 | // 显示主界面 425 | this.Visible = true; 426 | this.WindowState = FormWindowState.Normal; 427 | this.TopMost = true; 428 | this.TopMost = false; 429 | } 430 | else 431 | // 显示托盘菜单 432 | mTaryMenu.Show(); 433 | } 434 | 435 | // 托盘菜单被单击事件 436 | private void mTaryMenu_MenuClick(int clickIndex) 437 | { 438 | // 判断用户单击的菜单项 439 | switch (clickIndex) 440 | { 441 | case 0: // 设置中心 442 | mNotifyIcon_MouseClick(new object(), new MouseEventArgs(MouseButtons.Left, 0, 0, 0, 0)); 443 | break; 444 | case 1: // 打开同步目录 445 | if (mIsVaildSyncDir) 446 | Process.Start(mSyncDir); 447 | else 448 | { 449 | CharmMessageBox msgbox = new CharmMessageBox(); 450 | msgbox.Show("您的操作由于以下原因导致失败:\n\n" + 451 | "- 未设置有效的同步目录", 452 | "操作失败", MessageBoxButtons.OK, CharmMessageBoxIcon.Error); 453 | } 454 | break; 455 | case 3: // 立即同步 456 | mIsSyncNow = true; 457 | break; 458 | case 5: // 关于 459 | mMenuSelectedIndex = 3; 460 | ShowPanels(); 461 | this.Invalidate(); 462 | mNotifyIcon_MouseClick(new object(), new MouseEventArgs(MouseButtons.Left, 0, 0, 0, 0)); 463 | break; 464 | case 6: // 退出 465 | btnClose_MouseClick(new object(), new MouseEventArgs(MouseButtons.Left, 0, 0, 0, 0)); 466 | break; 467 | } 468 | } 469 | 470 | // 确定按钮被单击事件 471 | private void btnMakeChange_MouseClick(object sender, MouseEventArgs e) 472 | { 473 | if (mBtnApply.Enabled) 474 | mBtnApply_MouseClick(sender, e); 475 | 476 | this.Visible = false; 477 | } 478 | 479 | // 取消按钮被单击事件 480 | private void btnCancel_MouseClick(object sender, MouseEventArgs e) 481 | { 482 | if (mBtnApply.Enabled) 483 | { 484 | mBtnApply.Enabled = false; 485 | mIsLoadFinished = false; 486 | LoadLocalSetting(); 487 | } 488 | this.Visible = false; 489 | } 490 | 491 | // 应用按钮被单击事件 492 | private void mBtnApply_MouseClick(object sender, MouseEventArgs e) 493 | { 494 | mBtnApply.Enabled = false; 495 | this.Invalidate(mBtnApply.ClientRectangle); // 重绘控件 496 | 497 | #region 同步设置 498 | IniOperation.WriteValue(APP_CFG_PATH, "setting", "sync_dir", ((CharmTextBox)mSyncSettingControls[0]).Text); 499 | IniOperation.WriteValue(APP_CFG_PATH, "setting", "sync_cycle", ((CharmTextBox)mSyncSettingControls[1]).Text); 500 | IniOperation.WriteValue(APP_CFG_PATH, "setting", "bucket", ((CharmTextBox)mSyncSettingControls[4]).Text); 501 | IniOperation.WriteValue(APP_CFG_PATH, "setting", "private_bucket", (mSyncSettingCharmControls[4]).Checked.ToString()); 502 | IniOperation.WriteValue(APP_CFG_PATH, "setting", "prefix", ((CharmTextBox)mSyncSettingControls[5]).Text); 503 | IniOperation.WriteValue(APP_CFG_PATH, "setting", "capture_changes", (mSyncSettingCharmControls[5]).Checked.ToString()); 504 | 505 | string accessKey = ((CharmTextBox)mSyncSettingControls[2]).Text; 506 | string secretKey = ((CharmTextBox)mSyncSettingControls[3]).Text; 507 | 508 | if (accessKey.Length > 0 && secretKey.Length > 0) 509 | { 510 | string keys = BasicMethod.DesEncrypt(accessKey, "QWERTYUI") + "|" + 511 | BasicMethod.DesEncrypt(secretKey, "ASDFGHJK"); 512 | StreamWriter sw = new StreamWriter("Config/KEY"); 513 | sw.Write(keys); 514 | sw.Close(); 515 | } 516 | #endregion 517 | 518 | mIsLoadFinished = false; 519 | LoadLocalSetting(); 520 | 521 | if (!mIsUpdateChecked || !mIsVaildSyncDir || !mIsHasKeys) 522 | { 523 | RedrawStatusText("正在校验修改..."); 524 | return; 525 | } 526 | 527 | RedrawStatusText("正在校验修改..."); 528 | mIsNeedVerifyAuth = true; 529 | } 530 | #endregion 531 | 532 | #region 同步设置 533 | // 同步目录文本框文本改变事件(同步周期、AccessKey、SecretKey、空间名称文本框也关联此事件) 534 | private void txtSyncDir_TextChanged(object sender, EventArgs e) 535 | { 536 | // 首次启动时程序数据加载时不作响应 537 | if (!mIsLoadFinished || mBtnApply.Enabled) return; 538 | 539 | mBtnApply.Enabled = true; 540 | this.Invalidate(mBtnApply.ClientRectangle); // 重绘控件 541 | } 542 | 543 | // 浏览路径按钮被单击事件 544 | private void btnViewPath_MouseClick(object sender, MouseEventArgs e) 545 | { 546 | // 创建并实例化文件浏览对话框 547 | FolderBrowserDialog folderBrowserFialog = new FolderBrowserDialog 548 | { 549 | Description = "请选择同步目录", 550 | SelectedPath = ((CharmTextBox)mSyncSettingControls[0]).Text 551 | }; 552 | 553 | // 显示对话框并判断用户是否指定新的目录 554 | if (folderBrowserFialog.ShowDialog() == DialogResult.OK) 555 | ((CharmTextBox)mSyncSettingControls[0]).Text = folderBrowserFialog.SelectedPath; // 用户指定新的目录 556 | } 557 | 558 | // 私有空间检查框被单击事件(监控变动检查框也关联此事件) 559 | private void chkPrivateBucket_MouseClick(object sender, MouseEventArgs e) 560 | { 561 | // 首次启动时程序数据加载时不作响应 562 | if (!mIsLoadFinished || mBtnApply.Enabled) return; 563 | 564 | mBtnApply.Enabled = true; 565 | this.Invalidate(mBtnApply.ClientRectangle); // 重绘控件 566 | } 567 | 568 | // 立即同步按钮被单击事件 569 | private void btnSyncNow_MouseClick(object sender, MouseEventArgs e) 570 | { 571 | mIsSyncNow = true; 572 | } 573 | 574 | // 查看 AccessKey 按钮被单击事件 575 | private void btnViewAccessKey_MouseClick(object sender, MouseEventArgs e) 576 | { 577 | CharmMessageBox msgbox = new CharmMessageBox(); 578 | string accessKey = ((CharmTextBox)mSyncSettingControls[2]).Text; 579 | if (accessKey.Length == 0) 580 | msgbox.Show("尊敬的用户您好:\n" + 581 | "您尚未填写任何 AccessKey,请到七牛开发者中心获取!", 582 | "查看密钥", MessageBoxButtons.OK, CharmMessageBoxIcon.Warning); 583 | else 584 | msgbox.Show("尊敬的用户您好:\n" + 585 | "以下为您的 AccessKey,请妥善保管!\n\n" + accessKey, 586 | "查看密钥"); 587 | } 588 | 589 | // 查看 SecretKey 按钮被单击事件 590 | private void btnViewSecretKey_MouseClick(object sender, MouseEventArgs e) 591 | { 592 | CharmMessageBox msgbox = new CharmMessageBox(); 593 | string secretKey = ((CharmTextBox)mSyncSettingControls[3]).Text; 594 | if (secretKey.Length == 0) 595 | msgbox.Show("尊敬的用户您好:\n" + 596 | "您尚未填写任何 SecretKey,请到七牛开发者中心获取!", 597 | "查看密钥", MessageBoxButtons.OK, CharmMessageBoxIcon.Warning); 598 | else 599 | msgbox.Show("尊敬的用户您好:\n" + 600 | "以下为您的 SecretKey,请妥善保管!\n\n" + secretKey, 601 | "查看密钥"); 602 | } 603 | 604 | // 七牛开发平台链接标签鼠标单击事件 605 | private static void lblQiniuOpen_MouseClick(object sender, MouseEventArgs e) 606 | { 607 | Process.Start("https://portal.qiniu.com/"); 608 | } 609 | 610 | // 文件监视器捕捉到重命名事件 611 | private void mFileWatcher_Renamed(object source, RenamedEventArgs e) 612 | { 613 | if (mIsSyncing || !mIsCaptureChanges) return; 614 | 615 | // 判断是否为目录改名 616 | if (Directory.Exists(e.FullPath)) 617 | DirectoryRenameEvent(e.OldFullPath, e.FullPath); 618 | else 619 | FileRenameEvent(e.OldName, e.Name); 620 | } 621 | #endregion 622 | 623 | #region 高级设置 624 | // 重载过滤规则按钮被单击事件 625 | private void btnReloadFilter_MouseClick(object sender, MouseEventArgs e) 626 | { 627 | LoadFilterList(mFilterList); 628 | RedrawStatusText("过滤规则已重载"); 629 | } 630 | 631 | // 过滤规则测试按钮被单击事件 632 | private void btnTestFilter_MouseClick(object sender, MouseEventArgs e) 633 | { 634 | mFrmFilterTester.Show(); 635 | } 636 | #endregion 637 | 638 | #region 关于 639 | // GitHub 链接标签鼠标单击事件 640 | private static void lblGithub_MouseClick(object sender, MouseEventArgs e) 641 | { 642 | Process.Start("https://github.com/Unknwon/qiniudrive"); 643 | } 644 | 645 | // 新浪链接标签鼠标单击事件 646 | private static void lblSina_MouseClick(object sender, MouseEventArgs e) 647 | { 648 | Process.Start("http://weibo.com/Obahua"); 649 | } 650 | #endregion 651 | #endregion 652 | 653 | #region 线程 654 | // 全局更新检查线程 655 | private void GlobalUpdateCheck() 656 | { 657 | // 检查更新 658 | RedrawStatusText("正在检查更新..."); 659 | 660 | try 661 | { 662 | WebClient wb = new WebClient { Proxy = null }; 663 | wb.Headers.Add("Cache-Control", "no-cache"); 664 | var dict = 665 | JsonConvert.DeserializeObject>( 666 | wb.DownloadString("https://raw.github.com/Unknwon/qiniudrive/master/VERSION.json")); 667 | if (UPDATE_VERSION < (int)dict["version"]) 668 | { 669 | CharmMessageBox msgbox = new CharmMessageBox(); 670 | DialogResult result = msgbox.Show("尊敬的用户您好:\n\n" + 671 | "感谢您使用 " + APP_NAME + ",更高版本已经发布。\n\n" + 672 | "为了获得更好的用户体验,建议您立即更新!\n\n" + 673 | "请到 github.com/Unknwon/qiniudrive 获取最新下载地址。\n\n" + 674 | "或单击 是 自动跳转到下载页面。", 675 | "版本升级", MessageBoxButtons.YesNo); 676 | if (result == DialogResult.Yes) 677 | Process.Start(dict["download_url"]); 678 | } 679 | } 680 | catch (Exception e) 681 | { 682 | CharmMessageBox msgbox = new CharmMessageBox(); 683 | msgbox.Show("检查更新失败:\n\n" + 684 | e.Message, 685 | "错误提示", MessageBoxButtons.OK, CharmMessageBoxIcon.Error); 686 | } 687 | 688 | // 初始化七牛服务 689 | mRsfClient = new RSFClient(mBucket); 690 | mRsClient = new RSClient(); 691 | mServerFileList = new List(); 692 | mLocalFileList = new List(); 693 | mLocalFileCache = new List(); 694 | mChangeFileList = new List(); 695 | 696 | // 检查授权及其它设置 697 | VerifyAuth(); 698 | RedrawStatusText("正在连接服务器..."); 699 | 700 | mIsUpdateChecked = true; 701 | 702 | int count = 0; 703 | 704 | while (true) 705 | { 706 | #if(!DEBUG) 707 | //内存释放 708 | Process proc = Process.GetCurrentProcess(); 709 | proc.MaxWorkingSet = Process.GetCurrentProcess().MaxWorkingSet; 710 | proc.Dispose(); 711 | #endif 712 | if (((mSyncCycle != 0 && count % mSyncCycle == 0) || mIsSyncNow) && 713 | mIsVaildKeys && mIsVaildBucket && mIsVaildSyncDir) 714 | { 715 | count = 0; 716 | mIsSyncing = true; 717 | Sync(); 718 | mIsSyncNow = false; 719 | mIsSyncing = false; 720 | mServerFileList.Clear(); 721 | mLocalFileList.Clear(); 722 | } 723 | 724 | // 检查是否需要验证授权 725 | if (mIsNeedVerifyAuth) 726 | { 727 | mIsNeedVerifyAuth = false; 728 | VerifyAuth(); 729 | RedrawStatusText("等待同步"); 730 | } 731 | 732 | count++; 733 | Thread.Sleep(1000); 734 | } 735 | // ReSharper disable once FunctionNeverReturns 736 | } 737 | #endregion 738 | 739 | #region 方法 740 | // 初始化设置 741 | private void InitializeSetting() 742 | { 743 | // 设置窗体属性 744 | this.Icon = Properties.Resources.icon; 745 | this.Text = APP_NAME; 746 | 747 | // 绘制窗体背景 748 | DrawFormBackground(); 749 | 750 | // 初始化菜单栏 751 | mMenuNames = new[] { "同步设置", "高级设置", "网络设置", "关于" }; 752 | 753 | #region 创建窗体组件 754 | // 创建最小化系统按钮 755 | CharmSysButton btnMin = new CharmSysButton 756 | { 757 | SysButtonType = SysButtonType.Minimum, 758 | ToolTipText = "最小化", 759 | Location = new Point(this.Width - 75, 1) 760 | }; 761 | // 创建关闭系统按钮 762 | CharmSysButton btnClose = new CharmSysButton 763 | { 764 | SysButtonType = SysButtonType.Close, 765 | ToolTipText = "关闭", 766 | Location = new Point(this.Width - 44, 1) 767 | }; 768 | 769 | // 创建确定按钮 770 | CharmButton btnMakeChange = new CharmButton 771 | { 772 | ButtonType = ButtonType.Classic_Size_08223, 773 | Text = "确 定", 774 | Location = new Point(300, 430) 775 | }; 776 | // 创建取消按钮 777 | CharmButton btnCancel = new CharmButton 778 | { 779 | ButtonType = ButtonType.Classic_Size_08223, 780 | Text = "取 消", 781 | Location = new Point(390, 430) 782 | }; 783 | // 创建应用按钮 784 | mBtnApply = new CharmButton 785 | { 786 | ButtonType = ButtonType.Classic_Size_08223, 787 | Text = "应 用", 788 | Location = new Point(480, 430), 789 | Enabled = false 790 | }; 791 | 792 | // 创建控件集合 793 | mCharmControls = new List { btnMin, btnClose, btnMakeChange, btnCancel, mBtnApply }; 794 | 795 | // 关联控件事件 796 | btnMin.MouseClick += btnMin_MouseClick; 797 | btnClose.MouseClick += btnClose_MouseClick; 798 | btnMakeChange.MouseClick += btnMakeChange_MouseClick; 799 | btnCancel.MouseClick += btnCancel_MouseClick; 800 | mBtnApply.MouseClick += mBtnApply_MouseClick; 801 | #endregion 802 | 803 | #region 创建主体区域 804 | // 创建同步设置面板 805 | CreateSyncSettingPanel(); 806 | // 创建高级设置面板 807 | CreateAdvancedSettingPanel(); 808 | // 创建关于面板 809 | CreateAboutPanel(); 810 | #endregion 811 | 812 | // 加载本地设置 813 | LoadLocalSetting(); 814 | 815 | // 设置初始参数 816 | if (!mIsHasKeys) 817 | mStatusText = "未填写密钥"; 818 | else if (!mIsVaildSyncDir) 819 | mStatusText = "无效的同步目录"; 820 | else 821 | mStatusText = "正在初始化..."; 822 | 823 | #region 创建菜单 824 | // 创建托盘菜单 825 | mTaryMenu = new CharmMenu(); 826 | mTaryMenu.AddItem("设置中心", MenuItemType.TextItem, 827 | new Bitmap(Properties.Resources.logo, 16, 16)); 828 | mTaryMenu.AddItem("打开同步目录", MenuItemType.TextItem); 829 | mTaryMenu.AddItem("", MenuItemType.Spliter); 830 | mTaryMenu.AddItem("立即同步", MenuItemType.TextItem); 831 | mTaryMenu.AddItem("", MenuItemType.Spliter); 832 | mTaryMenu.AddItem("关于", MenuItemType.TextItem); 833 | mTaryMenu.AddItem("退出", MenuItemType.TextItem); 834 | 835 | // 关联控件事件 836 | mTaryMenu.MenuClick += mTaryMenu_MenuClick; 837 | #endregion 838 | 839 | #region 创建托盘图标 840 | // 创建托盘图标 841 | mNotifyIcon = new NotifyIcon 842 | { 843 | Icon = Properties.Resources.icon, 844 | Text = APP_NAME + "\n" + mStatusText, 845 | Visible = true 846 | }; 847 | 848 | // 关联控件事件 849 | mNotifyIcon.MouseClick += mNotifyIcon_MouseClick; 850 | #endregion 851 | 852 | #region 创建服务线程 853 | // 启动全局更新检查线程 854 | Thread globalUpdateCheckThread = new Thread(GlobalUpdateCheck) { IsBackground = true }; 855 | globalUpdateCheckThread.Start(); 856 | #endregion 857 | } 858 | 859 | // 绘制窗体背景 860 | private void DrawFormBackground() 861 | { 862 | // 加载窗体背景图像资源 863 | Bitmap imgBkg = new Bitmap(this.Width, this.Height); 864 | 865 | #region 绘制窗体内容 866 | using (Graphics g = Graphics.FromImage(imgBkg)) 867 | { 868 | // 设置参数 869 | const int bottomHeight = 45; 870 | 871 | // 绘制标题栏背景 872 | SolidBrush sb = new SolidBrush(Color.DodgerBlue); 873 | g.FillRectangle(sb, 0, 0, this.Width, TITLE_HEIGHT); 874 | // 绘制窗口图标 875 | g.DrawImage(Properties.Resources.logo, new Rectangle(6, 5, 20, 20)); 876 | // 绘制窗口标题 877 | g.DrawString("设置中心 - 七牛云盘", 878 | new Font("微软雅黑", 10, FontStyle.Bold), Brushes.White, new Point(30, 6)); 879 | // 绘制竖线 880 | g.DrawLine(Pens.DarkGray, MENU_WIDTH + 1, TITLE_HEIGHT, 91, this.Height); 881 | // 绘制菜单区 882 | sb.Color = Color.FromArgb(100, Color.LightGray); 883 | g.FillRectangle(sb, 1, TITLE_HEIGHT, 90, this.Height); 884 | // 绘制主面板区 885 | sb.Color = Color.FromArgb(255, Color.WhiteSmoke); 886 | g.FillRectangle(sb, MENU_WIDTH + 2, TITLE_HEIGHT, this.Width - MENU_WIDTH - 4, this.Height - TITLE_HEIGHT - bottomHeight); 887 | // 绘制横线 888 | g.DrawLine(Pens.DarkGray, MENU_WIDTH + 2, this.Height - bottomHeight, this.Width - 2, this.Height - bottomHeight); 889 | // 绘制按钮区 890 | sb.Color = Color.FromArgb(70, Color.LightGray); 891 | g.FillRectangle(sb, MENU_WIDTH + 2, this.Height - bottomHeight, this.Width - 2, bottomHeight); 892 | // 绘制边框 893 | g.DrawRectangle(new Pen(Color.DimGray), 0, 0, this.Width - 1, this.Height - 1); 894 | } 895 | #endregion 896 | 897 | // 设置背景资源 898 | this.BackgroundImage = new Bitmap(imgBkg); 899 | 900 | // 释放系统资源 901 | imgBkg.Dispose(); 902 | } 903 | 904 | // 加载本地设置 905 | private void LoadLocalSetting() 906 | { 907 | // 判断是否为初次运行程序 908 | if (!Directory.Exists("Config")) 909 | Directory.CreateDirectory("Config"); 910 | 911 | mIsVaildKeys = true; 912 | mIsVaildBucket = true; 913 | 914 | #region 同步设置 915 | // 获取同步目录 916 | mSyncDir = IniOperation.ReadValue(APP_CFG_PATH, "setting", "sync_dir"); 917 | if (mSyncDir.Length > 0 && Directory.Exists(mSyncDir)) 918 | { 919 | ((CharmTextBox)mSyncSettingControls[0]).Text = mSyncDir; 920 | 921 | // 初始化文件监视器 922 | if (mFileWatcher == null) 923 | { 924 | mFileWatcher = new FileSystemWatcher { IncludeSubdirectories = true }; 925 | mFileWatcher.Renamed += mFileWatcher_Renamed; 926 | } 927 | 928 | mFileWatcher.Path = mSyncDir; 929 | mFileWatcher.EnableRaisingEvents = true; 930 | mIsVaildSyncDir = true; 931 | } 932 | else 933 | { 934 | if (mFileWatcher != null) 935 | mFileWatcher.Dispose(); 936 | mIsVaildSyncDir = false; 937 | } 938 | 939 | // 获取同步周期 940 | try 941 | { 942 | mSyncCycle = Convert.ToInt32(IniOperation.ReadValue(APP_CFG_PATH, "setting", "sync_cycle")); 943 | } 944 | catch 945 | { 946 | mSyncCycle = 60; 947 | } 948 | 949 | if (mSyncCycle < 10) 950 | mSyncCycle = 10; 951 | ((CharmTextBox)mSyncSettingControls[1]).Text = Convert.ToString(mSyncCycle); 952 | 953 | // 获取空间名称 954 | mBucket = IniOperation.ReadValue(APP_CFG_PATH, "setting", "bucket"); 955 | if (mBucket.Length > 0) 956 | ((CharmTextBox)mSyncSettingControls[4]).Text = mBucket; 957 | else 958 | mIsVaildBucket = false; 959 | // 私有空间 960 | if (IniOperation.ReadValue(APP_CFG_PATH, "setting", "private_bucket").Equals("True")) 961 | { 962 | mIsPrivateBucket = true; 963 | mSyncSettingCharmControls[4].Checked = true; 964 | } 965 | else 966 | { 967 | mIsPrivateBucket = false; 968 | mSyncSettingCharmControls[4].Checked = false; 969 | } 970 | 971 | // 获取同步前缀 972 | mPrefix = IniOperation.ReadValue(APP_CFG_PATH, "setting", "prefix"); 973 | if (mPrefix.Length > 0) 974 | ((CharmTextBox)mSyncSettingControls[5]).Text = mBucket; 975 | 976 | // 监控变动 977 | if (IniOperation.ReadValue(APP_CFG_PATH, "setting", "capture_changes").Equals("True")) 978 | { 979 | mIsCaptureChanges = true; 980 | mSyncSettingCharmControls[5].Checked = true; 981 | } 982 | else 983 | { 984 | mIsCaptureChanges = false; 985 | mSyncSettingCharmControls[5].Checked = false; 986 | } 987 | 988 | 989 | // 获取密钥 990 | if (File.Exists("Config/KEY")) 991 | { 992 | StreamReader sr = new StreamReader("Config/KEY"); 993 | string[] keys = sr.ReadToEnd().Split('|'); 994 | sr.Close(); 995 | 996 | if (keys.Length > 0) 997 | { 998 | Config.ACCESS_KEY = BasicMethod.DesDecrypt(keys[0], "QWERTYUI"); 999 | Config.SECRET_KEY = BasicMethod.DesDecrypt(keys[1], "ASDFGHJK"); 1000 | ((CharmTextBox)mSyncSettingControls[2]).Text = Config.ACCESS_KEY; 1001 | ((CharmTextBox)mSyncSettingControls[3]).Text = Config.SECRET_KEY; 1002 | mIsHasKeys = true; 1003 | } 1004 | } 1005 | #endregion 1006 | 1007 | #region 高级设置 1008 | if (mFilterList == null) 1009 | mFilterList = new List(); 1010 | LoadFilterList(mFilterList); 1011 | #endregion 1012 | 1013 | mIsLoadFinished = true; 1014 | } 1015 | 1016 | // 加载过滤器列表 1017 | public static void LoadFilterList(List filterList) 1018 | { 1019 | if (!File.Exists("Config/filter.txt")) return; 1020 | 1021 | filterList.Clear(); 1022 | 1023 | StreamReader sr = new StreamReader("Config/filter.txt"); 1024 | string[] filters = sr.ReadToEnd().Split('\n'); 1025 | sr.Close(); 1026 | 1027 | foreach (string f in filters) 1028 | { 1029 | string name = f.TrimEnd('\r'); 1030 | if (name.Length <= 0) continue; 1031 | 1032 | int index = name.IndexOf('*'); 1033 | int lastIndex = name.LastIndexOf('*'); 1034 | 1035 | // 判断过滤类型 1036 | if (index == 0 && lastIndex == name.Length - 1) // 包含 1037 | filterList.Add(new Filter(name.Substring(1, name.Length - 2), FilterType.Contain)); 1038 | else if (index == 0) // 后缀 1039 | filterList.Add(new Filter(name.Substring(1), FilterType.Suffix)); 1040 | else if (index == name.Length - 1) // 前缀 1041 | filterList.Add(new Filter(name.Substring(0, name.Length - 1), FilterType.Prefix)); 1042 | else // 全匹配 1043 | filterList.Add(new Filter(name, FilterType.FullMatch)); 1044 | } 1045 | } 1046 | 1047 | // 显示面板 1048 | private void ShowPanels() 1049 | { 1050 | // 设置面板相关控件的可见性 1051 | if (mMenuSelectedIndex == 0) // 同步设置 1052 | foreach (Control ctrl in mSyncSettingControls) 1053 | ctrl.Visible = true; 1054 | else 1055 | foreach (Control ctrl in mSyncSettingControls) 1056 | ctrl.Visible = false; 1057 | } 1058 | 1059 | // 重绘状态文本 1060 | private void RedrawStatusText(string text) 1061 | { 1062 | if (text.Length > 0) 1063 | mStatusText = text; 1064 | 1065 | if (!mIsHasKeys) 1066 | mStatusText = "未填写密钥"; 1067 | else if (!mIsVaildSyncDir) 1068 | mStatusText = "无效的同步目录"; 1069 | else if (!mIsVaildBucket) 1070 | mStatusText = "无效的空间名称"; 1071 | else if (!mIsVaildKeys) 1072 | mStatusText = "无效的密钥"; 1073 | 1074 | if (mStatusText.Length > 28) 1075 | mStatusText = mStatusText.Substring(0, 28) + "..."; 1076 | this.Invalidate(new Rectangle(150, 6, 360, 24)); 1077 | mNotifyIcon.Text = APP_NAME + "\n" + mStatusText; 1078 | } 1079 | 1080 | // 验证授权 1081 | private void VerifyAuth() 1082 | { 1083 | if (mBucket.Length <= 0) return; 1084 | 1085 | try 1086 | { 1087 | mRsfClient.ListPrefix(mBucket, "", "", 1); 1088 | } 1089 | catch (Exception e) 1090 | { 1091 | CheckException(e.Message); 1092 | } 1093 | } 1094 | 1095 | // 同步 1096 | private void Sync() 1097 | { 1098 | #region 文件重命名处理 1099 | if (mChangeFileList.Count > 0) 1100 | { 1101 | RedrawStatusText("正在移动文件..."); 1102 | 1103 | // 处理改名文件 1104 | List eppList = new List(); 1105 | foreach (ChangeFile cf in mChangeFileList) 1106 | // 判断文件是否依旧存在 1107 | if (File.Exists(mSyncDir + "\\" + cf.NewName)) 1108 | { 1109 | eppList.Add(new EntryPathPair(mBucket, 1110 | cf.OldName.Replace("\\", "/"), cf.NewName.Replace("\\", "/"))); 1111 | Console.WriteLine("增加移动文件:{0} {1}", cf.OldName.Replace("\\", "/"), cf.NewName.Replace("\\", "/")); 1112 | } 1113 | mRsClient.BatchMove(eppList.ToArray()); 1114 | } 1115 | #endregion 1116 | 1117 | #region 文件删除处理 1118 | RedrawStatusText("正在对比文件..."); 1119 | 1120 | // 获取本地文件列表 1121 | LoadLocalFiles(""); 1122 | 1123 | //// 获取本地文件缓存 1124 | if (File.Exists("Data/file_cache_list")) 1125 | { 1126 | mLocalFileCache.Clear(); 1127 | 1128 | StreamReader sr = new StreamReader("Data/file_cache_list"); 1129 | string[] files = sr.ReadToEnd().Split('\n'); 1130 | sr.Close(); 1131 | 1132 | // 查找删除项 1133 | foreach (string f in files) 1134 | { 1135 | string cacheName = f.TrimEnd('\r'); 1136 | // 判断文件是否被移动 1137 | if (cacheName.Length <= 0 || FindChangeFileIndex(cacheName) > -1) continue; 1138 | 1139 | cacheName = cacheName.Replace("\\", "/"); 1140 | if (IsNeedFilter(cacheName)) continue; 1141 | 1142 | if (FindLocalFileIndex(cacheName) != -1) 1143 | { 1144 | mLocalFileCache.Add(cacheName); 1145 | continue; 1146 | } 1147 | 1148 | //RedrawStatusText("正在删除(上行)..." + cacheName); 1149 | //CallRet cr = mRsClient.Delete(new EntryPath(mBucket, cacheName)); 1150 | //if (!cr.OK) 1151 | //{ 1152 | // if (!cr.Exception.Message.Contains("612")) 1153 | // RedrawStatusText("删除失败..." + cacheName); 1154 | //} 1155 | //else 1156 | // Thread.Sleep(1000); 1157 | } 1158 | } 1159 | 1160 | mChangeFileList.Clear(); 1161 | #endregion 1162 | 1163 | // 拉取服务器文件列表 1164 | bool isHasMore = true; 1165 | string marker = string.Empty; 1166 | 1167 | while (isHasMore) 1168 | { 1169 | DumpRet dr = mRsfClient.ListPrefix(mBucket, mPrefix, marker); 1170 | 1171 | // 存储文件到服务器文件列表 1172 | foreach (DumpItem item in dr.Items) 1173 | if (!IsNeedFilter(item.Key)) 1174 | mServerFileList.Add(new SyncFile(item.Key, item.PutTime / 10000000)); 1175 | 1176 | if (dr.Marker != null) 1177 | marker = dr.Marker; 1178 | else 1179 | isHasMore = false; 1180 | } 1181 | 1182 | List tmpCacheList = new List(); 1183 | // 文件差异对比及消除 1184 | for (int i = 0; i < mServerFileList.Count; i++) 1185 | { 1186 | int index = FindLocalFileIndex(mServerFileList[i].Name); 1187 | string curServeFileName = mServerFileList[i].Name.Replace("/", "\\"); 1188 | DateTime unixTime = new DateTime(1970, 1, 1).AddSeconds(mServerFileList[i].Timestamp); 1189 | 1190 | if (index == -1) 1191 | { 1192 | //RedrawStatusText("正在下载..." + mServerFileList[i].Name); 1193 | //if (!DownloadFile("http://" + mBucket + ".u.qiniudn.com/" + mServerFileList[i].Name, 1194 | // curServeFileName, unixTime)) 1195 | // RedrawStatusText("下载失败..." + mServerFileList[i].Name); 1196 | } 1197 | else if (mServerFileList[i].Timestamp > mLocalFileList[index].Timestamp) 1198 | { 1199 | //RedrawStatusText("正在更新..." + mServerFileList[i].Name); 1200 | //if (!DownloadFile("http://" + mBucket + ".u.qiniudn.com/" + mServerFileList[i].Name, 1201 | // curServeFileName, unixTime)) 1202 | // RedrawStatusText("更新失败..." + mServerFileList[i].Name); 1203 | mLocalFileList.RemoveAt(index); 1204 | } 1205 | else if (mServerFileList[i].Timestamp < mLocalFileList[index].Timestamp) 1206 | { 1207 | RedrawStatusText("正在上传..." + mServerFileList[i].Name); 1208 | mIsDonePut = false; 1209 | PutFile(mPrefix + mServerFileList[i].Name, mSyncDir + "\\" + mServerFileList[i].Name, true); 1210 | WaitUntilTure(mIsDonePut); 1211 | if (mPutRet.OK) 1212 | // ReSharper disable once PossibleLossOfFraction 1213 | File.SetLastWriteTimeUtc(mSyncDir + "\\" + mServerFileList[i].Name, 1214 | new DateTime(1970, 1, 1).AddSeconds(mRsClient.Stat(new EntryPath(mBucket, mServerFileList[i].Name)).PutTime / 10000000)); 1215 | else 1216 | { 1217 | CheckException(mPutRet.Exception.Message); 1218 | RedrawStatusText("同步失败"); 1219 | mNotifyIcon.ShowBalloonTip(1000, "同步失败", mPutRet.Exception.Message, ToolTipIcon.Error); 1220 | return; 1221 | } 1222 | mLocalFileList.RemoveAt(index); 1223 | } 1224 | else if (mServerFileList[i].Timestamp == mLocalFileList[index].Timestamp) 1225 | mLocalFileList.RemoveAt(index); 1226 | tmpCacheList.Add(curServeFileName); 1227 | } 1228 | 1229 | #region 本地完全差异文件上传 1230 | foreach (SyncFile sf in mLocalFileList) 1231 | { 1232 | // 存在缓存列表中说明已经上传过,因此属删除操作 1233 | if (FindLocalCacheIndex(sf.Name) > -1) 1234 | { 1235 | //RedrawStatusText("正在删除(下行)..." + sf.Name); 1236 | //File.Delete(mSyncDir + "\\" + sf.Name); 1237 | //continue; 1238 | } 1239 | 1240 | if (IsNeedFilter(sf.Name)) continue; 1241 | 1242 | RedrawStatusText("正在上载..." + sf.Name); 1243 | mIsDonePut = false; 1244 | PutFile(mPrefix + sf.Name.Replace("\\", "/"), mSyncDir + "\\" + sf.Name); 1245 | WaitUntilTure(mIsDonePut); 1246 | if (mPutRet.OK) 1247 | // ReSharper disable once PossibleLossOfFraction 1248 | File.SetLastWriteTimeUtc(mSyncDir + "\\" + sf.Name, 1249 | new DateTime(1970, 1, 1).AddSeconds( 1250 | mRsClient.Stat(new EntryPath(mBucket, sf.Name.Replace("\\", "/"))).PutTime / 10000000)); 1251 | else 1252 | { 1253 | CheckException(mPutRet.Exception.Message); 1254 | RedrawStatusText("上载失败"); 1255 | mNotifyIcon.ShowBalloonTip(1000, "上载失败", mPutRet.Exception.Message, ToolTipIcon.Error); 1256 | return; 1257 | } 1258 | tmpCacheList.Add(sf.Name); 1259 | } 1260 | #endregion 1261 | 1262 | // 保存本地文件缓存 1263 | StringBuilder sb = new StringBuilder(); 1264 | foreach (string s in tmpCacheList) 1265 | sb.AppendLine(s); 1266 | if (sb.Length > 0) 1267 | { 1268 | StreamWriter sw = new StreamWriter("Data/file_cache_list"); 1269 | sw.Write(sb.ToString()); 1270 | sw.Close(); 1271 | } 1272 | 1273 | RedrawStatusText("同步完成"); 1274 | } 1275 | 1276 | // 判断是否需要过滤 1277 | private bool IsNeedFilter(string name) 1278 | { 1279 | if (mFilterList == null) return false; 1280 | 1281 | foreach (Filter f in mFilterList) 1282 | switch (f.Type) 1283 | { 1284 | case FilterType.Contain: 1285 | if (name.Contains(f.Name)) 1286 | return true; 1287 | break; 1288 | case FilterType.Prefix: 1289 | if (name.StartsWith(f.Name)) 1290 | return true; 1291 | break; 1292 | case FilterType.Suffix: 1293 | if (name.EndsWith(f.Name)) 1294 | return true; 1295 | break; 1296 | case FilterType.FullMatch: 1297 | if (name.Equals(f.Name)) 1298 | return true; 1299 | break; 1300 | } 1301 | 1302 | return false; 1303 | } 1304 | 1305 | // 加载本地文件(可用于递归操作) 1306 | private void LoadLocalFiles(string dirPrefix) 1307 | { 1308 | DateTime timeStamp = new DateTime(1970, 1, 1); 1309 | 1310 | // 检查文件 1311 | DirectoryInfo di = new DirectoryInfo(mSyncDir + dirPrefix); 1312 | foreach (FileInfo fi in di.GetFiles()) 1313 | { 1314 | string fileName = (dirPrefix.TrimStart('\\') + "\\" + fi.Name).TrimStart('\\'); 1315 | 1316 | // 忽略隐藏文件 1317 | if (fi.Attributes.ToString().Contains("Hidden")) 1318 | continue; 1319 | 1320 | fileName = fileName.Replace("\\", "/"); 1321 | if (!IsNeedFilter(fileName)) 1322 | mLocalFileList.Add( 1323 | new SyncFile(fileName, (fi.LastWriteTimeUtc.Ticks - timeStamp.Ticks) / 10000000)); 1324 | else 1325 | Console.WriteLine("过滤 " + fileName); 1326 | } 1327 | 1328 | // 检查目录 1329 | foreach (DirectoryInfo subDi in di.GetDirectories()) 1330 | LoadLocalFiles(dirPrefix + "\\" + subDi.Name); 1331 | } 1332 | 1333 | // 查找修改文件列表中的匹配项 1334 | private int FindChangeFileIndex(string name) 1335 | { 1336 | for (int i = 0; i < mChangeFileList.Count; i++) 1337 | if (name.Equals(mChangeFileList[i].OldName) || 1338 | name.Equals(mChangeFileList[i].NewName)) 1339 | return i; 1340 | return -1; 1341 | } 1342 | 1343 | // 查找本地文件列表中的匹配项 1344 | private int FindLocalFileIndex(string name) 1345 | { 1346 | for (int i = 0; i < mLocalFileList.Count; i++) 1347 | if (mLocalFileList[i].Name.Equals(name.TrimStart(mPrefix.ToCharArray()))) 1348 | return i; 1349 | return -1; 1350 | } 1351 | 1352 | // 查找本地缓存列表中的匹配项 1353 | private int FindLocalCacheIndex(string name) 1354 | { 1355 | for (int i = 0; i < mLocalFileCache.Count; i++) 1356 | if (mLocalFileCache[i].Equals(name)) 1357 | return i; 1358 | return -1; 1359 | } 1360 | 1361 | /// 1362 | /// 上传文件 1363 | /// 1364 | /// 文件名称 1365 | /// 本地文件名称 1366 | /// 是否覆盖 1367 | private void PutFile(string key, string fname, bool rewrite = false) 1368 | { 1369 | string scope = mBucket; 1370 | if (rewrite) 1371 | scope += ":" + key; 1372 | 1373 | string upToken = new PutPolicy(scope).Token(); 1374 | IOClient client = new IOClient(); 1375 | client.PutFinished += (o, ret) => 1376 | { 1377 | mPutRet = ret; 1378 | mIsDonePut = true; 1379 | }; 1380 | client.PutFile(upToken, key, fname, null); 1381 | } 1382 | 1383 | // 循环等待 1384 | private void WaitUntilTure(bool cond) 1385 | { 1386 | while (true) 1387 | { 1388 | if (cond) 1389 | return; 1390 | Thread.Sleep(100); 1391 | } 1392 | } 1393 | 1394 | // 检查异常 1395 | private void CheckException(string message) 1396 | { 1397 | mIsVaildKeys = !message.Contains("(401)"); 1398 | mIsVaildBucket = !message.Contains("(631)") && !message.Contains("(599)"); 1399 | } 1400 | 1401 | // 目录重命名事件 1402 | private void DirectoryRenameEvent(string oldPath, string newPath) 1403 | { 1404 | DirectoryInfo di = new DirectoryInfo(newPath); 1405 | foreach (FileInfo fi in di.GetFiles()) 1406 | FileRenameEvent((oldPath + "\\" + fi.Name).Substring(mSyncDir.Length + 1), 1407 | (newPath + "\\" + fi.Name).Substring(mSyncDir.Length + 1)); 1408 | 1409 | foreach (DirectoryInfo subDi in di.GetDirectories()) 1410 | DirectoryRenameEvent(oldPath + "\\" + subDi.Name, subDi.FullName); 1411 | } 1412 | 1413 | // 文件重命名事件 1414 | private void FileRenameEvent(string oldPath, string newPath) 1415 | { 1416 | // 忽略 .tmp 结尾的文件 1417 | if (oldPath.ToLower().EndsWith(".tmp") || newPath.ToLower().EndsWith(".tmp")) return; 1418 | 1419 | int index = FindChangeFileIndex(oldPath); 1420 | if (index == -1) 1421 | mChangeFileList.Add(new ChangeFile(oldPath, newPath)); 1422 | else 1423 | { 1424 | // 判断是否改回了原来的名字 1425 | if (!mChangeFileList[index].OldName.Equals(newPath)) 1426 | mChangeFileList.Add(new ChangeFile(mChangeFileList[index].OldName, newPath)); 1427 | mChangeFileList.RemoveAt(index); 1428 | } 1429 | Console.WriteLine("文件重命名:{0} {1}", oldPath, newPath); 1430 | } 1431 | 1432 | // 下载文件 1433 | private bool DownloadFile(string url, string key, DateTime unixTime) 1434 | { 1435 | // 判断是否为私有空间 1436 | if (mIsPrivateBucket) 1437 | { 1438 | string baseUrl = GetPolicy.MakeBaseUrl(mBucket + ".u.qiniudn.com", key); 1439 | url = GetPolicy.MakeRequest(baseUrl); 1440 | } 1441 | 1442 | string path = mSyncDir + "\\" + key; 1443 | // ReSharper disable once AssignNullToNotNullAttribute 1444 | Directory.CreateDirectory(Path.GetDirectoryName(path)); 1445 | WebClient wb = new WebClient { Proxy = null }; 1446 | try 1447 | { 1448 | wb.DownloadFile(url, path); 1449 | File.SetLastWriteTimeUtc(path, unixTime); 1450 | } 1451 | catch 1452 | { 1453 | return false; 1454 | } 1455 | return true; 1456 | } 1457 | 1458 | #region 创建面板方法 1459 | // 创建同步设置面板 1460 | private void CreateSyncSettingPanel() 1461 | { 1462 | // 创建同步目录文本框 1463 | CharmTextBox txtSyncDir = new CharmTextBox 1464 | { 1465 | Location = new Point(127 + MENU_WIDTH, 20 + TITLE_HEIGHT), 1466 | Width = 260 1467 | }; 1468 | // 创建浏览路径按钮 1469 | CharmButton btnViewPath = new CharmButton 1470 | { 1471 | ButtonType = ButtonType.Classic_Size_08223, 1472 | Text = "浏览路径", 1473 | ForeColor = Color.DarkGreen, 1474 | Location = new Point(399 + MENU_WIDTH, 22 + TITLE_HEIGHT) 1475 | }; 1476 | 1477 | // 创建同步周期文本框 1478 | CharmTextBox txtSyncCycle = new CharmTextBox 1479 | { 1480 | Location = new Point(127 + MENU_WIDTH, 55 + TITLE_HEIGHT), 1481 | Width = 50, 1482 | TextInputMode = InputMode.Integer, 1483 | MaxLength = 5, 1484 | TextAlign = HorizontalAlignment.Center 1485 | }; 1486 | 1487 | // 创建空间名称文本框 1488 | CharmTextBox txtBucket = new CharmTextBox 1489 | { 1490 | Location = new Point(290 + MENU_WIDTH, 55 + TITLE_HEIGHT), 1491 | Width = 95, 1492 | }; 1493 | 1494 | // 创建私有空间检查框 1495 | CharmCheckBox chkPrivateBucket = new CharmCheckBox 1496 | { 1497 | Location = new Point(400 + MENU_WIDTH, 57 + TITLE_HEIGHT), 1498 | Text = "私有空间" 1499 | }; 1500 | 1501 | // 创建同步前缀文本框 1502 | CharmTextBox txtPrefix = new CharmTextBox 1503 | { 1504 | Location = new Point(150 + MENU_WIDTH, 90 + TITLE_HEIGHT), 1505 | Width = 95, 1506 | }; 1507 | 1508 | // 创建监控变动检查框 1509 | CharmCheckBox chkCaptureChange = new CharmCheckBox 1510 | { 1511 | Location = new Point(260 + MENU_WIDTH, 92 + TITLE_HEIGHT), 1512 | Text = "监控目录文件变化" 1513 | }; 1514 | 1515 | // 创建立即同步按钮 1516 | CharmButton btnSyncNow = new CharmButton 1517 | { 1518 | ButtonType = ButtonType.Classic_Size_08223, 1519 | Text = "立即同步", 1520 | ForeColor = Color.Purple, 1521 | Location = new Point(399 + MENU_WIDTH, 92 + TITLE_HEIGHT) 1522 | }; 1523 | 1524 | // 创建 AccessKey 文本框 1525 | CharmTextBox txtAccessKey = new CharmTextBox 1526 | { 1527 | Location = new Point(127 + MENU_WIDTH, 160 + TITLE_HEIGHT), 1528 | Width = 260, 1529 | TextInputMode = InputMode.Password 1530 | }; 1531 | // 创建查看 AccessKey 按钮 1532 | CharmButton btnViewAccessKey = new CharmButton 1533 | { 1534 | ButtonType = ButtonType.Classic_Size_08223, 1535 | Text = "查看密钥", 1536 | ForeColor = Color.Red, 1537 | Location = new Point(399 + MENU_WIDTH, 162 + TITLE_HEIGHT) 1538 | }; 1539 | 1540 | // 创建 SecretKey 文本框 1541 | CharmTextBox txtSecretKey = new CharmTextBox 1542 | { 1543 | Location = new Point(127 + MENU_WIDTH, 195 + TITLE_HEIGHT), 1544 | Width = 260, 1545 | TextInputMode = InputMode.Password 1546 | }; 1547 | // 创建查看 SecretKey 按钮 1548 | CharmButton btnViewSecretKey = new CharmButton 1549 | { 1550 | ButtonType = ButtonType.Classic_Size_08223, 1551 | Text = "查看密钥", 1552 | ForeColor = Color.Red, 1553 | Location = new Point(399 + MENU_WIDTH, 197 + TITLE_HEIGHT) 1554 | }; 1555 | 1556 | // 创建七牛开发平台链接标签 1557 | CharmLinkLabel lblQiniuOpen = new CharmLinkLabel 1558 | { 1559 | Location = new Point(280, 265), 1560 | ForeColor = Color.Blue, 1561 | Text = "七牛云存储开发者平台" 1562 | }; 1563 | 1564 | // 关联控件事件 1565 | txtSyncDir.TextChanged += txtSyncDir_TextChanged; 1566 | btnViewPath.MouseClick += btnViewPath_MouseClick; 1567 | txtSyncCycle.TextChanged += txtSyncDir_TextChanged; 1568 | txtBucket.TextChanged += txtSyncDir_TextChanged; 1569 | chkPrivateBucket.MouseClick += chkPrivateBucket_MouseClick; 1570 | lblQiniuOpen.MouseClick += lblQiniuOpen_MouseClick; 1571 | txtAccessKey.TextChanged += txtSyncDir_TextChanged; 1572 | btnViewAccessKey.MouseClick += btnViewAccessKey_MouseClick; 1573 | txtSecretKey.TextChanged += txtSyncDir_TextChanged; 1574 | btnViewSecretKey.MouseClick += btnViewSecretKey_MouseClick; 1575 | txtPrefix.TextChanged += txtSyncDir_TextChanged; 1576 | chkCaptureChange.MouseClick += chkPrivateBucket_MouseClick; 1577 | btnSyncNow.MouseClick += btnSyncNow_MouseClick; 1578 | 1579 | // 将控件添加到集合中 1580 | this.Controls.Add(txtSyncDir); 1581 | this.Controls.Add(txtSyncCycle); 1582 | this.Controls.Add(txtAccessKey); 1583 | this.Controls.Add(txtSecretKey); 1584 | this.Controls.Add(txtBucket); 1585 | this.Controls.Add(txtPrefix); 1586 | 1587 | // 创建同步设置面板控件集合 1588 | mSyncSettingControls = new List { txtSyncDir, txtSyncCycle, txtAccessKey, txtSecretKey, txtBucket, txtPrefix }; 1589 | mSyncSettingCharmControls = new List { btnViewPath, lblQiniuOpen, btnViewAccessKey, btnViewSecretKey, chkPrivateBucket, chkCaptureChange, btnSyncNow }; 1590 | } 1591 | 1592 | // 创建高级设置面板 1593 | private void CreateAdvancedSettingPanel() 1594 | { 1595 | // 创建重载过滤规则按钮 1596 | CharmButton btnReloadFilter = new CharmButton 1597 | { 1598 | ButtonType = ButtonType.Classic_Size_12425, 1599 | Text = "重载过滤规则", 1600 | ForeColor = Color.MediumPurple, 1601 | Location = new Point(85 + MENU_WIDTH, 50 + TITLE_HEIGHT) 1602 | 1603 | }; 1604 | // 创建过滤规则测试按钮 1605 | CharmButton btnTestFilter = new CharmButton 1606 | { 1607 | ButtonType = ButtonType.Classic_Size_12425, 1608 | Text = "过滤规则测试", 1609 | ForeColor = Color.MediumSlateBlue, 1610 | Location = new Point(245 + MENU_WIDTH, 50 + TITLE_HEIGHT) 1611 | 1612 | }; 1613 | 1614 | // 关联控件事件 1615 | btnReloadFilter.MouseClick += btnReloadFilter_MouseClick; 1616 | btnTestFilter.MouseClick += btnTestFilter_MouseClick; 1617 | 1618 | // 创建同步设置面板控件集合 1619 | mAdvancedSettingCharmControls = new List { btnReloadFilter, btnTestFilter }; 1620 | } 1621 | 1622 | // 创建关于面板 1623 | private void CreateAboutPanel() 1624 | { 1625 | // 创建 GitHub 链接标签 1626 | CharmLinkLabel lblGithub = new CharmLinkLabel 1627 | { 1628 | Location = new Point(185, 145), 1629 | Font = new Font("微软雅黑", 10), 1630 | ForeColor = Color.Blue, 1631 | Text = "github.com/Unknwon/qiniudrive" 1632 | }; 1633 | 1634 | // 创建新浪微博链接标签 1635 | CharmLinkLabel lblSina = new CharmLinkLabel 1636 | { 1637 | Location = new Point(330, 223), 1638 | Font = new Font("微软雅黑", 10), 1639 | ForeColor = Color.Blue, 1640 | Text = "@无闻Unknown" 1641 | }; 1642 | 1643 | // 关联控件事件 1644 | lblGithub.MouseClick += lblGithub_MouseClick; 1645 | lblSina.MouseClick += lblSina_MouseClick; 1646 | 1647 | // 创建关于面板控件集合 1648 | mAboutCharmControls = new List { lblGithub, lblSina }; 1649 | } 1650 | #endregion 1651 | 1652 | #region 绘制面板方法 1653 | // 绘制同步设置面板 1654 | private void DrawSyncSettingPanel(Graphics g) 1655 | { 1656 | // 同步目录 1657 | g.DrawString("云盘同步目录:", this.Font, Brushes.Black, 125, 25 + TITLE_HEIGHT); 1658 | // 同步周期 1659 | g.DrawString("同步时间周期:", this.Font, Brushes.Black, 125, 60 + TITLE_HEIGHT); 1660 | g.DrawString("秒", this.Font, Brushes.Black, 275, 60 + TITLE_HEIGHT); 1661 | 1662 | // 空间名称 1663 | g.DrawString("空间名称:", this.Font, Brushes.Black, 310, 60 + TITLE_HEIGHT); 1664 | 1665 | // 同步前缀 1666 | g.DrawString("当前设备同步前缀:", this.Font, Brushes.Black, 125, 95 + TITLE_HEIGHT); 1667 | 1668 | // 密钥管理 1669 | g.DrawString("密钥管理", this.Font, Brushes.Black, new Point(22 + MENU_WIDTH, 135 + TITLE_HEIGHT)); 1670 | g.DrawLine(Pens.DarkGray, 90 + MENU_WIDTH, 145 + TITLE_HEIGHT, this.Width - 50, 145 + TITLE_HEIGHT); 1671 | g.DrawString("Access Key:", this.Font, Brushes.Black, 125, 165 + TITLE_HEIGHT); 1672 | g.DrawString("Secret Key:", this.Font, Brushes.Black, 125, 200 + TITLE_HEIGHT); 1673 | g.DrawString("申请注册七牛开发者帐号:", this.Font, Brushes.Black, 125, 235 + TITLE_HEIGHT); 1674 | } 1675 | 1676 | // 绘制高级设置面板 1677 | private void DrawAdvancedSettingPanel(Graphics g) 1678 | { 1679 | // 文件过滤 1680 | g.DrawString("文件过滤", this.Font, Brushes.Black, new Point(22 + MENU_WIDTH, 22 + TITLE_HEIGHT)); 1681 | g.DrawLine(Pens.DarkGray, 90 + MENU_WIDTH, 32 + TITLE_HEIGHT, this.Width - 50, 32 + TITLE_HEIGHT); 1682 | } 1683 | 1684 | // 绘制关于面板 1685 | private static void DrawAboutPanel(Graphics g) 1686 | { 1687 | Font font = new Font("微软雅黑", 10, FontStyle.Bold); 1688 | // 基本说明 1689 | string intro = "本程序是基于七牛开放 API 构建的第三方同步程序\n" + 1690 | "七牛云存储官方不对本程序的任何行为负责。"; 1691 | g.DrawString(intro, font, Brushes.Black, 185, 60); 1692 | // 开源说明 1693 | intro = "七牛云盘由 C# 编写并已开源在 GitHub 上:"; 1694 | g.DrawString(intro, font, Brushes.Black, 185, 120); 1695 | // 版权说明 1696 | g.DrawImage(Properties.Resources.unknown, 190, 200, 64, 64); 1697 | //font = new Font("微软雅黑", 10); 1698 | intro = "程序作者:无闻\n" + 1699 | "新浪微博:\n" + 1700 | "个人博客:暂未开通"; 1701 | g.DrawString(intro, font, Brushes.Black, 265, 205); 1702 | intro = "软件版本:" + APP_VER + " 版权所有 @ 2013 无闻"; 1703 | g.DrawString(intro, font, Brushes.Black, 205, 285); 1704 | // 绘制七牛 LOGO 1705 | g.DrawImage(Properties.Resources.qiniu_logo, 190, 330, 290, 45); 1706 | } 1707 | #endregion 1708 | #endregion 1709 | } 1710 | } 1711 | -------------------------------------------------------------------------------- /FrmMain.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, and 10 | distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by the copyright 13 | owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all other entities 16 | that control, are controlled by, or are under common control with that entity. 17 | For the purposes of this definition, "control" means (i) the power, direct or 18 | indirect, to cause the direction or management of such entity, whether by 19 | contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the 20 | outstanding shares, or (iii) beneficial ownership of such entity. 21 | 22 | "You" (or "Your") shall mean an individual or Legal Entity exercising 23 | permissions granted by this License. 24 | 25 | "Source" form shall mean the preferred form for making modifications, including 26 | but not limited to software source code, documentation source, and configuration 27 | files. 28 | 29 | "Object" form shall mean any form resulting from mechanical transformation or 30 | translation of a Source form, including but not limited to compiled object code, 31 | generated documentation, and conversions to other media types. 32 | 33 | "Work" shall mean the work of authorship, whether in Source or Object form, made 34 | available under the License, as indicated by a copyright notice that is included 35 | in or attached to the work (an example is provided in the Appendix below). 36 | 37 | "Derivative Works" shall mean any work, whether in Source or Object form, that 38 | is based on (or derived from) the Work and for which the editorial revisions, 39 | annotations, elaborations, or other modifications represent, as a whole, an 40 | original work of authorship. For the purposes of this License, Derivative Works 41 | shall not include works that remain separable from, or merely link (or bind by 42 | name) to the interfaces of, the Work and Derivative Works thereof. 43 | 44 | "Contribution" shall mean any work of authorship, including the original version 45 | of the Work and any modifications or additions to that Work or Derivative Works 46 | thereof, that is intentionally submitted to Licensor for inclusion in the Work 47 | by the copyright owner or by an individual or Legal Entity authorized to submit 48 | on behalf of the copyright owner. For the purposes of this definition, 49 | "submitted" means any form of electronic, verbal, or written communication sent 50 | to the Licensor or its representatives, including but not limited to 51 | communication on electronic mailing lists, source code control systems, and 52 | issue tracking systems that are managed by, or on behalf of, the Licensor for 53 | the purpose of discussing and improving the Work, but excluding communication 54 | that is conspicuously marked or otherwise designated in writing by the copyright 55 | owner as "Not a Contribution." 56 | 57 | "Contributor" shall mean Licensor and any individual or Legal Entity on behalf 58 | of whom a Contribution has been received by Licensor and subsequently 59 | incorporated within the Work. 60 | 61 | 2. Grant of Copyright License. 62 | 63 | Subject to the terms and conditions of this License, each Contributor hereby 64 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 65 | irrevocable copyright license to reproduce, prepare Derivative Works of, 66 | publicly display, publicly perform, sublicense, and distribute the Work and such 67 | Derivative Works in Source or Object form. 68 | 69 | 3. Grant of Patent License. 70 | 71 | Subject to the terms and conditions of this License, each Contributor hereby 72 | grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, 73 | irrevocable (except as stated in this section) patent license to make, have 74 | made, use, offer to sell, sell, import, and otherwise transfer the Work, where 75 | such license applies only to those patent claims licensable by such Contributor 76 | that are necessarily infringed by their Contribution(s) alone or by combination 77 | of their Contribution(s) with the Work to which such Contribution(s) was 78 | submitted. If You institute patent litigation against any entity (including a 79 | cross-claim or counterclaim in a lawsuit) alleging that the Work or a 80 | Contribution incorporated within the Work constitutes direct or contributory 81 | patent infringement, then any patent licenses granted to You under this License 82 | for that Work shall terminate as of the date such litigation is filed. 83 | 84 | 4. Redistribution. 85 | 86 | You may reproduce and distribute copies of the Work or Derivative Works thereof 87 | in any medium, with or without modifications, and in Source or Object form, 88 | provided that You meet the following conditions: 89 | 90 | You must give any other recipients of the Work or Derivative Works a copy of 91 | this License; and 92 | You must cause any modified files to carry prominent notices stating that You 93 | changed the files; and 94 | You must retain, in the Source form of any Derivative Works that You distribute, 95 | all copyright, patent, trademark, and attribution notices from the Source form 96 | of the Work, excluding those notices that do not pertain to any part of the 97 | Derivative Works; and 98 | If the Work includes a "NOTICE" text file as part of its distribution, then any 99 | Derivative Works that You distribute must include a readable copy of the 100 | attribution notices contained within such NOTICE file, excluding those notices 101 | that do not pertain to any part of the Derivative Works, in at least one of the 102 | following places: within a NOTICE text file distributed as part of the 103 | Derivative Works; within the Source form or documentation, if provided along 104 | with the Derivative Works; or, within a display generated by the Derivative 105 | Works, if and wherever such third-party notices normally appear. The contents of 106 | the NOTICE file are for informational purposes only and do not modify the 107 | License. You may add Your own attribution notices within Derivative Works that 108 | You distribute, alongside or as an addendum to the NOTICE text from the Work, 109 | provided that such additional attribution notices cannot be construed as 110 | modifying the License. 111 | You may add Your own copyright statement to Your modifications and may provide 112 | additional or different license terms and conditions for use, reproduction, or 113 | distribution of Your modifications, or for any such Derivative Works as a whole, 114 | provided Your use, reproduction, and distribution of the Work otherwise complies 115 | with the conditions stated in this License. 116 | 117 | 5. Submission of Contributions. 118 | 119 | Unless You explicitly state otherwise, any Contribution intentionally submitted 120 | for inclusion in the Work by You to the Licensor shall be under the terms and 121 | conditions of this License, without any additional terms or conditions. 122 | Notwithstanding the above, nothing herein shall supersede or modify the terms of 123 | any separate license agreement you may have executed with Licensor regarding 124 | such Contributions. 125 | 126 | 6. Trademarks. 127 | 128 | This License does not grant permission to use the trade names, trademarks, 129 | service marks, or product names of the Licensor, except as required for 130 | reasonable and customary use in describing the origin of the Work and 131 | reproducing the content of the NOTICE file. 132 | 133 | 7. Disclaimer of Warranty. 134 | 135 | Unless required by applicable law or agreed to in writing, Licensor provides the 136 | Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, 137 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, 138 | including, without limitation, any warranties or conditions of TITLE, 139 | NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are 140 | solely responsible for determining the appropriateness of using or 141 | redistributing the Work and assume any risks associated with Your exercise of 142 | permissions under this License. 143 | 144 | 8. Limitation of Liability. 145 | 146 | In no event and under no legal theory, whether in tort (including negligence), 147 | contract, or otherwise, unless required by applicable law (such as deliberate 148 | and grossly negligent acts) or agreed to in writing, shall any Contributor be 149 | liable to You for damages, including any direct, indirect, special, incidental, 150 | or consequential damages of any character arising as a result of this License or 151 | out of the use or inability to use the Work (including but not limited to 152 | damages for loss of goodwill, work stoppage, computer failure or malfunction, or 153 | any and all other commercial damages or losses), even if such Contributor has 154 | been advised of the possibility of such damages. 155 | 156 | 9. Accepting Warranty or Additional Liability. 157 | 158 | While redistributing the Work or Derivative Works thereof, You may choose to 159 | offer, and charge a fee for, acceptance of support, warranty, indemnity, or 160 | other liability obligations and/or rights consistent with this License. However, 161 | in accepting such obligations, You may act only on Your own behalf and on Your 162 | sole responsibility, not on behalf of any other Contributor, and only if You 163 | agree to indemnify, defend, and hold each Contributor harmless for any liability 164 | incurred by, or claims asserted against, such Contributor by reason of your 165 | accepting any such warranty or additional liability. 166 | 167 | END OF TERMS AND CONDITIONS 168 | 169 | APPENDIX: How to apply the Apache License to your work 170 | 171 | To apply the Apache License to your work, attach the following boilerplate 172 | notice, with the fields enclosed by brackets "[]" replaced with your own 173 | identifying information. (Don't include the brackets!) The text should be 174 | enclosed in the appropriate comment syntax for the file format. We also 175 | recommend that a file or class name and description of purpose be included on 176 | the same "printed page" as the copyright notice for easier identification within 177 | third-party archives. 178 | 179 | Copyright [yyyy] [name of copyright owner] 180 | 181 | Licensed under the Apache License, Version 2.0 (the "License"); 182 | you may not use this file except in compliance with the License. 183 | You may obtain a copy of the License at 184 | 185 | http://www.apache.org/licenses/LICENSE-2.0 186 | 187 | Unless required by applicable law or agreed to in writing, software 188 | distributed under the License is distributed on an "AS IS" BASIS, 189 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 190 | See the License for the specific language governing permissions and 191 | limitations under the License. 192 | -------------------------------------------------------------------------------- /Program.cs: -------------------------------------------------------------------------------- 1 | #region 文档说明 2 | /* **************************************************************************************************** 3 | * 文档作者:无闻 4 | * 创建日期:2013 年 10 月 16 日 5 | * 文档用途:七牛云盘程序主入口,用于必要文件检查等操作 6 | * ----------------------------------------------------------------------------------------------------- 7 | * 修改记录: 8 | * ----------------------------------------------------------------------------------------------------- 9 | * 参考文献: 10 | * *****************************************************************************************************/ 11 | #endregion 12 | 13 | #region 命名空间引用 14 | using System; 15 | using System.IO; 16 | using System.Windows.Forms; 17 | using System.Diagnostics; 18 | 19 | using CharmCommonMethod; 20 | using CharmControlLibrary; 21 | 22 | #endregion 23 | 24 | namespace QiNiuDrive 25 | { 26 | // 程序主入口 27 | static class Program 28 | { 29 | #region 常量 30 | private const string APP_NAME = "七牛云盘"; // 应用程序名称 31 | #endregion 32 | 33 | #region 方法 34 | // 应用程序的主入口点。 35 | [STAThread] 36 | static void Main() 37 | { 38 | if (!Directory.Exists("Data")) 39 | Directory.CreateDirectory("Data"); 40 | 41 | // 检查相同进程是否正在运行 42 | if (CheckSameProcess()) 43 | return; 44 | 45 | Application.EnableVisualStyles(); 46 | Application.SetCompatibleTextRenderingDefault(false); 47 | Application.Run(new FrmMain()); 48 | } 49 | 50 | // 检查相同进程是否正在运行 51 | // 返回:true=有相同进程; false=没有相同进程 52 | public static bool CheckSameProcess() 53 | { 54 | // 判断是否存在禁止检查标识 55 | if (File.Exists("Data\\UAC.SIGN")) 56 | { 57 | File.Delete("Data\\UAC.SIGN"); 58 | // 创建UAC模式标识 59 | FileStream fs = File.Create("Data\\UACMODE.SIGN"); 60 | fs.Dispose(); 61 | } 62 | else 63 | { 64 | // 获取同名进程数量 65 | Process[] procs = Process.GetProcessesByName(APP_NAME); 66 | // 判断返回的进程数组长度,如果数组长度大于1则说明相同程序已经启动 67 | if (procs.Length <= 1) return false; 68 | 69 | // 判断程序启动模式 70 | if (File.Exists("Data\\UACMODE.SIGN")) 71 | { 72 | // 相同进程采用 UAC 模式运行 73 | CharmMessageBox msgbox = new CharmMessageBox(); 74 | msgbox.Show("尊敬的用户您好:\n" + 75 | "欢迎您使用 " + APP_NAME + "!\n\n" + 76 | "由于您的系统开启了 UAC 安全控制," + 77 | "程序无法进行相关操作,因此您需要手动恢复已打开的程序窗口!\n\n" + 78 | "感谢您对 " + APP_NAME + " 的支持与理解!", 79 | "温馨提示"); 80 | } 81 | else 82 | { 83 | Process currentProc = Process.GetCurrentProcess(); 84 | // 对比进程 ID,排除非自身进程,找到已经启动的主程序,并显示主窗口 85 | foreach (Process proc in procs) 86 | { 87 | if (proc.Id == currentProc.Id) continue; 88 | IntPtr hwnd = APIOperation.GetWindowHandle(APP_NAME); 89 | APIOperation.ShowWindowForeground(hwnd); 90 | } 91 | } 92 | // 检测到正在运行的相同进程 93 | return true; 94 | } 95 | 96 | // 没有检测到正在运行的相同进程 97 | return false; 98 | } 99 | #endregion 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // 有关程序集的常规信息通过以下 6 | // 特性集控制。更改这些特性值可修改 7 | // 与程序集关联的信息。 8 | [assembly: AssemblyTitle("QiNiuDrive")] 9 | [assembly: AssemblyDescription("七牛云盘是基于七牛开放 API 构建的第三方同步程序")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("无闻")] 12 | [assembly: AssemblyProduct("QiNiuDrive")] 13 | [assembly: AssemblyCopyright("Copyright © 2013 无闻")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // 将 ComVisible 设置为 false 使此程序集中的类型 18 | // 对 COM 组件不可见。如果需要从 COM 访问此程序集中的类型, 19 | // 则将该类型上的 ComVisible 特性设置为 true。 20 | [assembly: ComVisible(false)] 21 | 22 | // 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID 23 | [assembly: Guid("fbf10096-0bf0-496b-92b9-fe6a1f132a45")] 24 | 25 | // 程序集的版本信息由下面四个值组成: 26 | // 27 | // 主版本 28 | // 次版本 29 | // 生成号 30 | // 修订号 31 | // 32 | // 可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值, 33 | // 方法是按如下所示使用“*”: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("0.1.3.1021")] 37 | -------------------------------------------------------------------------------- /Properties/Resources.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.33440 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace QiNiuDrive.Properties { 12 | using System; 13 | 14 | 15 | /// 16 | /// A strongly-typed resource class, for looking up localized strings, etc. 17 | /// 18 | // This class was auto-generated by the StronglyTypedResourceBuilder 19 | // class via a tool like ResGen or Visual Studio. 20 | // To add or remove a member, edit your .ResX file then rerun ResGen 21 | // with the /str option, or rebuild your VS project. 22 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] 23 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] 24 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 25 | internal class Resources { 26 | 27 | private static global::System.Resources.ResourceManager resourceMan; 28 | 29 | private static global::System.Globalization.CultureInfo resourceCulture; 30 | 31 | [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] 32 | internal Resources() { 33 | } 34 | 35 | /// 36 | /// Returns the cached ResourceManager instance used by this class. 37 | /// 38 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 39 | internal static global::System.Resources.ResourceManager ResourceManager { 40 | get { 41 | if (object.ReferenceEquals(resourceMan, null)) { 42 | global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("QiNiuDrive.Properties.Resources", typeof(Resources).Assembly); 43 | resourceMan = temp; 44 | } 45 | return resourceMan; 46 | } 47 | } 48 | 49 | /// 50 | /// Overrides the current thread's CurrentUICulture property for all 51 | /// resource lookups using this strongly typed resource class. 52 | /// 53 | [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] 54 | internal static global::System.Globalization.CultureInfo Culture { 55 | get { 56 | return resourceCulture; 57 | } 58 | set { 59 | resourceCulture = value; 60 | } 61 | } 62 | 63 | /// 64 | /// Looks up a localized resource of type System.Drawing.Icon similar to (Icon). 65 | /// 66 | internal static System.Drawing.Icon icon { 67 | get { 68 | object obj = ResourceManager.GetObject("icon", resourceCulture); 69 | return ((System.Drawing.Icon)(obj)); 70 | } 71 | } 72 | 73 | /// 74 | /// Looks up a localized resource of type System.Drawing.Bitmap. 75 | /// 76 | internal static System.Drawing.Bitmap logo { 77 | get { 78 | object obj = ResourceManager.GetObject("logo", resourceCulture); 79 | return ((System.Drawing.Bitmap)(obj)); 80 | } 81 | } 82 | 83 | /// 84 | /// Looks up a localized resource of type System.Drawing.Bitmap. 85 | /// 86 | internal static System.Drawing.Bitmap qiniu_logo { 87 | get { 88 | object obj = ResourceManager.GetObject("qiniu_logo", resourceCulture); 89 | return ((System.Drawing.Bitmap)(obj)); 90 | } 91 | } 92 | 93 | /// 94 | /// Looks up a localized resource of type System.Drawing.Bitmap. 95 | /// 96 | internal static System.Drawing.Bitmap tab_button_setting_chosen { 97 | get { 98 | object obj = ResourceManager.GetObject("tab_button_setting_chosen", resourceCulture); 99 | return ((System.Drawing.Bitmap)(obj)); 100 | } 101 | } 102 | 103 | /// 104 | /// Looks up a localized resource of type System.Drawing.Bitmap. 105 | /// 106 | internal static System.Drawing.Bitmap unknown { 107 | get { 108 | object obj = ResourceManager.GetObject("unknown", resourceCulture); 109 | return ((System.Drawing.Bitmap)(obj)); 110 | } 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /Properties/Resources.resx: -------------------------------------------------------------------------------- 1 |  2 | 3 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | text/microsoft-resx 110 | 111 | 112 | 2.0 113 | 114 | 115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 116 | 117 | 118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 119 | 120 | 121 | 122 | ..\Resources\Icons\icon.ico;System.Drawing.Icon, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 123 | 124 | 125 | ..\Resources\Icons\logo.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 126 | 127 | 128 | ..\Resources\FrmMain\qiniu_logo.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 129 | 130 | 131 | ..\Resources\FrmMain\tab_button_setting_chosen.png;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 132 | 133 | 134 | ..\Resources\FrmMain\unknown.jpg;System.Drawing.Bitmap, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a 135 | 136 | -------------------------------------------------------------------------------- /Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.33440 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace QiNiuDrive.Properties { 12 | 13 | 14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "12.0.0.0")] 16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { 17 | 18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 19 | 20 | public static Settings Default { 21 | get { 22 | return defaultInstance; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /QiNiuDrive.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {EC83ADAF-F396-4385-A1E2-A1CFA1BD336F} 8 | WinExe 9 | Properties 10 | QiNiuDrive 11 | 七牛云盘 12 | v4.0 13 | 512 14 | 15 | publish\ 16 | true 17 | Disk 18 | false 19 | Foreground 20 | 7 21 | Days 22 | false 23 | false 24 | true 25 | 0 26 | 1.0.0.%2a 27 | false 28 | false 29 | true 30 | 31 | 32 | x86 33 | true 34 | full 35 | false 36 | ..\Applications\七牛云盘\ 37 | DEBUG;TRACE 38 | prompt 39 | 4 40 | 41 | 42 | x86 43 | pdbonly 44 | true 45 | ..\Applications\七牛云盘\ 46 | TRACE 47 | prompt 48 | 4 49 | 50 | 51 | Resources\Icons\icon.ico 52 | 53 | 54 | 55 | ..\Libraries\CharmCommonMethod.dll 56 | 57 | 58 | ..\Libraries\CharmControlLibrary.dll 59 | 60 | 61 | False 62 | ..\Libraries\Newtonsoft.Json.dll 63 | 64 | 65 | ..\Libraries\Qiniu.dll 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | Form 80 | 81 | 82 | FrmFilterTester.cs 83 | 84 | 85 | Form 86 | 87 | 88 | FrmMain.cs 89 | 90 | 91 | 92 | 93 | FrmFilterTester.cs 94 | 95 | 96 | FrmMain.cs 97 | 98 | 99 | ResXFileCodeGenerator 100 | Resources.Designer.cs 101 | Designer 102 | 103 | 104 | True 105 | Resources.resx 106 | True 107 | 108 | 109 | 110 | SettingsSingleFileGenerator 111 | Settings.Designer.cs 112 | 113 | 114 | True 115 | Settings.settings 116 | True 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | False 137 | Microsoft .NET Framework 4 %28x86 and x64%29 138 | true 139 | 140 | 141 | False 142 | .NET Framework 3.5 SP1 Client Profile 143 | false 144 | 145 | 146 | False 147 | .NET Framework 3.5 SP1 148 | false 149 | 150 | 151 | False 152 | Windows Installer 4.5 153 | true 154 | 155 | 156 | 157 | 164 | -------------------------------------------------------------------------------- /QiNiuDrive.csproj.DotSettings: -------------------------------------------------------------------------------- 1 |  2 | No -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 七牛云盘 2 | ========== 3 | [![Requirement >= .NET Framework 4.0](http://b.repl.ca/v1/Requirement-%3E%3D_.NET_Framework_4.0-blue.png)]() 4 | 5 | 七牛云盘是基于七牛开放 API 构建的第三方同步程序。 6 | 7 | ## 软件功能(v0.1.5) 8 | 9 | - [更新日志](https://github.com/Unknwon/qiniudrive/wiki/%E6%9B%B4%E6%96%B0%E6%97%A5%E5%BF%97) 10 | - 自定义同步目录并进行单向上传同步。 11 | - 支持对每个设备单独设置前缀,避免覆盖及帮助识别。 12 | - 支持设置 `Config/filter.txt` 进行文件过滤。 13 | - `*/.git/*` 表示包含过滤。 14 | - `*.txt` 表示后缀过滤。 15 | - `look*` 表示前缀过滤。 16 | - `ok/fine.txt` 表示相对于同步目录的相对路径过滤。 17 | 18 | ## 注意事项 19 | 20 | - 当前版本仅支持文件或目录的 **重命名** 事件捕捉,如果文件或目录发生 **移动** 操作,则会先删除,再上传。 21 | - 书写文件过滤规则时,目录分割符均为 `/`。 22 | - 默认忽略所有隐藏文件、以 `.tmp` 结尾的文件。 23 | 24 | ## 已知问题 25 | 26 | - 如果您将旧的文件覆盖同步目录中的同名文件,会重新从云端下载旧的文件,这是由于通过只判断最后修改时间导致的。 27 | 28 | ## 主要原理 29 | 30 | - 同步周期:为节省 API 调用次数,软件根据用户指定周期(默认为 60 秒)进行文件差异判断,因此如果您在不同设备间进行同步,务必人为确保完成同步后,再修改其它设备上的文件。 31 | - 差异判断:通过文件最后修改时间完成差异判断,因此如果您通过其它手段非正常你不能 修改文件时间后可能会导致同步结果无法预料。 32 | - 文件删除:每次同步完成后会将本地文件建立缓存列表并保存至 `Data/file_cache_list`。因此,您对该文件的任何非法改动都可能将导致您的数据意外丢失! 33 | - 云端删除:判断是需要上传还是其它设备将文件删除,也是依靠本地缓存列表判断的。如果已存在缓存列表中,则说明该文件已经与云端完成同步,此时发现云端文件被删除,既表示是从其它设备进行删除操作;反之,则表示未进行过同步,需要上传。 34 | - 文件重命名:为节省 API 调用次数,在软件运行时采用事件捕捉机制,在同步时使用批量移动操作统一重命名。如果您在未启动软件时重命名文件或目录,则会导致软件进行先删除,再上传操作。 35 | 36 | ## 界面预览 37 | 38 | ![](imgs/SyncSetting.png) 39 | -------------------------------------------------------------------------------- /Resources/FrmMain/qiniu_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unknwon/qiniudrive/bc3d53de13c1628b37932ea06575f4c22ef92a3c/Resources/FrmMain/qiniu_logo.png -------------------------------------------------------------------------------- /Resources/FrmMain/tab_button_setting_chosen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unknwon/qiniudrive/bc3d53de13c1628b37932ea06575f4c22ef92a3c/Resources/FrmMain/tab_button_setting_chosen.png -------------------------------------------------------------------------------- /Resources/FrmMain/unknown.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unknwon/qiniudrive/bc3d53de13c1628b37932ea06575f4c22ef92a3c/Resources/FrmMain/unknown.jpg -------------------------------------------------------------------------------- /Resources/Icons/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unknwon/qiniudrive/bc3d53de13c1628b37932ea06575f4c22ef92a3c/Resources/Icons/icon.ico -------------------------------------------------------------------------------- /Resources/Icons/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unknwon/qiniudrive/bc3d53de13c1628b37932ea06575f4c22ef92a3c/Resources/Icons/logo.png -------------------------------------------------------------------------------- /VERSION.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 201312030, 3 | "download_url": "https://github.com/Unknwon/qiniudrive" 4 | } -------------------------------------------------------------------------------- /app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /imgs/SyncSetting.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unknwon/qiniudrive/bc3d53de13c1628b37932ea06575f4c22ef92a3c/imgs/SyncSetting.png -------------------------------------------------------------------------------- /libs.rar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/unknwon/qiniudrive/bc3d53de13c1628b37932ea06575f4c22ef92a3c/libs.rar --------------------------------------------------------------------------------