123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730 |
- using System;
- using System.Collections.Generic;
- using System.Text;
- using System.IO;
- using System.Threading;
- using CommonLang;
- using CommonLang.File;
- using CommonLang.Concurrent;
- using CommonLang.Log;
- namespace MPQ.Updater
- {
- public class MPQUpdater : IDisposable
- {
- private CommonLang.Log.Logger log = LoggerFactory.GetLogger("MPQUpdater");
- public static string ZIP_EXT = ".z";
- public static string MPQ_EXT = ".mpq";
- public static string VERSION_TEXT_BEGIN = "BEGIN";
- public static string VERSION_TEXT_END = "END";
- private static UTF8Encoding UTF8 = new UTF8Encoding(false);
- private static readonly char Separator = Path.DirectorySeparatorChar;
- private readonly MPQDirver driver;
- private Uri version_url;
- private FileInfo version_file;
- private string[] url_roots;
- private string version_text = "";
- private MPQUpdaterListener listener;
- private SyncMessageQueue<MPQUpdaterEvent> events = new SyncMessageQueue<MPQUpdaterEvent>();
- private bool is_running = false;
- private bool is_exit = false;
- private long last_update_time;
- private long last_update_download_bytes = 0;
- private long last_update_unzip_bytes = 0;
- private long current_download_speed_BPS = 0;
- private long current_unzip_spped_BPS = 0;
- private DirectoryInfo stream_root;
- private DirectoryInfo save_root;
- private bool is_check_md5 = false;
- private Thread workthread;
- private long total_unzip_bytes = 0;
- private long total_download_bytes = 0;
- private AtomicLong current_unzip_bytes = new AtomicLong(0);
- private AtomicLong current_download_bytes = new AtomicLong(0);
- private List<RemoteFileInfo> remoteFiles = new List<RemoteFileInfo>();
- private AtomicReference<Status> status = new AtomicReference<Status>(Status.NA);
- private long _mSaveAvaliable = 0L;
- public DirectoryInfo LocalSaveRoot { get { return save_root; } }
- public MPQUpdater(MPQDirver d = null)
- {
- if (d == null) { d = new MPQDirver(); }
- this.driver = d;
- }
- /// <summary>
- /// 创建自动更新程序
- /// </summary>
- /// <param name="remote_version_url">远程列表文件地址(http://localhost/xxxx/version.txt)</param>
- /// <param name="remote_version_prefix">远程下载地址根目录(多个备选)</param>
- /// <param name="version_suffix">下载资源类型后缀</param>
- /// <param name="local_save_root">本地存储目录</param>
- /// <param name="local_bundle_root">本地包内资源目录</param>
- /// <param name="validate_md5">是否验证MD5</param>
- /// <param name="listener">监听器</param>
- public void Init(
- Uri remote_version_url,
- string[] remote_version_prefix,
- string version_suffix,
- DirectoryInfo local_save_root,
- DirectoryInfo local_bundle_root,
- string mpq_txt,
- bool validate_md5,
- MPQUpdaterListener listener)
- {
- if (remote_version_prefix.Length == 0)
- {
- throw new Exception("remote_version_prefix length is 0 !!!");
- }
- if (local_save_root.Equals(local_bundle_root))
- {
- throw new Exception("save root cannot be bundle root !!!");
- }
- stream_root = local_bundle_root;
- this.save_root = local_save_root;
- this.version_file = new FileInfo(Path.GetFullPath(local_save_root.FullName + Separator + version_suffix));
- this.url_roots = remote_version_prefix;
- this.version_url = remote_version_url;
- this.is_check_md5 = validate_md5;
- versionText = mpq_txt;
- this.listener = listener;
- this.DownloadTimeoutSEC = 15;
- }
- public void Init(
- string[] remote_version_prefix,
- string version_suffix,
- DirectoryInfo local_save_root,
- DirectoryInfo local_bundle_root,
- bool validate_md5,
- MPQUpdaterListener listener)
- {
- if (remote_version_prefix.Length == 0)
- {
- throw new Exception("remote_version_prefix length is 0 !!!");
- }
- if (local_save_root.Equals(local_bundle_root))
- {
- throw new Exception("save root cannot be bundle root !!!");
- }
- this.save_root = local_save_root;// new DirectoryInfo(Path.GetFullPath(local_save_root));
- this.stream_root = local_bundle_root;// new DirectoryInfo(Path.GetFullPath(local_bundle_root));
- this.version_file = new FileInfo(Path.GetFullPath(local_save_root.FullName + Separator + version_suffix));
- this.url_roots = remote_version_prefix;
- this.version_url = new Uri(url_roots[0] + "/" + version_suffix);
- this.is_check_md5 = validate_md5;
- this.listener = listener;
- this.DownloadTimeoutSEC = 30;
- }
- Action<float> checkMpqUpdateCb = null;
- public void CheckNeedUpdate(bool userMpq, Action<float> checkCb)
- {
- if(userMpq)
- {
- checkMpqUpdateCb = checkCb;
- var thread = new Thread(CheckThread);
- thread.IsBackground = true;
- status.Value = Status.Checking;
- thread.Start();
- }
- else
- {
- IsLoadFinised = true;
- status.Value = Status.Done;
- checkCb(0);
- }
- }
- public float CheckProgress = 0f;
- public string versionText;
- public long needSize = 0;
- public Queue<RemoteFileInfo> need_files = new Queue<RemoteFileInfo>();
- void CheckThread()
- {
- remoteFiles.Clear();
- HashMap<string, RemoteFileInfo> _zip_files = new HashMap<string, RemoteFileInfo>();
- HashMap<string, RemoteFileInfo> _bin_files = new HashMap<string, RemoteFileInfo>();
- if(string.IsNullOrEmpty(versionText))
- {
- versionText = CommonNetwork.Http.WebClient.DownloadString(version_url, UTF8);
- }
- if (versionText.StartsWith(VERSION_TEXT_BEGIN) && versionText.EndsWith(VERSION_TEXT_END))
- {
- char[] spc = { ':' };
- string[] lines = versionText.Split('\n');
- int cur = 0;
- int len = lines.Length;
- foreach (string line in lines)
- {
- string[] kv = line.Split(spc, 3);
- if (kv.Length == 3)
- {
- string key = kv[2].Trim().Replace('\\', Separator);
- string md5 = kv[0].Trim();
- uint fsize = uint.Parse(kv[1].Trim());
- //如果peristerpath有此文件
- var _savePath = Path.GetFullPath(save_root.FullName + Separator + key);
- FileInfo localFile = new FileInfo(_savePath);
- RemoteFileInfo localinf = new RemoteFileInfo(md5, fsize, key, localFile);
- remoteFiles.Add(localinf);
- if (localFile.Exists)
- {
- if (localFile.Length == fsize)
- {
- if(key.EndsWith(ZIP_EXT))
- {
- // 如果是没有解压的zip ,就直接放在need_files 进入下一步
- need_files.Enqueue(localinf);
- }
- cur++;
- CheckProgress = cur * 1f / len;
- continue;
- }
- else if (localFile.Length > fsize)
- {
- localFile.Delete();
- }
- }
- if (key.ToLower().EndsWith(ZIP_EXT))
- {
- _zip_files[localFile.Name] = localinf;
- needSize += localinf.size;
- }
- else if (!key.ToLower().EndsWith(MPQ_EXT))
- {
- _bin_files[localFile.Name] = localinf;
- needSize += localinf.size;
- }
- else
- {
- cur++;
- CheckProgress = cur * 1f / len;
- }
- }
- }
- List<RemoteFileInfo> trydownlist = new List<RemoteFileInfo>();
- trydownlist.AddRange(_zip_files.Values);
- trydownlist.AddRange(_bin_files.Values);
- foreach (RemoteFileInfo inf in trydownlist)
- {
- // 确认对应的MPQ文件是否完整 //
- if (inf.key.EndsWith(ZIP_EXT))
- {
- string mpq_name = inf.key.TrimEnd('.', 'z');
- var mpq_file = remoteFiles.Find(data => data.key == mpq_name);
- if (mpq_file != null && mpq_file.IsCompletion())
- {
- cur++;
- CheckProgress = cur * 1f / len;
- needSize -= inf.size;
- inf.file.Delete();
- continue;
- }
- }
- // 如果已经存在未下载完成的
- if (inf.file.Exists)
- {
- needSize -= inf.size;
- needSize += inf.NeedLoadSize();
- }
- need_files.Enqueue(inf);
- cur++;
- CheckProgress = cur * 1f / len;
- }
- }
- var mbytes = (float)Math.Round(needSize / (float)1048576, 2);
- if (checkMpqUpdateCb != null)
- {
- if (mbytes == 0)
- {
- if(need_files.Count == 0)
- {
- IsLoadFinised = true;
- status.Value = Status.Done;
- }
- else
- {
- IsLoadFinised = true;
- status.Value = Status.Unzipping;
- }
- }
- checkMpqUpdateCb(mbytes);
- }
- }
- /**
- * 开始自动更新
- */
- public void Start()
- {
- _mSaveAvaliable = this.driver.GetAvaliableSpace(this.save_root.FullName);
- this.workthread = new Thread(new ThreadStart(Run));
- this.workthread.Name = "MPQUpdater";
- this.workthread.Start();
- }
- public bool HaveEnoughSpace(float size)
- {
- var uSize = (long)Math.Ceiling(size * 1048576);
- var space = driver.GetAvaliableSpace(save_root.FullName);
- return space > uSize;
- }
- public void Update()
- {
- long now_time = Environment.TickCount;
- if (Math.Abs(now_time - last_update_time) >= 1000)
- {
- double delta_time = now_time - last_update_time;
- double delta_download = current_download_bytes.Value - last_update_download_bytes;
- double delta_unzip = current_unzip_bytes.Value - last_update_unzip_bytes;
- this.current_download_speed_BPS = (long)(delta_download / delta_time * 1000.0);
- this.current_unzip_spped_BPS = (long)(delta_unzip / delta_time * 1000.0);
- this.last_update_time = now_time;
- this.last_update_download_bytes = current_download_bytes.Value;
- this.last_update_unzip_bytes = current_unzip_bytes.Value;
- }
- events.ProcessMessages(DoEvent);
- }
- private void QueueEvent(MPQUpdaterEvent evt)
- {
- listener.onEvent(this, evt);
- }
- public string GetThreadSattus()
- {
- log.Log(workthread.IsAlive.ToString());
- return workthread.ThreadState.ToString();
- }
- public void RunTry()
- {
- Start();
- }
- private void DoEvent(MPQUpdaterEvent e)
- {
- listener.onEvent(this, e);
- }
- public void Run()
- {
- try
- {
- new RunTask(this).Run();
- }
- catch (Exception err)
- {
- log.Error(err.Message, err);
- }
- }
- //----------------------------------------------------------------------------------------------------------
- public enum Status
- {
- Error = -1,
- NA = 0,
- Checking = 1,
- Downloading = 2,
- Unzipping = 3,
- Done = 4,
- }
- class RunTask
- {
- private CommonLang.Log.Logger log;
- private readonly MPQUpdater updater;
- private Queue<RemoteFileInfo> _zip_files = new Queue<RemoteFileInfo>();
- public RunTask(MPQUpdater updater)
- {
- this.log = updater.log;
- this.updater = updater;
- }
- public void Run()
- {
- updater.is_running = true;
- try
- {
- // 先删除 资源服上 没有的资源
- List<FileInfo> exists = CFiles.listAllFiles(updater.save_root);
- foreach (FileInfo ff in exists)
- {
- var remoteAsset = updater.remoteFiles.Find(data => data.file.Name == ff.Name);
- if (remoteAsset == null)
- {
- ff.Delete();
- }
- }
- if (!updater.IsLoadFinised)
- {
- // 下载资源
- updater.status.Value = Status.Downloading;
- }
- if (run_download_zips())
- {
- return;
- }
- updater.IsLoadFinised = true;
- // 解压资源
- updater.status.Value = Status.Unzipping;
- if (run_unzip())
- {
- return;
- }
- updater.status.Value = Status.Done;
- }
- catch (Exception err)
- {
- log.DebugFormat(err.Message, err);
- updater.QueueEvent(new MPQUpdaterEvent(MPQUpdaterEvent.TYPE_ERROR, "ERROR : " + err.Message, err));
- }
- finally
- {
- updater.is_running = false;
- }
- }
- private bool run_download_zips()
- {
- var needFiles = updater.need_files;
- while(needFiles.Count > 0)
- {
- if (updater.is_exit) return true;
- var inf = needFiles.Dequeue();
- try
- {
- long need_bytes = inf.size;
- long exist_size = 0;
- // 如果已经存在未下载完成的 //
- if (inf.file.Exists)
- {
- exist_size = inf.file.Length;
- need_bytes = inf.size - exist_size;
- if (need_bytes == 0)
- {
- if (inf.key.EndsWith(ZIP_EXT))
- {
- var str = inf.key.TrimEnd('.', 'z');
- var file = updater.remoteFiles.Find(data => data.key == str);
- if (file != null)
- {
- updater.total_unzip_bytes += file.size;
- _zip_files.Enqueue(inf);
- }
- }
- continue;
- }
- }
- else
- {
- CFiles.createFile(inf.file);
- }
- // 如果需要的空间无法满足 //
- // long save_avaliable = updater.driver.GetAvaliableSpace(updater.save_root.FullName);
- long save_avaliable = updater._mSaveAvaliable;
- if (need_bytes >= save_avaliable)
- {
- needFiles.Enqueue(inf);
- updater.QueueEvent(new MPQUpdaterEvent(
- MPQUpdaterEvent.TYPE_NOT_ENOUGH_SPACE, "Space not available!" ));
- return true;
- }
- try
- {
- log.Info(string.Format("开始下载: {0} ", inf.key));
- if (run_download_single(inf, exist_size, need_bytes))
- {
- needFiles.Enqueue(inf);
- }
- else
- {
- if (inf.key.EndsWith(ZIP_EXT))
- {
- var str = inf.key.TrimEnd('.', 'z');
- var file = updater.remoteFiles.Find(data => data.key == str);
- if (file != null)
- {
- updater.total_unzip_bytes += file.size;
- _zip_files.Enqueue(inf);
- }
- }
- }
- }
- catch (Exception err)
- {
- log.Error(err);
- needFiles.Enqueue(inf);
- }
- }
- catch (Exception err)
- {
- needFiles.Enqueue(inf);
- log.ErrorFormat(err.Message, err);
- updater.QueueEvent(
- new MPQUpdaterEvent(MPQUpdaterEvent.TYPE_ERROR,
- string.Format("DOWNLOAD_ZIPS : {0} : {1}", inf.file, err.Message), err));
- return true;
- }
- }
- return false;
- }
- private bool run_download_single(RemoteFileInfo inf, long exist_size, long need_bytes)
- {
- long old_current_download_bytes = updater.current_download_bytes.Value;
- if (updater.driver.RunDownloadSingle(updater, inf, exist_size, need_bytes, updater.current_download_bytes) == false)
- {
- return true;
- }
- if (!check_md5(inf.file, inf.md5)) return true;
- if ((old_current_download_bytes + need_bytes) != updater.current_download_bytes.Value)
- {
- updater.current_download_bytes.Value = old_current_download_bytes + need_bytes;
- }
- return false;
- }
- private bool run_unzip()
- {
- while(_zip_files.Count > 0 )
- {
- var inf = _zip_files.Dequeue();
- if (updater.is_exit) return true;
- var mpqName = inf.key.TrimEnd('.', 'z');
- var info = updater.remoteFiles.Find(data => data.key == mpqName);
- // 如果需要的空间无法满足
- // long save_avaliable = updater.driver.GetAvaliableSpace(updater.save_root.FullName);
- long save_avaliable = updater._mSaveAvaliable;
- long need_bytes = info.size;
- if (need_bytes >= save_avaliable)
- {
- _zip_files.Enqueue(inf);
- updater.QueueEvent(new MPQUpdaterEvent(
- MPQUpdaterEvent.TYPE_NOT_ENOUGH_SPACE, "Space not available!"));
- return true;
- }
- // 开始解压缩
- if (inf.file.Exists)
- {
- try
- {
- log.Info(string.Format("开始解压缩: {0} -> {1}", inf.key, info.file.Extension));
- if (run_unzip_single(inf, info) || !info.file.Exists)
- {
- _zip_files.Enqueue(inf);
- }
- else
- {
- inf.file.Delete();
- }
- }
- catch (Exception err)
- {
- log.Error(err);
- _zip_files.Enqueue(inf);
- throw;
- }
- }
- }
- return false;
- }
- private bool run_unzip_single(RemoteFileInfo inf, RemoteFileInfo mpqf)
- {
- long need_bytes = mpqf.size;
- long old_current_unzip_bytes = updater.current_unzip_bytes.Value;
- if (updater.driver.RunUnzipSingle(updater, inf, mpqf, updater.current_unzip_bytes) == false)
- {
- return true;
- }
- if ((old_current_unzip_bytes + need_bytes) != updater.current_unzip_bytes.Value)
- {
- updater.current_unzip_bytes.Value = old_current_unzip_bytes + need_bytes;
- }
- return false;
- }
- private bool check_md5(FileInfo ff, string md5)
- {
- string fmd5;
- if (updater.driver.RunGetFileMD5(ff.FullName, out fmd5) == false)
- {
- return false;
- }
- if (fmd5.ToLower().Equals(md5.ToLower()))
- {
- return true;
- }
- return false;
- }
- }
- //----------------------------------------------------------------------------------------------------------
- public void Dispose()
- {
- is_exit = true;
- try
- {
- if (workthread != null)
- {
- workthread.Join();
- }
- }
- catch (System.Exception err)
- {
- log.Error(err.Message, err);
- //Console.WriteLine(e);
- }
- }
- public ICollection<FileInfo> GetAllFiles()
- {
- List<FileInfo> files = new List<FileInfo>();
- lock (remoteFiles)
- {
- foreach (RemoteFileInfo rf in remoteFiles)
- {
- files.Add(rf.file);
- }
- }
- return files;
- }
- public int DownloadTimeoutSEC
- {
- get;
- set;
- }
- public string[] UrlRoots { get { return url_roots; } }
- public string UrlRoot { get { return url_roots[0]; } }
- public string VersionText
- {
- get { return version_text; }
- }
- public long TotalDownloadBytes
- {
- get { return total_download_bytes; }
- }
- public long CurrentDownloadBytes
- {
- get { return current_download_bytes.Value; }
- }
- public long CurrentDownloadSpeed
- {
- get { return current_download_speed_BPS; }
- }
- public long TotalUnzipBytes
- {
- get { return total_unzip_bytes; }
- }
- public long CurrentUnzipBytes
- {
- get { return current_unzip_bytes.Value; }
- }
- public long CurrentUnzipSpeed
- {
- get { return current_unzip_spped_BPS; }
- }
- public bool IsRunning
- {
- get { return is_running; }
- }
- public bool IsDisposing
- {
- get { return is_running && is_exit; }
- }
- public Status CurrentStatus { get { return status.Value; } }
- public bool IsLoadFinised { get; private set; }
- //-----------------------------------------------------------------------------------------------------------
- //-----------------------------------------------------------------------------------------------------------
- public class RemoteFileInfo
- {
- private FileInfo _file;
- public readonly string md5;
- public readonly string key;
- public readonly uint size;
- public FileInfo file
- {
- get { _file.Refresh(); return _file; }
- }
- internal RemoteFileInfo(string md5, uint size, string key, FileInfo file)
- {
- this.md5 = md5;
- this.key = key;
- this.size = size;
- this._file = file;
- }
- internal bool IsCompletion()
- {
- if (file.Exists && file.Length == size)
- {
- return true;
- }
- return false;
- }
- internal long NeedLoadSize()
- {
- if (file.Exists) return (size - file.Length);
- else return size;
- }
- }
- }
- }
|