源起

(个人理解)包管理最开始应该是从java平台下的maven开始吧,因为java的开发大多数是基于开源组件开发的,一个开源包在使用时很可能要去依赖其他的开源包,而且必须是特定的版本才可以。以往在找到一个开源包后,往往要用很多时间去把依赖的包找齐,于是maven出现了,它能自动搜索一个包的依赖项并下载到本地,免去找各种引用包的时间。

在maven出现不久后,.net也出现了自己的包管理工具,nuget,相信园子里的人都有所了解,nuget的官方源和microsoft源上集成了很多开源组件,供大家使用,而且在下载过程会进行相应解析,下载对应的依赖包。

上面是对包管理的一些介绍,理解包管理,那么很容易想到,有没有可能用包管理现成的组件来开发一个面向程序的自动更新?

主要有以下的好处:

1.更新的服务器端是现成的(nuget.server,nuget.galley)

2.发布工具是责成的(nuget command)

那么,主要就是要完成自动更新部分的检测,下载,以及解析。

先分析一下VS中包管理的方式:

1.所有包都维护在项目下的packages.config文件中;

2.在检测更新时,会连接到服务器上去进行检测(不同的包源)

3.要下载包

4.在包下载后,要将包解开,加到工程引用中;

那么,我们读源码的工作,主要如下:

1.理解怎么通过packages.config文件得到包的引用

2.得到包的引用后,如何去检测更新

3.怎么对包进行解析

下面和大家分享我的做法。

1.理解源码的第一步,需要懂得nuget.core中是怎么对这个packages.config进行解析,按照这种思路,在nuget.core中找到PackageReferenceFile这个类(直接全工程搜“package.config",最后定位于此)

namespace NuGet
{
public class PackageReferenceFile
{
public PackageReferenceFile(string path);
public PackageReferenceFile(IFileSystem fileSystem, string path); public void AddEntry(string id, SemanticVersion version);
public void AddEntry(string id, SemanticVersion version, FrameworkName targetFramework);
public bool DeleteEntry(string id, SemanticVersion version);
public bool EntryExists(string packageId, SemanticVersion version);
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "This might be expensive")]
public IEnumerable<PackageReference> GetPackageReferences();
public IEnumerable<PackageReference> GetPackageReferences(bool requireVersion);
public void MarkEntryForReinstallation(string id, SemanticVersion version, FrameworkName targetFramework, bool requireReinstallation);
}
}

注意到一个很直接的构造函数

namespace NuGet
{
public class PackageReferenceFile
{
public PackageReferenceFile(string path);
//other codes
}
}

在本地调用一下,发现成功能生成PackageReferenceFile类,同时用GetPackageReferences,能够得到一个IEnumerable<PackageReference>

继续代码,查到PackageReference定义:

  

namespace NuGet
{
public class PackageReference : IEquatable<PackageReference>
{
public PackageReference(string id, SemanticVersion version, IVersionSpec versionConstraint, FrameworkName targetFramework, bool isDevelopmentDependency, bool requireReinstallation = false); public string Id { get; }
public bool IsDevelopmentDependency { get; }
public bool RequireReinstallation { get; }
public FrameworkName TargetFramework { get; }
public SemanticVersion Version { get; }
public IVersionSpec VersionConstraint { get; set; } public override bool Equals(object obj);
public bool Equals(PackageReference other);
public override int GetHashCode();
public override string ToString();
}
}

可以看到,在这个类中包的ID,版本都有对应的属性来表达,那么这应该就是我们可以用来解析包引用的类,这样,我们第一步工作已经完成了,通过解析本地的文件得到了包的引用关系。

2.第二步,要理解怎么去检测更新。第一个直观的想法是查查有没有包函类似于update,getupdate方法的类,或者是接口,成功的找到最终的接口 IServiceBasedRepository

namespace NuGet
{
public interface IServiceBasedRepository : IPackageRepository
{
IEnumerable<IPackage> GetUpdates(IEnumerable<IPackage> packages, bool includePrerelease, bool includeAllVersions, IEnumerable<System.Runtime.Versioning.FrameworkName> targetFrameworks, IEnumerable<IVersionSpec> versionConstraints);
IQueryable<IPackage> Search(string searchTerm, IEnumerable<string> targetFrameworks, bool allowPrereleaseVersions);
}
}

