Updater.cs 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. using System.IO;
  5. using System.Threading;
  6. using CommonLang;
  7. using CommonLang.File;
  8. using CommonLang.Concurrent;
  9. using CommonLang.Log;
  10. namespace MPQ.Updater
  11. {
  12. public class MPQUpdater : IDisposable
  13. {
  14. private CommonLang.Log.Logger log = LoggerFactory.GetLogger("MPQUpdater");
  15. public static string ZIP_EXT = ".z";
  16. public static string MPQ_EXT = ".mpq";
  17. public static string VERSION_TEXT_BEGIN = "BEGIN";
  18. public static string VERSION_TEXT_END = "END";
  19. private static UTF8Encoding UTF8 = new UTF8Encoding(false);
  20. private static readonly char Separator = Path.DirectorySeparatorChar;
  21. private readonly MPQDirver driver;
  22. private Uri version_url;
  23. private FileInfo version_file;
  24. private string[] url_roots;
  25. private string version_text = "";
  26. private MPQUpdaterListener listener;
  27. private SyncMessageQueue<MPQUpdaterEvent> events = new SyncMessageQueue<MPQUpdaterEvent>();
  28. private bool is_running = false;
  29. private bool is_exit = false;
  30. private long last_update_time;
  31. private long last_update_download_bytes = 0;
  32. private long last_update_unzip_bytes = 0;
  33. private long current_download_speed_BPS = 0;
  34. private long current_unzip_spped_BPS = 0;
  35. private DirectoryInfo stream_root;
  36. private DirectoryInfo save_root;
  37. private bool is_check_md5 = false;
  38. private Thread workthread;
  39. private long total_unzip_bytes = 0;
  40. private long total_download_bytes = 0;
  41. private AtomicLong current_unzip_bytes = new AtomicLong(0);
  42. private AtomicLong current_download_bytes = new AtomicLong(0);
  43. private List<RemoteFileInfo> remoteFiles = new List<RemoteFileInfo>();
  44. private AtomicReference<Status> status = new AtomicReference<Status>(Status.NA);
  45. private long _mSaveAvaliable = 0L;
  46. public DirectoryInfo LocalSaveRoot { get { return save_root; } }
  47. public MPQUpdater(MPQDirver d = null)
  48. {
  49. if (d == null) { d = new MPQDirver(); }
  50. this.driver = d;
  51. }
  52. /// <summary>
  53. /// 创建自动更新程序
  54. /// </summary>
  55. /// <param name="remote_version_url">远程列表文件地址(http://localhost/xxxx/version.txt)</param>
  56. /// <param name="remote_version_prefix">远程下载地址根目录(多个备选)</param>
  57. /// <param name="version_suffix">下载资源类型后缀</param>
  58. /// <param name="local_save_root">本地存储目录</param>
  59. /// <param name="local_bundle_root">本地包内资源目录</param>
  60. /// <param name="validate_md5">是否验证MD5</param>
  61. /// <param name="listener">监听器</param>
  62. public void Init(
  63. Uri remote_version_url,
  64. string[] remote_version_prefix,
  65. string version_suffix,
  66. DirectoryInfo local_save_root,
  67. DirectoryInfo local_bundle_root,
  68. string mpq_txt,
  69. bool validate_md5,
  70. MPQUpdaterListener listener)
  71. {
  72. if (remote_version_prefix.Length == 0)
  73. {
  74. throw new Exception("remote_version_prefix length is 0 !!!");
  75. }
  76. if (local_save_root.Equals(local_bundle_root))
  77. {
  78. throw new Exception("save root cannot be bundle root !!!");
  79. }
  80. stream_root = local_bundle_root;
  81. this.save_root = local_save_root;
  82. this.version_file = new FileInfo(Path.GetFullPath(local_save_root.FullName + Separator + version_suffix));
  83. this.url_roots = remote_version_prefix;
  84. this.version_url = remote_version_url;
  85. this.is_check_md5 = validate_md5;
  86. versionText = mpq_txt;
  87. this.listener = listener;
  88. this.DownloadTimeoutSEC = 15;
  89. }
  90. public void Init(
  91. string[] remote_version_prefix,
  92. string version_suffix,
  93. DirectoryInfo local_save_root,
  94. DirectoryInfo local_bundle_root,
  95. bool validate_md5,
  96. MPQUpdaterListener listener)
  97. {
  98. if (remote_version_prefix.Length == 0)
  99. {
  100. throw new Exception("remote_version_prefix length is 0 !!!");
  101. }
  102. if (local_save_root.Equals(local_bundle_root))
  103. {
  104. throw new Exception("save root cannot be bundle root !!!");
  105. }
  106. this.save_root = local_save_root;// new DirectoryInfo(Path.GetFullPath(local_save_root));
  107. this.stream_root = local_bundle_root;// new DirectoryInfo(Path.GetFullPath(local_bundle_root));
  108. this.version_file = new FileInfo(Path.GetFullPath(local_save_root.FullName + Separator + version_suffix));
  109. this.url_roots = remote_version_prefix;
  110. this.version_url = new Uri(url_roots[0] + "/" + version_suffix);
  111. this.is_check_md5 = validate_md5;
  112. this.listener = listener;
  113. this.DownloadTimeoutSEC = 30;
  114. }
  115. Action<float> checkMpqUpdateCb = null;
  116. public void CheckNeedUpdate(bool userMpq, Action<float> checkCb)
  117. {
  118. if(userMpq)
  119. {
  120. checkMpqUpdateCb = checkCb;
  121. var thread = new Thread(CheckThread);
  122. thread.IsBackground = true;
  123. status.Value = Status.Checking;
  124. thread.Start();
  125. }
  126. else
  127. {
  128. IsLoadFinised = true;
  129. status.Value = Status.Done;
  130. checkCb(0);
  131. }
  132. }
  133. public float CheckProgress = 0f;
  134. public string versionText;
  135. public long needSize = 0;
  136. public Queue<RemoteFileInfo> need_files = new Queue<RemoteFileInfo>();
  137. void CheckThread()
  138. {
  139. remoteFiles.Clear();
  140. HashMap<string, RemoteFileInfo> _zip_files = new HashMap<string, RemoteFileInfo>();
  141. HashMap<string, RemoteFileInfo> _bin_files = new HashMap<string, RemoteFileInfo>();
  142. if(string.IsNullOrEmpty(versionText))
  143. {
  144. versionText = CommonNetwork.Http.WebClient.DownloadString(version_url, UTF8);
  145. }
  146. if (versionText.StartsWith(VERSION_TEXT_BEGIN) && versionText.EndsWith(VERSION_TEXT_END))
  147. {
  148. char[] spc = { ':' };
  149. string[] lines = versionText.Split('\n');
  150. int cur = 0;
  151. int len = lines.Length;
  152. foreach (string line in lines)
  153. {
  154. string[] kv = line.Split(spc, 3);
  155. if (kv.Length == 3)
  156. {
  157. string key = kv[2].Trim().Replace('\\', Separator);
  158. string md5 = kv[0].Trim();
  159. uint fsize = uint.Parse(kv[1].Trim());
  160. //如果peristerpath有此文件
  161. var _savePath = Path.GetFullPath(save_root.FullName + Separator + key);
  162. FileInfo localFile = new FileInfo(_savePath);
  163. RemoteFileInfo localinf = new RemoteFileInfo(md5, fsize, key, localFile);
  164. remoteFiles.Add(localinf);
  165. if (localFile.Exists)
  166. {
  167. if (localFile.Length == fsize)
  168. {
  169. if(key.EndsWith(ZIP_EXT))
  170. {
  171. // 如果是没有解压的zip ,就直接放在need_files 进入下一步
  172. need_files.Enqueue(localinf);
  173. }
  174. cur++;
  175. CheckProgress = cur * 1f / len;
  176. continue;
  177. }
  178. else if (localFile.Length > fsize)
  179. {
  180. localFile.Delete();
  181. }
  182. }
  183. if (key.ToLower().EndsWith(ZIP_EXT))
  184. {
  185. _zip_files[localFile.Name] = localinf;
  186. needSize += localinf.size;
  187. }
  188. else if (!key.ToLower().EndsWith(MPQ_EXT))
  189. {
  190. _bin_files[localFile.Name] = localinf;
  191. needSize += localinf.size;
  192. }
  193. else
  194. {
  195. cur++;
  196. CheckProgress = cur * 1f / len;
  197. }
  198. }
  199. }
  200. List<RemoteFileInfo> trydownlist = new List<RemoteFileInfo>();
  201. trydownlist.AddRange(_zip_files.Values);
  202. trydownlist.AddRange(_bin_files.Values);
  203. foreach (RemoteFileInfo inf in trydownlist)
  204. {
  205. // 确认对应的MPQ文件是否完整 //
  206. if (inf.key.EndsWith(ZIP_EXT))
  207. {
  208. string mpq_name = inf.key.TrimEnd('.', 'z');
  209. var mpq_file = remoteFiles.Find(data => data.key == mpq_name);
  210. if (mpq_file != null && mpq_file.IsCompletion())
  211. {
  212. cur++;
  213. CheckProgress = cur * 1f / len;
  214. needSize -= inf.size;
  215. inf.file.Delete();
  216. continue;
  217. }
  218. }
  219. // 如果已经存在未下载完成的
  220. if (inf.file.Exists)
  221. {
  222. needSize -= inf.size;
  223. needSize += inf.NeedLoadSize();
  224. }
  225. need_files.Enqueue(inf);
  226. cur++;
  227. CheckProgress = cur * 1f / len;
  228. }
  229. }
  230. var mbytes = (float)Math.Round(needSize / (float)1048576, 2);
  231. if (checkMpqUpdateCb != null)
  232. {
  233. if (mbytes == 0)
  234. {
  235. if(need_files.Count == 0)
  236. {
  237. IsLoadFinised = true;
  238. status.Value = Status.Done;
  239. }
  240. else
  241. {
  242. IsLoadFinised = true;
  243. status.Value = Status.Unzipping;
  244. }
  245. }
  246. checkMpqUpdateCb(mbytes);
  247. }
  248. }
  249. /**
  250. * 开始自动更新
  251. */
  252. public void Start()
  253. {
  254. _mSaveAvaliable = this.driver.GetAvaliableSpace(this.save_root.FullName);
  255. this.workthread = new Thread(new ThreadStart(Run));
  256. this.workthread.Name = "MPQUpdater";
  257. this.workthread.Start();
  258. }
  259. public bool HaveEnoughSpace(float size)
  260. {
  261. var uSize = (long)Math.Ceiling(size * 1048576);
  262. var space = driver.GetAvaliableSpace(save_root.FullName);
  263. return space > uSize;
  264. }
  265. public void Update()
  266. {
  267. long now_time = Environment.TickCount;
  268. if (Math.Abs(now_time - last_update_time) >= 1000)
  269. {
  270. double delta_time = now_time - last_update_time;
  271. double delta_download = current_download_bytes.Value - last_update_download_bytes;
  272. double delta_unzip = current_unzip_bytes.Value - last_update_unzip_bytes;
  273. this.current_download_speed_BPS = (long)(delta_download / delta_time * 1000.0);
  274. this.current_unzip_spped_BPS = (long)(delta_unzip / delta_time * 1000.0);
  275. this.last_update_time = now_time;
  276. this.last_update_download_bytes = current_download_bytes.Value;
  277. this.last_update_unzip_bytes = current_unzip_bytes.Value;
  278. }
  279. events.ProcessMessages(DoEvent);
  280. }
  281. private void QueueEvent(MPQUpdaterEvent evt)
  282. {
  283. listener.onEvent(this, evt);
  284. }
  285. public string GetThreadSattus()
  286. {
  287. log.Log(workthread.IsAlive.ToString());
  288. return workthread.ThreadState.ToString();
  289. }
  290. public void RunTry()
  291. {
  292. Start();
  293. }
  294. private void DoEvent(MPQUpdaterEvent e)
  295. {
  296. listener.onEvent(this, e);
  297. }
  298. public void Run()
  299. {
  300. try
  301. {
  302. new RunTask(this).Run();
  303. }
  304. catch (Exception err)
  305. {
  306. log.Error(err.Message, err);
  307. }
  308. }
  309. //----------------------------------------------------------------------------------------------------------
  310. public enum Status
  311. {
  312. Error = -1,
  313. NA = 0,
  314. Checking = 1,
  315. Downloading = 2,
  316. Unzipping = 3,
  317. Done = 4,
  318. }
  319. class RunTask
  320. {
  321. private CommonLang.Log.Logger log;
  322. private readonly MPQUpdater updater;
  323. private Queue<RemoteFileInfo> _zip_files = new Queue<RemoteFileInfo>();
  324. public RunTask(MPQUpdater updater)
  325. {
  326. this.log = updater.log;
  327. this.updater = updater;
  328. }
  329. public void Run()
  330. {
  331. updater.is_running = true;
  332. try
  333. {
  334. // 先删除 资源服上 没有的资源
  335. List<FileInfo> exists = CFiles.listAllFiles(updater.save_root);
  336. foreach (FileInfo ff in exists)
  337. {
  338. var remoteAsset = updater.remoteFiles.Find(data => data.file.Name == ff.Name);
  339. if (remoteAsset == null)
  340. {
  341. ff.Delete();
  342. }
  343. }
  344. if (!updater.IsLoadFinised)
  345. {
  346. // 下载资源
  347. updater.status.Value = Status.Downloading;
  348. }
  349. if (run_download_zips())
  350. {
  351. return;
  352. }
  353. updater.IsLoadFinised = true;
  354. // 解压资源
  355. updater.status.Value = Status.Unzipping;
  356. if (run_unzip())
  357. {
  358. return;
  359. }
  360. updater.status.Value = Status.Done;
  361. }
  362. catch (Exception err)
  363. {
  364. log.DebugFormat(err.Message, err);
  365. updater.QueueEvent(new MPQUpdaterEvent(MPQUpdaterEvent.TYPE_ERROR, "ERROR : " + err.Message, err));
  366. }
  367. finally
  368. {
  369. updater.is_running = false;
  370. }
  371. }
  372. private bool run_download_zips()
  373. {
  374. var needFiles = updater.need_files;
  375. while(needFiles.Count > 0)
  376. {
  377. if (updater.is_exit) return true;
  378. var inf = needFiles.Dequeue();
  379. try
  380. {
  381. long need_bytes = inf.size;
  382. long exist_size = 0;
  383. // 如果已经存在未下载完成的 //
  384. if (inf.file.Exists)
  385. {
  386. exist_size = inf.file.Length;
  387. need_bytes = inf.size - exist_size;
  388. if (need_bytes == 0)
  389. {
  390. if (inf.key.EndsWith(ZIP_EXT))
  391. {
  392. var str = inf.key.TrimEnd('.', 'z');
  393. var file = updater.remoteFiles.Find(data => data.key == str);
  394. if (file != null)
  395. {
  396. updater.total_unzip_bytes += file.size;
  397. _zip_files.Enqueue(inf);
  398. }
  399. }
  400. continue;
  401. }
  402. }
  403. else
  404. {
  405. CFiles.createFile(inf.file);
  406. }
  407. // 如果需要的空间无法满足 //
  408. // long save_avaliable = updater.driver.GetAvaliableSpace(updater.save_root.FullName);
  409. long save_avaliable = updater._mSaveAvaliable;
  410. if (need_bytes >= save_avaliable)
  411. {
  412. needFiles.Enqueue(inf);
  413. updater.QueueEvent(new MPQUpdaterEvent(
  414. MPQUpdaterEvent.TYPE_NOT_ENOUGH_SPACE, "Space not available!" ));
  415. return true;
  416. }
  417. try
  418. {
  419. log.Info(string.Format("开始下载: {0} ", inf.key));
  420. if (run_download_single(inf, exist_size, need_bytes))
  421. {
  422. needFiles.Enqueue(inf);
  423. }
  424. else
  425. {
  426. if (inf.key.EndsWith(ZIP_EXT))
  427. {
  428. var str = inf.key.TrimEnd('.', 'z');
  429. var file = updater.remoteFiles.Find(data => data.key == str);
  430. if (file != null)
  431. {
  432. updater.total_unzip_bytes += file.size;
  433. _zip_files.Enqueue(inf);
  434. }
  435. }
  436. }
  437. }
  438. catch (Exception err)
  439. {
  440. log.Error(err);
  441. needFiles.Enqueue(inf);
  442. }
  443. }
  444. catch (Exception err)
  445. {
  446. needFiles.Enqueue(inf);
  447. log.ErrorFormat(err.Message, err);
  448. updater.QueueEvent(
  449. new MPQUpdaterEvent(MPQUpdaterEvent.TYPE_ERROR,
  450. string.Format("DOWNLOAD_ZIPS : {0} : {1}", inf.file, err.Message), err));
  451. return true;
  452. }
  453. }
  454. return false;
  455. }
  456. private bool run_download_single(RemoteFileInfo inf, long exist_size, long need_bytes)
  457. {
  458. long old_current_download_bytes = updater.current_download_bytes.Value;
  459. if (updater.driver.RunDownloadSingle(updater, inf, exist_size, need_bytes, updater.current_download_bytes) == false)
  460. {
  461. return true;
  462. }
  463. if (!check_md5(inf.file, inf.md5)) return true;
  464. if ((old_current_download_bytes + need_bytes) != updater.current_download_bytes.Value)
  465. {
  466. updater.current_download_bytes.Value = old_current_download_bytes + need_bytes;
  467. }
  468. return false;
  469. }
  470. private bool run_unzip()
  471. {
  472. while(_zip_files.Count > 0 )
  473. {
  474. var inf = _zip_files.Dequeue();
  475. if (updater.is_exit) return true;
  476. var mpqName = inf.key.TrimEnd('.', 'z');
  477. var info = updater.remoteFiles.Find(data => data.key == mpqName);
  478. // 如果需要的空间无法满足
  479. // long save_avaliable = updater.driver.GetAvaliableSpace(updater.save_root.FullName);
  480. long save_avaliable = updater._mSaveAvaliable;
  481. long need_bytes = info.size;
  482. if (need_bytes >= save_avaliable)
  483. {
  484. _zip_files.Enqueue(inf);
  485. updater.QueueEvent(new MPQUpdaterEvent(
  486. MPQUpdaterEvent.TYPE_NOT_ENOUGH_SPACE, "Space not available!"));
  487. return true;
  488. }
  489. // 开始解压缩
  490. if (inf.file.Exists)
  491. {
  492. try
  493. {
  494. log.Info(string.Format("开始解压缩: {0} -> {1}", inf.key, info.file.Extension));
  495. if (run_unzip_single(inf, info) || !info.file.Exists)
  496. {
  497. _zip_files.Enqueue(inf);
  498. }
  499. else
  500. {
  501. inf.file.Delete();
  502. }
  503. }
  504. catch (Exception err)
  505. {
  506. log.Error(err);
  507. _zip_files.Enqueue(inf);
  508. throw;
  509. }
  510. }
  511. }
  512. return false;
  513. }
  514. private bool run_unzip_single(RemoteFileInfo inf, RemoteFileInfo mpqf)
  515. {
  516. long need_bytes = mpqf.size;
  517. long old_current_unzip_bytes = updater.current_unzip_bytes.Value;
  518. if (updater.driver.RunUnzipSingle(updater, inf, mpqf, updater.current_unzip_bytes) == false)
  519. {
  520. return true;
  521. }
  522. if ((old_current_unzip_bytes + need_bytes) != updater.current_unzip_bytes.Value)
  523. {
  524. updater.current_unzip_bytes.Value = old_current_unzip_bytes + need_bytes;
  525. }
  526. return false;
  527. }
  528. private bool check_md5(FileInfo ff, string md5)
  529. {
  530. string fmd5;
  531. if (updater.driver.RunGetFileMD5(ff.FullName, out fmd5) == false)
  532. {
  533. return false;
  534. }
  535. if (fmd5.ToLower().Equals(md5.ToLower()))
  536. {
  537. return true;
  538. }
  539. return false;
  540. }
  541. }
  542. //----------------------------------------------------------------------------------------------------------
  543. public void Dispose()
  544. {
  545. is_exit = true;
  546. try
  547. {
  548. if (workthread != null)
  549. {
  550. workthread.Join();
  551. }
  552. }
  553. catch (System.Exception err)
  554. {
  555. log.Error(err.Message, err);
  556. //Console.WriteLine(e);
  557. }
  558. }
  559. public ICollection<FileInfo> GetAllFiles()
  560. {
  561. List<FileInfo> files = new List<FileInfo>();
  562. lock (remoteFiles)
  563. {
  564. foreach (RemoteFileInfo rf in remoteFiles)
  565. {
  566. files.Add(rf.file);
  567. }
  568. }
  569. return files;
  570. }
  571. public int DownloadTimeoutSEC
  572. {
  573. get;
  574. set;
  575. }
  576. public string[] UrlRoots { get { return url_roots; } }
  577. public string UrlRoot { get { return url_roots[0]; } }
  578. public string VersionText
  579. {
  580. get { return version_text; }
  581. }
  582. public long TotalDownloadBytes
  583. {
  584. get { return total_download_bytes; }
  585. }
  586. public long CurrentDownloadBytes
  587. {
  588. get { return current_download_bytes.Value; }
  589. }
  590. public long CurrentDownloadSpeed
  591. {
  592. get { return current_download_speed_BPS; }
  593. }
  594. public long TotalUnzipBytes
  595. {
  596. get { return total_unzip_bytes; }
  597. }
  598. public long CurrentUnzipBytes
  599. {
  600. get { return current_unzip_bytes.Value; }
  601. }
  602. public long CurrentUnzipSpeed
  603. {
  604. get { return current_unzip_spped_BPS; }
  605. }
  606. public bool IsRunning
  607. {
  608. get { return is_running; }
  609. }
  610. public bool IsDisposing
  611. {
  612. get { return is_running && is_exit; }
  613. }
  614. public Status CurrentStatus { get { return status.Value; } }
  615. public bool IsLoadFinised { get; private set; }
  616. //-----------------------------------------------------------------------------------------------------------
  617. //-----------------------------------------------------------------------------------------------------------
  618. public class RemoteFileInfo
  619. {
  620. private FileInfo _file;
  621. public readonly string md5;
  622. public readonly string key;
  623. public readonly uint size;
  624. public FileInfo file
  625. {
  626. get { _file.Refresh(); return _file; }
  627. }
  628. internal RemoteFileInfo(string md5, uint size, string key, FileInfo file)
  629. {
  630. this.md5 = md5;
  631. this.key = key;
  632. this.size = size;
  633. this._file = file;
  634. }
  635. internal bool IsCompletion()
  636. {
  637. if (file.Exists && file.Length == size)
  638. {
  639. return true;
  640. }
  641. return false;
  642. }
  643. internal long NeedLoadSize()
  644. {
  645. if (file.Exists) return (size - file.Length);
  646. else return size;
  647. }
  648. }
  649. }
  650. }