스마트 업데이트 라이브러리 만들기 (완료) - slog

다음의 요구사항을 충족하는 스마트 업데이트 라이브러리 구현

  • [x] .NET Framework 4.6, WinForms 사용
  • [x] 작은 용량
  • [x] 스플래시 스크린 제공
  • [x] 별도 프로세스로 임시 디렉토리에서 실행
  • [x] 관리자 권한으로 실행
  • [x] 최신 버젼의 쉬운 배포 (복사 및 붙여넣기)
  • [x] SHA1 해시 사용
  • [x] gzip 압축 배포
  • [x] 기본 업데이트 진행 창 제공
    • [x] 현재 버젼, 최신 버젼 비교 창
    • [x] 업데이트 창
  • [ ] 커스텀 업데이트 구현 가능 (callback 기반)
  • [ ] API 제공(버젼 비교, 업데이트 관련, 재시작, 기존 버젼으로 복구)

결과물

  • [ ] 라이브러리 nuget 배포
  • [x] github 소스코드
좋아요 7

동작 관련

  • lastupdate.yml 파일 생성
    최신 버젼을 생성하면 간단한 유틸리티를 통해 버젼 정보와 파일 해시코드를 lastupdate.yml 파일로 생성한다. 만약 기존 버젼에서 유지해야 파일(예를 드어 설정파일)이 있다면 lastupdate.yml을 프로젝트에 추가해서 그 정보를 기록할 수 있도록 한다.

  • 온라인의 특정 경로로 복사

  • 업데이트 프로그램 실행

    • 프로그램은 시작 시 라이브러리의 Splash Screen을 띄우면서 최신 버젼이 있는지를 확인
    • 최신 버젼의 확인은 lastupdate.yml의 버젼을 통해 확인
    • 최신 버젼이 있을 경우 라이브러리는 업데이트를 진행
  • 업데이트

    • 라이브러리를 임시 파일로 복사 후 재실행 이때 관리자 권한이 필요할 경우 관리자 권한으로 상승해서 실행
    • 기존 경로의 파일을 복구를 위해 백업함
    • lastupdate.yml을 이용해서 다운로드 할 파일과 유지해야 할 파일, 삭제해야 할 파일을 구분해서 새로운 버젼을 구축함
    • 준비가 완료되면 새로운 프로그램으로 재시작 함
좋아요 3

프로젝트명을 무엇을로 지을까요? 특별히 아이디어가 안떠올라 SplashScreenAndSmartUpdate, SSASU라고… 쿨럭 이쁜 이름 추천 받아요~

좋아요 2

동작은 총 5가지로 정의 합니다.

enum ActionMode
{
    CheckUpdate,
    SplashScreen,
    GenerateHash,
    Update,
    Restore,
}

옵션은 두가지로 정의합니다.

enum ActionOptions
{
    Screen,
    Callback,
}
좋아요 2

프로젝트는 .NET Framework을 사용하지만 최신 프로젝트 설정을 사용 합니다.

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>WinExe</OutputType>
    <TargetFramework>net46-windows</TargetFramework>
	<UseWindowsForms>true</UseWindowsForms>
	<Nullable>enable</Nullable>
	<LangVersion>preview</LangVersion>
  </PropertyGroup>

</Project>
좋아요 2

명령 인자를 처리하는 패키지를 이용하면 좋겠지만 종속성 없는 실행 형태여야 하므로 명령 인자를 다음 처럼 처리했습니다.

    [STAThread]
    static void Main(string[] args)
    {
        var mode = ActionMode.CheckUpdate;
        if (args.Length > 0)
            Enum.TryParse<ActionMode>(args[0], out mode);

        var action = new Dictionary<ActionMode, Action>
        {
            [ActionMode.CheckUpdate] = CheckUpdate,
            [ActionMode.SplashScreen] = RunSplashScreen,
            [ActionMode.GenerateHash] = GenerateHash,
            [ActionMode.Update] = Update,
            [ActionMode.Restore] = Restore,
        };

        action[mode]();
    }

    private static void CheckUpdate()
    {
    }

    private static void RunSplashScreen()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new SplashScreenForm());
    }

    private static void GenerateHash()
    {
    }

    private static void Update()
    {
    }

    private static void Restore()
    {
    }
좋아요 2

이름제안

FormsSmartUpdate
FormsSmartUpdate.Splash

FormsLiveUpdate
FormsLiveUpdate.Splash

Dimohy.Forms.SmartUpdate
Dimohy.Forms.SmartUpdate.Splash

WinFormsSmartUpdate
WinFormsSmartUpdate.Splash

FormsUpdateKit
FormsUpdateKit.Splash

FormsVisualUpdate
FormsVisualUpdate.Splash

좋아요 2

AutoLibUpdator == ALU는 어떠신가용

좋아요 4

알러뷰인 것이지요? ^____^

좋아요 6

추가 패키지를 사용하지 않을 것이므로 yml 대신 xml을 사용하는 것으로 변경

좋아요 2

업데이트 정보 파일구조를 다음처럼 만들었습니다.

<?xml version="1.0" encoding="utf-8"?>
<Update xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" Version="1.0.0">
  <KeepFiles />
  <Files>
    <File Name="test.txt" Hash="1213131321352512341234555" />
  </Files>
</Update>