再查找实现这个接口的类,OK,我幸运的找到了表示服务器资源的类DataServicePackageRepository

namespace NuGet
{
public class DataServicePackageRepository : PackageRepositoryBase, IHttpClientEvents, IProgressProvider, IServiceBasedRepository, ICloneableRepository, ICultureAwareRepository, IOperationAwareRepository, IPackageLookup, IPackageRepository, ILatestPackageLookup, IWeakEventListener
{
public DataServicePackageRepository(IHttpClient client);
public DataServicePackageRepository(Uri serviceRoot);
public DataServicePackageRepository(IHttpClient client, PackageDownloader packageDownloader); public CultureInfo Culture { get; }
public PackageDownloader PackageDownloader { get; }
public override string Source { get; }
public override bool SupportsPrereleasePackages { get; } public event EventHandler<ProgressEventArgs> ProgressAvailable;
public event EventHandler<WebRequestEventArgs> SendingRequest; public IPackageRepository Clone();
public bool Exists(string packageId, SemanticVersion version);
public IPackage FindPackage(string packageId, SemanticVersion version);
public IEnumerable<IPackage> FindPackagesById(string packageId);
public override IQueryable<IPackage> GetPackages();
public IEnumerable<IPackage> GetUpdates(IEnumerable<IPackage> packages, bool includePrerelease, bool includeAllVersions, IEnumerable<System.Runtime.Versioning.FrameworkName> targetFrameworks, IEnumerable<IVersionSpec> versionConstraints);
public bool ReceiveWeakEvent(Type managerType, object sender, EventArgs e);
public IQueryable<IPackage> Search(string searchTerm, IEnumerable<string> targetFrameworks, bool allowPrereleaseVersions);
public IDisposable StartOperation(string operation, string mainPackageId, string mainPackageVersion);
public bool TryFindLatestPackageById(string id, out SemanticVersion latestVersion);
public bool TryFindLatestPackageById(string id, bool includePrerelease, out IPackage package);
}
}

这里面有两个方法一眼可以得知,一个是GetUpdates方法,显而易见,是查到有更新的包

另一个是构造函数 DataServicePackageRepository(Uri serviceRoot),即以nuget的源地址初始化,但是有一个问题,我们目前得到的是PackageReference,而函数里要调用的是IPackage,它的定义如下:

namespace NuGet
{
public interface IPackage : IPackageMetadata, IServerPackageMetadata
{
IEnumerable<IPackageAssemblyReference> AssemblyReferences { get; }
bool IsAbsoluteLatestVersion { get; }
bool IsLatestVersion { get; }
bool Listed { get; }
DateTimeOffset? Published { get; } [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "This might be expensive")]
IEnumerable<IPackageFile> GetFiles();
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "This might be expensive")]
Stream GetStream();
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "This might be expensive")]
IEnumerable<System.Runtime.Versioning.FrameworkName> GetSupportedFrameworks();
}
}

同时,我又看了IPackage的每一个类(在源码中),没有一个可以从PackageReference直接进行构造,而其他的逻辑又太复杂(我比较懒哈),怎么办?看了一下GetUpdates的源码,发现在检测更新时只用到了Package类里的两个字段, 即id和version,OK,那么这样就好办了,我们自己定义一个IPackge的实现,只要实现id和version就可以:

    class TempPackage :NuGet.IPackage
{
public string Id
{
get;
internal set;
} public NuGet.SemanticVersion Version
{
get;
internal set;
}
//other codes that not Implemented }

OK,那么得,定义了这个,我们在检测更新前进行一下转换即可:

   var localFiles = File.GetPackageReferences();//File is  NuGet.PackageReferenceFile
foreach (var i in localFiles)
{
localPacks.Add(new TempPackage() { Id = i.Id, Version = i.Version });
} var updatepacks = Source.GetUpdates(localPacks, false, false, null, null);//Source is DataServicePackageRepository

哈哈,至此,我们已经得到要更新的包。。 那么进入第三步,包的解析。

3.第三步,解析包,从自己定义TempPackage时,我们得到了IPackage的定义,发现有一个方法,MS不错,他是:

public interface IPackage : IPackageMetadata, IServerPackageMetadata
{
//other codes
[SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "This might be expensive")]
IEnumerable<IPackageFile> GetFiles();
}

