1+ using System ;
2+ using System . Net ;
3+ using System . Threading . Tasks ;
4+
5+ using System . Text . RegularExpressions ;
6+
7+ namespace Mayerch1 . GithubUpdateCheck
8+ {
9+ /// <summary>
10+ /// Enumeration of version steps (Major, Minor,...)
11+ /// </summary>
12+ public enum VersionChange
13+ {
14+ /// <summary>
15+ /// Major software update (X.0.0.0)
16+ /// </summary>
17+ Major = 1 ,
18+
19+ /// <summary>
20+ /// Minor software update (0.X.0.0)
21+ /// </summary>
22+ Minor = 2 ,
23+
24+ /// <summary>
25+ /// Build change (0.0.X.0)
26+ /// </summary>
27+ Build = 3 ,
28+
29+ /// <summary>
30+ /// Revision changed (0.0.0.X)
31+ /// </summary>
32+ Revision = 4
33+ }
34+
35+ /// <summary>
36+ /// The exception that is thrown if the version argument does not match the required pattern
37+ /// </summary>
38+ public class InvalidVersionException : ArgumentException {
39+ /// <summary>
40+ /// Initializes a new instance of the <see cref="InvalidVersionException"/> class with a specified error message
41+ /// </summary>
42+ public InvalidVersionException ( String message ) : base ( message )
43+ {
44+ }
45+ } ;
46+
47+
48+ /// <summary>
49+ /// Checks if your github repo is on a newer version or not
50+ /// </summary>
51+ public class GithubUpdateCheck
52+ {
53+ private const string githubUrl = "https://github.com/" ;
54+ private const string latestVersionString = "/releases/latest" ;
55+
56+ private string Username ;
57+ private string Repository ;
58+
59+ /// <summary>
60+ /// Assumes version numbering with the pattern 1.2.3.4
61+ /// </summary>
62+ /// <param name="Username">Username of Repository owner</param>
63+ /// <param name="Repository">Name of Github Repository</param>
64+ public GithubUpdateCheck ( string Username , string Repository )
65+ {
66+ this . Username = Username ;
67+ this . Repository = Repository ;
68+ }
69+
70+
71+ /// <summary>
72+ /// Tests if inputted version is valid based on the allowed/specified patterns
73+ /// </summary>
74+ /// <param name="version">Current software version, is compared against the github version</param>
75+ /// <returns></returns>
76+ private bool isValidInput ( string version )
77+ {
78+ // currently accepeted pattern:
79+ // 1.0.0.0
80+ // 1.0.0
81+ // v.1.0.0
82+ // v1.0.0
83+ // and any combination of those
84+ string pattern = @"^([^0-9]\.{0,1}){0,1}\d+\.\d+\.\d+(\.\d+){0,1}$" ;
85+ Match match = Regex . Match ( version , pattern ) ;
86+
87+ return match . Success ;
88+ }
89+
90+
91+ /// <summary>
92+ /// Removes leading v. and v from the string
93+ /// </summary>
94+ /// <param name="version">string which is compliant to the allowed pattern(s)</param>
95+ /// <returns>normalized string</returns>
96+ private string normalizeVersionString ( string version )
97+ {
98+ // leading (v. or v ) are allowed
99+ // therefore remove those from the version number
100+ string pattern = @"\d+\.\d+\.\d+(\.\d+){0,1}$" ;
101+ Match match = Regex . Match ( version , pattern ) ;
102+
103+ return match . Value ;
104+ }
105+
106+
107+
108+ /// <summary>
109+ /// <para>Compares the current software version to the latest release on github. Asynchronous web request</para>
110+ /// If the webservice is not available this function will assume no updates available
111+ /// </summary>
112+ /// <param name="CurrentVersion">The version of the software wich is compared to the github version</param>
113+ /// <param name="VersionChange">The granularity of the comparison. Any version change smaller than this will be ignored. (e.g. Minor will check for a change in the first 2 digits groups)</param>
114+ /// <exception cref="InvalidVersionException">Is thrown if the supplied version does not match the allowed version pattern</exception>
115+ /// <returns>bool - true if a newer version is available, false - if no newer version is available or if no connection to github is available</returns>
116+ public async Task < bool > IsUpdateAvailableAsync ( string CurrentVersion , VersionChange VersionChange = VersionChange . Minor )
117+ {
118+ if ( ! isValidInput ( CurrentVersion ) )
119+ {
120+ throw new InvalidVersionException ( CurrentVersion + " does not follow the specified version pattern [CurrentVersion]" ) ;
121+ }
122+ string resolved = await getResponseUrlAsync ( githubUrl + Username + "/" + Repository + latestVersionString ) ;
123+
124+ if ( resolved != null )
125+ return compareVersions ( normalizeVersionString ( CurrentVersion ) , resolved , VersionChange ) ;
126+ else
127+ return false ;
128+
129+
130+ }
131+
132+ /// <summary>
133+ /// <para>Compares the current software version to the latest release on github. Synchronous (blocking) web request</para>
134+ /// If the webservice is not available this function will assume no updates available
135+ /// </summary>
136+ /// <param name="CurrentVersion">The version of the software wich is compared to the github version</param>
137+ /// <param name="VersionChange">The granularity of the comparison. Any version change smaller than this will be ignored. (e.g. Minor will check for a change in the first 2 digits groups)</param>
138+ /// <exception cref="InvalidVersionException">Is thrown if the supplied version does not match the allowed version pattern</exception>
139+ /// <returns>bool - true if a newer version is available, false - if no newer version is available or if no connection to github is available</returns>
140+ public bool IsUpdateAvailable ( string CurrentVersion , VersionChange VersionChange = VersionChange . Minor )
141+ {
142+ if ( ! isValidInput ( CurrentVersion ) )
143+ {
144+ throw new InvalidVersionException ( CurrentVersion + " does not follow the specified version pattern [CurrentVersion]" ) ;
145+ }
146+
147+
148+ string resolved = getResponseUrl ( githubUrl + Username + "/" + Repository + latestVersionString ) ;
149+
150+ if ( resolved != null )
151+ return compareVersions ( normalizeVersionString ( CurrentVersion ) , resolved , VersionChange ) ;
152+ else
153+ return false ;
154+ }
155+
156+
157+
158+ /// <summary>
159+ /// Compares two version numbers. Extract version number of github release url
160+ /// "Current" must comply with pattern, aswell as github version
161+ /// </summary>
162+ /// <param name="current">Local software version, must be checket for complinance with the allowed pattern(s)</param>
163+ /// <param name="github">Url of the latest github release</param>
164+ /// <param name="changeLevel">The level for comparison</param>
165+ /// /// <exception cref="InvalidVersionException">Is thrown if the supplied version does not match the allowed version pattern</exception>
166+ /// <returns></returns>
167+ private bool compareVersions ( string current , string github , VersionChange changeLevel )
168+ {
169+ //no releases yet
170+ if ( ! github . Contains ( "/tag/" ) )
171+ return false ;
172+
173+ //get everything after last /tag/
174+ github = github . Substring ( github . LastIndexOf ( "/tag/" ) + "/tag/" . Length ) ;
175+
176+
177+ if ( ! isValidInput ( github ) )
178+ {
179+ throw new InvalidVersionException ( github + " the github version number does not follow the specified version pattern [Remote error]" ) ;
180+ }
181+
182+ github = normalizeVersionString ( github ) ;
183+
184+ // separate version into VersionChange
185+ // input is tested for numbers only between the seperators ('.')
186+ Int64 [ ] currentArr = Array . ConvertAll ( current . Split ( '.' ) , s => Int64 . Parse ( s ) ) ;
187+ Int64 [ ] gitArr = Array . ConvertAll ( github . Split ( '.' ) , s => Int64 . Parse ( s ) ) ;
188+
189+
190+ // set the comparison depth
191+ // take the minimum depth, in case one specified number is smaller than another one
192+ int cmpDepth = ( int ) changeLevel ;
193+ cmpDepth = Math . Min ( currentArr . Length , cmpDepth ) ;
194+ cmpDepth = Math . Min ( gitArr . Length , cmpDepth ) ;
195+
196+
197+ /* comparison of version numbers as follows
198+
199+ localMajor <> gitMajor
200+ [smalle] [equals] [greater]
201+ true | false
202+ V
203+ localMinor <> gitMinor
204+ [smalle] [equals] [greater]
205+ true | false
206+ V
207+ localBuild <> gitBuild
208+ [smalle] [equals] [greater]
209+ true | false
210+ V
211+ localRev. <> gitRev.
212+ [smalle] [equals] [greater]
213+ true false false
214+
215+ // if cmpDepth is reached at any time
216+ // the return is false aswell
217+
218+
219+ */
220+
221+ // implicitie cmpDepth == 1
222+ // 1. major is checked on every version
223+ // 2. input is guaranteed to have cmpDepth >= 1
224+
225+ //===============MAJOR==================
226+ if ( currentArr [ 0 ] < gitArr [ 0 ] )
227+ {
228+ return true ;
229+ }
230+ else if ( currentArr [ 0 ] > gitArr [ 0 ] )
231+ {
232+ return false ;
233+ }
234+ //===============MAJOR==================
235+ else
236+ {
237+ // first check if Depth was reached
238+ if ( cmpDepth <= 1 )
239+ {
240+ return false ;
241+ }
242+ //===============MINOR==================
243+ // repeat same procedure for minor
244+ if ( currentArr [ 1 ] < gitArr [ 1 ] )
245+ {
246+ return true ;
247+ }
248+ else if ( currentArr [ 1 ] > gitArr [ 1 ] )
249+ {
250+ return false ;
251+ }
252+ //===============MINOR==================
253+ else
254+ {
255+ // check if Depth was reached
256+ if ( cmpDepth <= 2 )
257+ {
258+ return false ;
259+ }
260+ //===============BUILD==================
261+ // repeat same procedure for build
262+ if ( currentArr [ 2 ] < gitArr [ 2 ] )
263+ {
264+ return true ;
265+ }
266+ else if ( currentArr [ 2 ] > gitArr [ 2 ] )
267+ {
268+ return false ;
269+ }
270+ //===============BUILD==================
271+ else
272+ {
273+ // check if Depth was reached
274+ if ( cmpDepth <= 3 )
275+ {
276+ return false ;
277+ }
278+
279+ //===============REVSIION==================
280+ // repeat same procedure for Revision
281+ if ( currentArr [ 3 ] < gitArr [ 3 ] )
282+ {
283+ return true ;
284+ }
285+ // no smaller compare possible
286+ else
287+ {
288+ return false ;
289+ }
290+ //===============REVISION==================
291+ }
292+ }
293+
294+
295+ }
296+ }
297+
298+ private string getResponseUrl ( string request )
299+ {
300+ HttpWebRequest req = ( HttpWebRequest ) WebRequest . Create ( request ) ;
301+ WebResponse wResp ;
302+ try
303+ {
304+ wResp = req . GetResponse ( ) ;
305+ }
306+ catch
307+ {
308+ //if not available
309+ return null ;
310+ }
311+
312+ return wResp . ResponseUri . ToString ( ) ;
313+ }
314+
315+ private async Task < string > getResponseUrlAsync ( string request )
316+ {
317+ HttpWebRequest req = ( HttpWebRequest ) WebRequest . Create ( request ) ;
318+ WebResponse wResp ;
319+ try
320+ {
321+ wResp = await req . GetResponseAsync ( ) ;
322+ }
323+ catch
324+ {
325+ return null ;
326+ //if not available
327+ }
328+
329+ return wResp . ResponseUri . ToString ( ) ;
330+ }
331+ }
332+ }
0 commit comments