유지해야 할 파일을 KeepFiles 목록에 두고 배포 파일 목록을 Files에 둡니다.

  1. 현 버젼이 원격 버젼보다 낮을 경우 업데이트 진행
  2. KeepFiles 목록을 그대로 새 버젼 디렉토리에 복사
  3. Files 목록을 순회하면서 해시값을 비교해 다를 경우 원격에서 복사. 같을 경우 로컬에서 복사

다음은 업데이트 정보 클래스 입니다.

[XmlRoot("Update")]
public class UpdateInfo
{

    [XmlIgnore]
    public Version Version { get; set; } = default!;

    [XmlAttribute(nameof(Version))]
    public string VersionText
    {
        get => Version.ToString();
        set
        {
            Version = Version.Parse(value);
        }
    }

    public List<File> KeepFiles { get; set; } = new List<File>();
    public List<File> Files { get; set; } = new List<File>();

    public bool IsLessVersionThan(UpdateInfo remote)
    {
        if (Version < remote.Version)
            return true;

        return false;
    }

    public IEnumerable<File> GetFiles(UpdateInfo remote)
    {
        foreach (var file in remote.KeepFiles)
        {
            file.Action = ActionKind.Keep;
            yield return file;
        }

        foreach (var file in remote.Files)
        {
            var localFile = Files.FirstOrDefault(x => x.Name == file.Name);
            if (localFile is null || localFile.Hash != file.Hash)
            {
                file.Action = ActionKind.Update;
                yield return file;
            }
        }

        foreach (var file in Files)
        {
            var remoteFile = remote.Files.FirstOrDefault(x => x.Name == file.Name);
            if (remoteFile is null)
            {
                file.Action = ActionKind.Remove;
                yield return file;
            }
        }
    }

    public void ToFile(string filename)
    {
        var serializer = new XmlSerializer(typeof(UpdateInfo));
        using var xw = XmlWriter.Create(filename, new() { Indent = true });
        serializer.Serialize(xw, this);
    }

    public static UpdateInfo FromFile(string filename)
    {
        var serializer = new XmlSerializer(typeof(UpdateInfo));
        using var xr = XmlReader.Create(filename);
        return (UpdateInfo)serializer.Deserialize(xr);
    }

    public static UpdateInfo FromUri(string uri)
    {
        var serializer = new XmlSerializer(typeof(UpdateInfo));
        using var webClient = new WebClient();
        var data = webClient.DownloadData(uri);
        using var s = new MemoryStream(data);
        return (UpdateInfo)serializer.Deserialize(s);
    }


    public class File
    {
        [XmlAttribute]
        public string Name { get; set; } = default!;

        [XmlAttribute]
        public string Hash { get; set; } = default!;

        [XmlIgnore]
        public ActionKind Action { get; set; }
    }

    public enum ActionKind
    {
        Keep,
        Remove,
        Update
    }
}
좋아요 2

업데이트 확인 창

image

image

  • 버젼이 같을 경우
    image
좋아요 2

오 이거 좋군요.~! 클릭원스에 의지하던 터라…

좋아요 2

게시본 만들기

updater Generate

게시할 파일의 해시 코드를 생성하여 lastupdate.xml을 업데이트 하며 publish 디렉토리에 게시할 파일을 gzip으로 압축해서 filename.gz으로 저장

| lastupdate.xml

<?xml version="1.0" encoding="utf-8"?>
<Update xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" Name="Test Program" Version="0.2" Uri="https://maum.in/update/test">
  <KeepFiles />
  <Files>
    <File Name="DMUpdater.exe" Hash="8F0CEC48CE560F290C759655ABD249FBBA1D3A04" />
    <File Name="DMUpdater.exe.config" Hash="68F6F077B1A4E939D0329FAAB51446FDA30CD503" />
    <File Name="DMUpdater.pdb" Hash="5C8218F5662ABA64C1AB293413B335F09509D7EC" />
    <File Name="DMUpdater.Test.deps.json" Hash="C2267C1B7DF394130352BE10B95DB9331802FAD8" />
    <File Name="DMUpdater.Test.dll" Hash="752B4670F9507F789F1DBB4B10D5144F89784008" />
    <File Name="DMUpdater.Test.exe" Hash="43C7C27EC9BB459AE21880C8A9E2FB28D3293851" />
    <File Name="DMUpdater.Test.pdb" Hash="91A94AA5D5146247615C1CB47DAF29DAC047562C" />
    <File Name="DMUpdater.Test.runtimeconfig.json" Hash="5028BA7F3503486654CACAD0D327E9C18FDE0DE3" />
  </Files>
  <IgnoreFiles>
    <File Name="backup" />
    <File Name="publish" />
    <File Name="lastupdate.xml" />
  </IgnoreFiles>
</Update>

| publish 디렉토리
image

publish내용을 서버로 복사

업데이트

updater Update

image

좋아요 2

남은 일감

  • API 제공
    • 어플리케이션이 준비 완료되면 스플래시 화면 닫음
    • 업데이트 완료 후 재시작
    • 기존 버젼으로 복구 (다음 배포본에 구현)
  • 커스텀 업데이트 구현 기능 (다음 배포본에 구현)
  • NuGet 배포
좋아요 2

소스코드는
https://github.com/dimohy/DMUpdater

이곳에 있습니다. 아직 NuGet에 패키징을 하지는 못했지만 슬로그는 완료하도록 하겠습니다. 관련해서 패키징 한 후 차 후 내가 만들었어요!에 관련 내용 게시하도록 하겠습니다.

좋아요 3