什么意思,可以得到包中包含的文件么?IPackageFile又是什么??

namespace NuGet
{
public interface IPackageFile : IFrameworkTargetable
{
string EffectivePath { get; }
string Path { get; }
FrameworkName TargetFramework { get; } [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "This might be expensive")]
Stream GetStream();
}
}

OK,在没有读源码的情况下,抱着试一试的感觉,我写了如下的代码:

  var installFiles = localfile.GetFiles();
foreach (var savefile in installFiles)
{
byte[] data = new byte[savefile.GetStream().Length];
savefile.GetStream().Read(data, , data.Length); var fileinfo = new System.IO.FileInfo(startDir + "\\" + savefile.EffectivePath);
if (System.IO.Directory.Exists(fileinfo.Directory.FullName) == false)
System.IO.Directory.CreateDirectory(fileinfo.Directory.FullName);
using (var filewrite = System.IO.File.Create(fileinfo.FullName))
{
filewrite.Write(data, , data.Length);
} }

哈哈,运行以后,我发现本地已经成功的解析出一个包里的相应文件 !!

到此,对源码的研究已经结束,下面就是按这个思路进行写软件了,至此,我们要解决的问题都已经全部完成,我又看了一下其他的部分,还有更优化的方案,即,可以用一个临时目录初始化一个LocalPackageRepository类,用服务器源和本地LocalPackageRepository的可以直接初始化PackageManager,在检测更新以后,直接用InstallPackage即可将包下载到本地。

4.更新文件的替换,因为程序在启动后,会自动加载相关的dll,那么怎么对更新文件进替换?其实很简单,用dynamic直接动态加载主窗体即可。

5.一些其他的技巧

看了一些人关于建立本地源以后自动化打包的方案,感觉都很麻烦,在一段时间摸索以后,发现要在post-build命令行中加如下命令,即可完成在编译后自动上传:

nuget pack "$(ProjectPath)" -o “本地临时目录”
nuget push 本地临时目录$(TargetName).*.nupkg apiKey -S 本地服务器包源
move 本地临时目录*.nupkg 本地包源

当然,一编译就上传这个事儿有点过份哈,不过如果版本号不改话,客户端是检测不到更新的,所以,在测试没问题时,可以将版本号进行更新,这样客户端就能检测到相应的更新了。

本人写东西的能力一般,这些源码里穿插着如此多的废话主要是想和大家分享自己去目的性研究一些代码的方法和思路,如果说的不对或是不当,还请拍砖。

附: nuget官网 http://www.nuget.org/

建立self-host包源 http://docs.nuget.org/docs/creating-packages/hosting-your-own-nuget-feeds

分析nuget源码,用nuget + nuget.server实现winform程序的自动更新的更多相关文章

  1. 一 分析easyswoole源码(启动服务)

    分析easyswoole源码 1以启动为例 //检查是否已经安装 installCheck();//检查锁文件是否存在,不存在结束 //启动服务 serverStart showLogo();//显示 ...

  2. [源码分析] 从源码入手看 Flink Watermark 之传播过程

    [源码分析] 从源码入手看 Flink Watermark 之传播过程 0x00 摘要 本文将通过源码分析,带领大家熟悉Flink Watermark 之传播过程,顺便也可以对Flink整体逻辑有一个 ...

  3. 如何分析SpringBoot源码模块及结构?--SpringBoot源码(二)

    注:该源码分析对应SpringBoot版本为2.1.0.RELEASE 1 前言 本篇接 如何搭建自己的SpringBoot源码调试环境?--SpringBoot源码(一). 前面搭建好了自己本地的S ...

  4. k8s client-go源码分析 informer源码分析(3)-Reflector源码分析

    k8s client-go源码分析 informer源码分析(3)-Reflector源码分析 1.Reflector概述 Reflector从kube-apiserver中list&watc ...

  5. Android源码分析--CircleImageView 源码详解

    源码地址为 https://github.com/hdodenhof/CircleImageView 实际上就是一个圆形的imageview 的自定义控件.代码写的很优雅,实现效果也很好, 特此分析. ...

  6. 分析jQuery源码时记录的一点感悟

    分析jQuery源码时记录的一点感悟      1.  链式写法      这是jQuery语法上的最大特色,也许该改改POJO里的set方法,和其他的非get方法什么的,可以把多行代码合并,减去每次 ...

  7. Linux内核(2) - 分析内核源码如何入手(上)

    透过现象看本质,兽兽们无非就是一些人体艺术展示.同样往本质里看过去,学习内核,就是学习内核的源代码,任何内核有关的书籍都是基于内核,而又不高于内核的. 既然要学习内核源码,就要经常对内核代码进行分析, ...

  8. STM32F103 ucLinux开发之一(BOOT分析及源码)

    STM32F103 ucLinux开发BOOT STM3210E-EVAL官方开发板主芯片STM32F103ZET6: 片内512K Flash,地址0x0800 0000 ~ 0x0807 FFFF ...

  9. Activiti架构分析及源码详解

    目录 Activiti架构分析及源码详解 引言 一.Activiti设计解析-架构&领域模型 1.1 架构 1.2 领域模型 二.Activiti设计解析-PVM执行树 2.1 核心理念 2. ...

随机推荐

  1. Kafka vs RocketMQ—— Topic数量对单机性能的影响-转自阿里中间件

    引言 上一期我们对比了三类消息产品(Kafka.RabbitMQ.RocketMQ)单纯发送小消息的性能,受到了程序猿们的广泛关注,其中大家对这种单纯的发送场景感到并不过瘾,因为没有任何一个网站的业务 ...

  2. Linux学习笔记(7)-进程

    明天开始学习进程,在以前的单片机开发中,都没有进程这个概念,但从网上了解到,这个东西在操作系统中似乎具有很重要的地位,一定好好学习! --------------------------------- ...

  3. AugularJS特性

    AugularJS特性 AngularJS是一个新出现的强大客户端技术,提供给大家的一种开发强大应用的方式.这种方式利用并且扩展HTML,CSS和javascript,并且弥补了它们的一些非常明显的不 ...

  4. mybatis自增长插入id

    第一种: <insert id="insertUser" parameterClass="ibatis.User"> <selectKey r ...

  5. Java jvisualvm简要说明

    jvisualvm能干什么:监控内存泄露,跟踪垃圾回收,执行时内存.cpu分析,线程分析... jvisualvm已经被集成在jdk1.6以上的版本中(不是jre).自身运行需要最低jdk1.6版本, ...

  6. SICAU教务系统登录密码加密算法的VB方式实现

    关于一个算法.这个算法是SICAU教务系统在账号登录时采取的一个加密算法.算法的实现并不复杂. 具体如下: Function Form1pwdvalue(ByVal pwdvalue As Strin ...

  7. 【Beta】Daily Scrum Meeting第三次

    1.任务进度 学号 已完成 接下去要做 502 将login改为面向对象,添加php测试:网络请求使用新线程及回调 将ConstantTools改成HashMap:重构相关代码 509 返回教师多行表 ...

  8. 汉字正则表达式[\u4E00-\u9FFF]原因

    转载易天:正则表达式的汉字匹配 这里是几个主要非英文语系字符范围 2E80-33FFh:中日韩符号区.收容康熙字典部首.中日韩辅助部首.注音符号.日本假名.韩文音符,中日韩的符号.标点.带圈或带括符文 ...

  9. linux下安装启动rpc服务

    1.上传包 rocky:~ # ls Desktop dts.xml jdk1..0_41 oswbb rpc.rstatd- rpc.rstatd-.tar.gz rocky:~ # cd rpc. ...

  10. Spring MVC 框架的架包分析,功能作用,优点

    由于刚搭建完一个MVC框架,决定分享一下我搭建过程中学习到的一些东西.我觉得不管你是个初级程序员还是高级程序员抑或是软件架构师,在学习和了解一个框架的时候,首先都应该知道的是这个框架的原理和与其有关j ...