diff --git a/Deploy/NAppUpdate/FeedBuilder.config b/Deploy/NAppUpdate/FeedBuilder.config
deleted file mode 100644
index 4e99a397fd..0000000000
--- a/Deploy/NAppUpdate/FeedBuilder.config
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
- http://127.0.0.1:8888
- True
- False
- True
- False
- <?xml version="1.0" encoding="utf-16"?>
- <ArrayOfString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" />
- True
- False
- ..\..\Output\Release
- True
- False
- ..\..\Output\Update\Update.xml
-
diff --git a/Deploy/NAppUpdate/FeedBuilder.exe b/Deploy/NAppUpdate/FeedBuilder.exe
deleted file mode 100644
index 278eea2032..0000000000
Binary files a/Deploy/NAppUpdate/FeedBuilder.exe and /dev/null differ
diff --git a/Deploy/NAppUpdate/FeedBuilder.exe.config b/Deploy/NAppUpdate/FeedBuilder.exe.config
deleted file mode 100644
index ae58790841..0000000000
--- a/Deploy/NAppUpdate/FeedBuilder.exe.config
+++ /dev/null
@@ -1,54 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
- True
-
-
- False
-
-
- True
-
-
- False
-
-
- True
-
-
- True
-
-
-
-
-
- True
-
-
-
-
- *.pdb
- *.config
-
-
-
-
- True
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/Deploy/NAppUpdate/build.bat b/Deploy/NAppUpdate/build.bat
deleted file mode 100644
index a9b07cb047..0000000000
--- a/Deploy/NAppUpdate/build.bat
+++ /dev/null
@@ -1,2 +0,0 @@
-cd /d %~dp0
-%~dp0FeedBuilder.exe "%~dp0FeedBuilder.config" -Build
diff --git a/Deploy/NAppUpdate/gui.bat b/Deploy/NAppUpdate/gui.bat
deleted file mode 100644
index 6c8eaf13e7..0000000000
--- a/Deploy/NAppUpdate/gui.bat
+++ /dev/null
@@ -1 +0,0 @@
-FeedBuilder.exe "FeedBuilder.config" -ShowGUI
diff --git a/Deploy/NAppUpdate/NAppUpdate.Framework.dll b/Deploy/UpdateGenerator/NAppUpdate.Framework.dll
similarity index 50%
rename from Deploy/NAppUpdate/NAppUpdate.Framework.dll
rename to Deploy/UpdateGenerator/NAppUpdate.Framework.dll
index 60fac8ffc9..398d46b810 100644
Binary files a/Deploy/NAppUpdate/NAppUpdate.Framework.dll and b/Deploy/UpdateGenerator/NAppUpdate.Framework.dll differ
diff --git a/Deploy/UpdateGenerator/Newtonsoft.Json.dll b/Deploy/UpdateGenerator/Newtonsoft.Json.dll
new file mode 100644
index 0000000000..0a61735d26
Binary files /dev/null and b/Deploy/UpdateGenerator/Newtonsoft.Json.dll differ
diff --git a/Deploy/UpdateGenerator/Wox.Infrastructure.dll b/Deploy/UpdateGenerator/Wox.Infrastructure.dll
new file mode 100644
index 0000000000..ba780a7d22
Binary files /dev/null and b/Deploy/UpdateGenerator/Wox.Infrastructure.dll differ
diff --git a/Deploy/UpdateGenerator/Wox.Plugin.dll b/Deploy/UpdateGenerator/Wox.Plugin.dll
new file mode 100644
index 0000000000..92111aea88
Binary files /dev/null and b/Deploy/UpdateGenerator/Wox.Plugin.dll differ
diff --git a/Deploy/UpdateGenerator/Wox.UpdateFeedGenerator.exe b/Deploy/UpdateGenerator/Wox.UpdateFeedGenerator.exe
new file mode 100644
index 0000000000..88284908a3
Binary files /dev/null and b/Deploy/UpdateGenerator/Wox.UpdateFeedGenerator.exe differ
diff --git a/Deploy/UpdateGenerator/build.bat b/Deploy/UpdateGenerator/build.bat
new file mode 100644
index 0000000000..f51873e12d
--- /dev/null
+++ b/Deploy/UpdateGenerator/build.bat
@@ -0,0 +1,2 @@
+cd /d %~dp0
+%~dp0Wox.UpdateFeedGenerator.exe
diff --git a/Deploy/UpdateGenerator/config.json b/Deploy/UpdateGenerator/config.json
new file mode 100644
index 0000000000..0e0afce156
--- /dev/null
+++ b/Deploy/UpdateGenerator/config.json
@@ -0,0 +1,10 @@
+{
+ "OutputDirectory": "..\\..\\Output\\Update",
+ "SourceDirectory": "..\\..\\Output\\Release",
+ "BaseURL": "http://127.0.0.1:8888",
+ "FeedXMLName": "update.xml",
+ "CheckVersion": false,
+ "CheckSize": false,
+ "CheckDate": false,
+ "CheckHash": true
+}
diff --git a/Wox.UpdateFeedGenerator/ConfigStorage.cs b/Wox.UpdateFeedGenerator/ConfigStorage.cs
new file mode 100644
index 0000000000..bdc6a30f3a
--- /dev/null
+++ b/Wox.UpdateFeedGenerator/ConfigStorage.cs
@@ -0,0 +1,57 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using Newtonsoft.Json;
+using Wox.Infrastructure.Storage;
+
+namespace Wox.UpdateFeedGenerator
+{
+ public class ConfigStorage : JsonStrorage
+ {
+ [JsonProperty]
+ public string OutputDirectory { get; set; }
+
+ [JsonProperty]
+ public string SourceDirectory { get; set; }
+
+ [JsonProperty]
+ public string BaseURL { get; set; }
+
+ [JsonProperty]
+ public string FeedXMLName { get; set; }
+
+ [JsonProperty]
+ public bool CheckVersion { get; set; }
+
+ [JsonProperty]
+ public bool CheckSize { get; set; }
+
+ [JsonProperty]
+ public bool CheckDate { get; set; }
+
+ [JsonProperty]
+ public bool CheckHash { get; set; }
+
+ protected override void OnAfterLoad(ConfigStorage config)
+ {
+ if (string.IsNullOrEmpty(config.OutputDirectory))
+ {
+ config.OutputDirectory = @"Update";
+ ConfigStorage.Instance.Save();
+ }
+ }
+
+ protected override string ConfigFolder
+ {
+ get { return Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); }
+ }
+
+ protected override string ConfigName
+ {
+ get { return "config"; }
+ }
+ }
+}
diff --git a/Wox.UpdateFeedGenerator/FileInfoEx.cs b/Wox.UpdateFeedGenerator/FileInfoEx.cs
new file mode 100644
index 0000000000..ff707162eb
--- /dev/null
+++ b/Wox.UpdateFeedGenerator/FileInfoEx.cs
@@ -0,0 +1,38 @@
+using System.Diagnostics;
+using System.IO;
+
+namespace Wox.UpdateFeedGenerator
+{
+ public class FileInfoEx
+ {
+ private readonly FileInfo myFileInfo;
+ private readonly string myFileVersion;
+ private readonly string myHash;
+
+ public FileInfo FileInfo
+ {
+ get { return myFileInfo; }
+ }
+
+ public string FileVersion
+ {
+ get { return myFileVersion; }
+ }
+
+ public string Hash
+ {
+ get { return myHash; }
+ }
+
+ public string RelativeName { get; private set; }
+
+ public FileInfoEx(string fileName,int rootDirectoryLength)
+ {
+ myFileInfo = new FileInfo(fileName);
+ myFileVersion = FileVersionInfo.GetVersionInfo(fileName).FileVersion;
+ if (myFileVersion != null) myFileVersion = myFileVersion.Replace(", ", ".");
+ myHash = NAppUpdate.Framework.Utils.FileChecksum.GetSHA256Checksum(fileName);
+ RelativeName = fileName.Substring(rootDirectoryLength + 1);
+ }
+ }
+}
diff --git a/Wox.UpdateFeedGenerator/FileSystemEnumerator.cs b/Wox.UpdateFeedGenerator/FileSystemEnumerator.cs
new file mode 100644
index 0000000000..e7ef75d5f2
--- /dev/null
+++ b/Wox.UpdateFeedGenerator/FileSystemEnumerator.cs
@@ -0,0 +1,248 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Runtime.ConstrainedExecution;
+using System.Runtime.InteropServices;
+using System.Security.Permissions;
+using System.Text.RegularExpressions;
+using Microsoft.Win32.SafeHandles;
+using Wox.UpdateFeedGenerator.Win32;
+
+namespace Wox.UpdateFeedGenerator
+{
+ namespace Win32
+ {
+ ///
+ /// Structure that maps to WIN32_FIND_DATA
+ ///
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
+ internal sealed class FindData
+ {
+ public int fileAttributes;
+ public int creationTime_lowDateTime;
+ public int creationTime_highDateTime;
+ public int lastAccessTime_lowDateTime;
+ public int lastAccessTime_highDateTime;
+ public int lastWriteTime_lowDateTime;
+ public int lastWriteTime_highDateTime;
+ public int nFileSizeHigh;
+ public int nFileSizeLow;
+ public int dwReserved0;
+ public int dwReserved1;
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 260)] public String fileName;
+ [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 14)] public String alternateFileName;
+ }
+
+ ///
+ /// SafeHandle class for holding find handles
+ ///
+ internal sealed class SafeFindHandle : SafeHandleMinusOneIsInvalid
+ {
+ ///
+ /// Constructor
+ ///
+ public SafeFindHandle() : base(true) {}
+
+ ///
+ /// Release the find handle
+ ///
+ /// true if the handle was released
+ [ReliabilityContract(Consistency.WillNotCorruptState, Cer.Success)]
+ protected override bool ReleaseHandle()
+ {
+ return SafeNativeMethods.FindClose(handle);
+ }
+ }
+
+ ///
+ /// Wrapper for P/Invoke methods used by FileSystemEnumerator
+ ///
+ [SecurityPermission(SecurityAction.Assert, UnmanagedCode = true)]
+ internal static class SafeNativeMethods
+ {
+ [DllImport("Kernel32.dll", CharSet = CharSet.Auto)]
+ public static extern SafeFindHandle FindFirstFile(String fileName, [In, Out] FindData findFileData);
+
+ [DllImport("kernel32", CharSet = CharSet.Auto)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ public static extern bool FindNextFile(SafeFindHandle hFindFile, [In, Out] FindData lpFindFileData);
+
+ [DllImport("kernel32", CharSet = CharSet.Auto)]
+ [return: MarshalAs(UnmanagedType.Bool)]
+ public static extern bool FindClose(IntPtr hFindFile);
+ }
+ }
+
+ ///
+ /// File system enumerator. This class provides an easy to use, efficient mechanism for searching a list of
+ /// directories for files matching a list of file specifications. The search is done incrementally as matches
+ /// are consumed, so the overhead before processing the first match is always kept to a minimum.
+ ///
+ public sealed class FileSystemEnumerator : IDisposable
+ {
+ ///
+ /// Information that's kept in our stack for simulated recursion
+ ///
+ private struct SearchInfo
+ {
+ ///
+ /// Find handle returned by FindFirstFile
+ ///
+ public readonly SafeFindHandle Handle;
+
+ ///
+ /// Path that was searched to yield the find handle.
+ ///
+ public readonly string Path;
+
+ ///
+ /// Constructor
+ ///
+ /// Find handle returned by FindFirstFile.
+ /// Path corresponding to find handle.
+ public SearchInfo(SafeFindHandle h, string p)
+ {
+ Handle = h;
+ Path = p;
+ }
+ }
+
+ ///
+ /// Stack of open scopes. This is a member (instead of a local variable)
+ /// to allow Dispose to close any open find handles if the object is disposed
+ /// before the enumeration is completed.
+ ///
+ private readonly Stack m_scopes;
+
+ ///
+ /// Array of paths to be searched.
+ ///
+ private readonly string[] m_paths;
+
+ ///
+ /// Array of regular expressions that will detect matching files.
+ ///
+ private readonly List m_fileSpecs;
+
+ ///
+ /// If true, sub-directories are searched.
+ ///
+ private readonly bool m_includeSubDirs;
+
+ #region IDisposable implementation
+
+ ///
+ /// IDisposable.Dispose
+ ///
+ public void Dispose()
+ {
+ while (m_scopes.Count > 0) {
+ SearchInfo si = m_scopes.Pop();
+ si.Handle.Close();
+ }
+ }
+
+ #endregion
+
+ ///
+ /// Constructor.
+ ///
+ /// Semicolon- or comma-delimitted list of paths to search.
+ /// Semicolon- or comma-delimitted list of wildcard filespecs to match.
+ /// If true, subdirectories are searched.
+ public FileSystemEnumerator(string pathsToSearch, string fileTypesToMatch, bool includeSubDirs)
+ {
+ m_scopes = new Stack();
+
+ // check for nulls
+ if (null == pathsToSearch) throw new ArgumentNullException("pathsToSearch");
+ if (null == fileTypesToMatch) throw new ArgumentNullException("fileTypesToMatch");
+
+ // make sure spec doesn't contain invalid characters
+ if (fileTypesToMatch.IndexOfAny(new[] { ':', '<', '>', '/', '\\' }) >= 0) throw new ArgumentException("invalid cahracters in wildcard pattern", "fileTypesToMatch");
+
+ m_includeSubDirs = includeSubDirs;
+ m_paths = pathsToSearch.Split(new[] { ';', ',' });
+
+ string[] specs = fileTypesToMatch.Split(new[] { ';', ',' });
+ m_fileSpecs = new List(specs.Length);
+ foreach (string spec in specs) {
+ // trim whitespace off file spec and convert Win32 wildcards to regular expressions
+ string pattern = spec.Trim().Replace(".", @"\.").Replace("*", @".*").Replace("?", @".?");
+ m_fileSpecs.Add(new Regex("^" + pattern + "$", RegexOptions.IgnoreCase));
+ }
+ }
+
+ ///
+ /// Get an enumerator that returns all of the files that match the wildcards that
+ /// are in any of the directories to be searched.
+ ///
+ /// An IEnumerable that returns all matching files one by one.
+ ///
+ /// The enumerator that is returned finds files using a lazy algorithm that
+ /// searches directories incrementally as matches are consumed.
+ ///
+ public IEnumerable Matches()
+ {
+ foreach (string rootPath in m_paths) {
+ string path = rootPath.Trim();
+
+ // we "recurse" into a new directory by jumping to this spot
+ top:
+
+ // check security - ensure that caller has rights to read this directory
+ new FileIOPermission(FileIOPermissionAccess.PathDiscovery, Path.Combine(path, ".")).Demand();
+
+ // now that security is checked, go read the directory
+ FindData findData = new FindData();
+ SafeFindHandle handle = SafeNativeMethods.FindFirstFile(Path.Combine(path, "*"), findData);
+ m_scopes.Push(new SearchInfo(handle, path));
+ bool restart = false;
+
+ // we "return" from a sub-directory by jumping to this spot
+ restart:
+// ReSharper disable InvertIf
+ if (!handle.IsInvalid) {
+// ReSharper restore InvertIf
+ do {
+ // if we restarted the loop (unwound a recursion), fetch the next match
+ if (restart) {
+ restart = false;
+ continue;
+ }
+
+ // don't match . or ..
+ if (findData.fileName.Equals(@".") || findData.fileName.Equals(@"..")) continue;
+
+ if ((findData.fileAttributes & (int)FileAttributes.Directory) != 0) {
+ if (m_includeSubDirs) {
+ // it's a directory - recurse into it
+ path = Path.Combine(path, findData.fileName);
+ goto top;
+ }
+ } else {
+ // it's a file, see if any of the filespecs matches it
+ foreach (Regex fileSpec in m_fileSpecs) {
+ // if this spec matches, return this file's info
+ if (fileSpec.IsMatch(findData.fileName)) yield return new FileInfo(Path.Combine(path, findData.fileName));
+ }
+ }
+ } while (SafeNativeMethods.FindNextFile(handle, findData));
+
+ // close this find handle
+ handle.Close();
+
+ // unwind the stack - are we still in a recursion?
+ m_scopes.Pop();
+ if (m_scopes.Count > 0) {
+ SearchInfo si = m_scopes.Peek();
+ handle = si.Handle;
+ path = si.Path;
+ restart = true;
+ goto restart;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/Wox.UpdateFeedGenerator/Generator.cs b/Wox.UpdateFeedGenerator/Generator.cs
new file mode 100644
index 0000000000..9444254895
--- /dev/null
+++ b/Wox.UpdateFeedGenerator/Generator.cs
@@ -0,0 +1,179 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Threading;
+using System.Xml;
+
+namespace Wox.UpdateFeedGenerator
+{
+ public class Generator
+ {
+ private string OutputDirectory;
+ private string SourceDirectory;
+ private string BaseURL = ConfigStorage.Instance.BaseURL;
+ private string feedXMLPath;
+ private bool checkVersion = ConfigStorage.Instance.CheckVersion;
+ private bool checkSize = ConfigStorage.Instance.CheckSize;
+ private bool checkDate = ConfigStorage.Instance.CheckDate;
+ private bool checkHash = ConfigStorage.Instance.CheckHash;
+
+ public Generator()
+ {
+ OutputDirectory = Path.GetFullPath(ConfigStorage.Instance.OutputDirectory);
+ SourceDirectory = Path.GetFullPath(ConfigStorage.Instance.SourceDirectory);
+ feedXMLPath = Path.Combine(ConfigStorage.Instance.OutputDirectory, ConfigStorage.Instance.FeedXMLName);
+ }
+
+ private List ReadSourceFiles()
+ {
+ List files = new List();
+ FileSystemEnumerator enumerator = new FileSystemEnumerator(SourceDirectory, "*.*", true);
+ foreach (FileInfo fi in enumerator.Matches())
+ {
+ string file = fi.FullName;
+ if ((IsIgnorable(file))) continue;
+ FileInfoEx thisInfo = new FileInfoEx(file, SourceDirectory.Length);
+ files.Add(thisInfo);
+ }
+ return files;
+ }
+
+ private bool IsIgnorable(string thisFile)
+ {
+ return false;
+ }
+
+ public void Build()
+ {
+ Console.WriteLine("Building Wox update feed");
+ if (!Directory.Exists(OutputDirectory))
+ {
+ Directory.CreateDirectory(OutputDirectory);
+ }
+
+ XmlDocument doc = new XmlDocument();
+ XmlDeclaration dec = doc.CreateXmlDeclaration("1.0", "utf-8", null);
+
+ doc.AppendChild(dec);
+ XmlElement feed = doc.CreateElement("Feed");
+ feed.SetAttribute("BaseUrl", BaseURL.Trim());
+ doc.AppendChild(feed);
+
+ XmlElement tasks = doc.CreateElement("Tasks");
+
+ foreach (FileInfoEx file in ReadSourceFiles())
+ {
+ Console.WriteLine("adding {0} to feed xml.", file.FileInfo.FullName);
+ XmlElement task = doc.CreateElement("FileUpdateTask");
+ task.SetAttribute("localPath", file.RelativeName);
+
+ // generate FileUpdateTask metadata items
+ task.SetAttribute("lastModified", file.FileInfo.LastWriteTime.ToFileTime().ToString(CultureInfo.InvariantCulture));
+ task.SetAttribute("fileSize", file.FileInfo.Length.ToString(CultureInfo.InvariantCulture));
+ if (!string.IsNullOrEmpty(file.FileVersion)) task.SetAttribute("version", file.FileVersion);
+
+ XmlElement conds = doc.CreateElement("Conditions");
+ XmlElement cond;
+ bool hasFirstCondition = false;
+
+ //File Exists
+ cond = doc.CreateElement("FileExistsCondition");
+ cond.SetAttribute("type", "or");
+ conds.AppendChild(cond);
+
+ //Version
+ if (checkVersion && !string.IsNullOrEmpty(file.FileVersion))
+ {
+ cond = doc.CreateElement("FileVersionCondition");
+ cond.SetAttribute("what", "below");
+ cond.SetAttribute("version", file.FileVersion);
+ conds.AppendChild(cond);
+ hasFirstCondition = true;
+ }
+
+ //Size
+ if (checkSize)
+ {
+ cond = doc.CreateElement("FileSizeCondition");
+ cond.SetAttribute("type", hasFirstCondition ? "or-not" : "not");
+ cond.SetAttribute("what", "is");
+ cond.SetAttribute("size", file.FileInfo.Length.ToString(CultureInfo.InvariantCulture));
+ conds.AppendChild(cond);
+ }
+
+ //Date
+ if (checkDate)
+ {
+ cond = doc.CreateElement("FileDateCondition");
+ if (hasFirstCondition) cond.SetAttribute("type", "or");
+ cond.SetAttribute("what", "older");
+ // local timestamp, not UTC
+ cond.SetAttribute("timestamp", file.FileInfo.LastWriteTime.ToFileTime().ToString(CultureInfo.InvariantCulture));
+ conds.AppendChild(cond);
+ }
+
+ //Hash
+ if (checkHash)
+ {
+ cond = doc.CreateElement("FileChecksumCondition");
+ cond.SetAttribute("type", hasFirstCondition ? "or-not" : "not");
+ cond.SetAttribute("checksumType", "sha256");
+ cond.SetAttribute("checksum", file.Hash);
+ conds.AppendChild(cond);
+ }
+
+ task.AppendChild(conds);
+ tasks.AppendChild(task);
+ string destFile = Path.Combine(OutputDirectory, file.RelativeName);
+ CopyFile(file.FileInfo.FullName, destFile);
+ }
+ feed.AppendChild(tasks);
+ doc.Save(feedXMLPath);
+ }
+
+ private bool CopyFile(string sourceFile, string destFile)
+ {
+ // If the target folder doesn't exist, create the path to it
+ var fi = new FileInfo(destFile);
+ var d = Directory.GetParent(fi.FullName);
+ if (!Directory.Exists(d.FullName)) CreateDirectoryPath(d.FullName);
+
+ // Copy with delayed retry
+ int retries = 3;
+ while (retries > 0)
+ {
+ try
+ {
+ if (File.Exists(destFile)) File.Delete(destFile);
+ File.Copy(sourceFile, destFile);
+ retries = 0; // success
+ return true;
+ }
+ catch (IOException)
+ {
+ // Failed... let's try sleeping a bit (slow disk maybe)
+ if (retries-- > 0) Thread.Sleep(200);
+ }
+ catch (UnauthorizedAccessException)
+ {
+ // same handling as IOException
+ if (retries-- > 0) Thread.Sleep(200);
+ }
+ }
+ return false;
+ }
+
+ private void CreateDirectoryPath(string directoryPath)
+ {
+ // Create the folder/path if it doesn't exist, with delayed retry
+ int retries = 3;
+ while (retries > 0 && !Directory.Exists(directoryPath))
+ {
+ Directory.CreateDirectory(directoryPath);
+ if (retries-- < 3) Thread.Sleep(200);
+ }
+ }
+
+ }
+}
diff --git a/Wox.UpdateFeedGenerator/Program.cs b/Wox.UpdateFeedGenerator/Program.cs
new file mode 100644
index 0000000000..62953a2a63
--- /dev/null
+++ b/Wox.UpdateFeedGenerator/Program.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Text;
+using System.Threading;
+using System.Xml;
+
+namespace Wox.UpdateFeedGenerator
+{
+ class Program
+ {
+ static void Main(string[] args)
+ {
+ new Generator().Build();
+ }
+ }
+}
diff --git a/Wox.UpdateFeedGenerator/Properties/AssemblyInfo.cs b/Wox.UpdateFeedGenerator/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..d0f4268282
--- /dev/null
+++ b/Wox.UpdateFeedGenerator/Properties/AssemblyInfo.cs
@@ -0,0 +1,36 @@
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+// 有关程序集的常规信息通过以下
+// 特性集控制。更改这些特性值可修改
+// 与程序集关联的信息。
+[assembly: AssemblyTitle("Wox.UpdateFeedGenerator")]
+[assembly: AssemblyDescription("")]
+[assembly: AssemblyConfiguration("")]
+[assembly: AssemblyCompany("")]
+[assembly: AssemblyProduct("Wox.UpdateFeedGenerator")]
+[assembly: AssemblyCopyright("Copyright © 2015")]
+[assembly: AssemblyTrademark("")]
+[assembly: AssemblyCulture("")]
+
+// 将 ComVisible 设置为 false 使此程序集中的类型
+// 对 COM 组件不可见。 如果需要从 COM 访问此程序集中的类型,
+// 则将该类型上的 ComVisible 特性设置为 true。
+[assembly: ComVisible(false)]
+
+// 如果此项目向 COM 公开,则下列 GUID 用于类型库的 ID
+[assembly: Guid("2f3420c0-2c21-4f71-a45d-a47b5305fe20")]
+
+// 程序集的版本信息由下面四个值组成:
+//
+// 主版本
+// 次版本
+// 生成号
+// 修订号
+//
+// 可以指定所有这些值,也可以使用“生成号”和“修订号”的默认值,
+// 方法是按如下所示使用“*”:
+// [assembly: AssemblyVersion("1.0.*")]
+[assembly: AssemblyVersion("1.0.0.0")]
+[assembly: AssemblyFileVersion("1.0.0.0")]
diff --git a/Wox.UpdateFeedGenerator/README.md b/Wox.UpdateFeedGenerator/README.md
new file mode 100644
index 0000000000..00741d2ac0
--- /dev/null
+++ b/Wox.UpdateFeedGenerator/README.md
@@ -0,0 +1 @@
+NAppUpdate feed generator for Wox. It's something like [FeedBuilder](https://github.com/synhershko/NAppUpdate/tree/master/FeedBuilder)
\ No newline at end of file
diff --git a/Wox.UpdateFeedGenerator/Wox.UpdateFeedGenerator.csproj b/Wox.UpdateFeedGenerator/Wox.UpdateFeedGenerator.csproj
new file mode 100644
index 0000000000..2752c0e04e
--- /dev/null
+++ b/Wox.UpdateFeedGenerator/Wox.UpdateFeedGenerator.csproj
@@ -0,0 +1,84 @@
+
+
+
+
+ Debug
+ AnyCPU
+ {D120E62B-EC59-4FB4-8129-EFDD4C446A5F}
+ Exe
+ Properties
+ Wox.UpdateFeedGenerator
+ Wox.UpdateFeedGenerator
+ v3.5
+ 512
+ ..\
+ true
+
+
+ AnyCPU
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ AnyCPU
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+ ..\References\NAppUpdate.Framework.dll
+
+
+ False
+ ..\packages\Newtonsoft.Json.6.0.8\lib\net35\Newtonsoft.Json.dll
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {4fd29318-a8ab-4d8f-aa47-60bc241b8da3}
+ Wox.Infrastructure
+
+
+
+
+
+
+ 这台计算机上缺少此项目引用的 NuGet 程序包。启用“NuGet 程序包还原”可下载这些程序包。有关详细信息,请参阅 http://go.microsoft.com/fwlink/?LinkID=322105。缺少的文件是 {0}。
+
+
+
+
+
\ No newline at end of file
diff --git a/Wox.UpdateFeedGenerator/packages.config b/Wox.UpdateFeedGenerator/packages.config
new file mode 100644
index 0000000000..7a13476a54
--- /dev/null
+++ b/Wox.UpdateFeedGenerator/packages.config
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/Wox.sln b/Wox.sln
index cd2007ff85..3125875e5f 100644
--- a/Wox.sln
+++ b/Wox.sln
@@ -41,6 +41,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wox.CrashReporter", "Wox.Cr
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wox.Plugin.QueryHistory", "Plugins\Wox.Plugin.QueryHistory\Wox.Plugin.QueryHistory.csproj", "{B552DCB6-692E-4B1D-9E0B-9096A2A7E6B0}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Wox.UpdateFeedGenerator", "Wox.UpdateFeedGenerator\Wox.UpdateFeedGenerator.csproj", "{D120E62B-EC59-4FB4-8129-EFDD4C446A5F}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -119,6 +121,10 @@ Global
{B552DCB6-692E-4B1D-9E0B-9096A2A7E6B0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B552DCB6-692E-4B1D-9E0B-9096A2A7E6B0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B552DCB6-692E-4B1D-9E0B-9096A2A7E6B0}.Release|Any CPU.Build.0 = Release|Any CPU
+ {D120E62B-EC59-4FB4-8129-EFDD4C446A5F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {D120E62B-EC59-4FB4-8129-EFDD4C446A5F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {D120E62B-EC59-4FB4-8129-EFDD4C446A5F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {D120E62B-EC59-4FB4-8129-EFDD4C446A5F}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE