28 Commits

Author SHA1 Message Date
af88692aab Fix artifact 2025-09-25 15:37:58 -04:00
3b1303c7d4 Add installer directory reference 2025-09-25 15:22:01 -04:00
ce1fed6636 Fix publish warning and build restore 2025-09-25 15:18:47 -04:00
40f7dd0fa4 Fix version number 2025-09-25 15:09:54 -04:00
00c2cb7227 Update build steps 2025-09-25 15:06:09 -04:00
4d7d7c041a Update to .NET 9 2025-09-25 14:37:42 -04:00
636c7f9b2a Merge branch 'main' into prerelease
# Conflicts:
#	Application/FeedCenter.csproj
#	Application/Properties/Resources.resx
2025-09-25 12:53:39 -04:00
41029671dc Update installer to self-contained 2025-09-25 12:34:36 -04:00
4e721efa55 Start adding server support 2025-09-24 21:08:59 -04:00
f3145d8811 Add prerelease option 2025-09-24 18:29:32 -04:00
9e2e7aabe8 Fix hang when moving window during refresh 2024-12-08 21:51:40 -05:00
7ee84f079b Revert attempts 2024-06-25 21:36:13 -04:00
8f70003bef Try previous image? 2024-06-25 21:29:33 -04:00
38f093dc5c Increase logging 2024-06-25 21:26:44 -04:00
e5bdc80d10 Try fixing build 2024-06-25 20:46:00 -04:00
260268194a Handle a few more errors and update a few packages 2024-06-18 20:34:22 -04:00
8ecde89be0 Crash fixes 2023-07-14 17:01:52 -04:00
845e80577c Undo previous change and set status to "success" if feed read but wasn't modified 2023-07-11 17:42:29 -04:00
31a04b13e6 Exclude "not modified" from errors 2023-07-11 17:35:03 -04:00
64d893ae0f Update HTTP request headers 2023-06-22 17:08:20 -04:00
0ddd38e3f2 Add support for per-feed user agent 2023-06-15 17:28:38 -04:00
f5f78c8825 Remove extra restore now that config is fixed 2023-04-29 19:06:58 -04:00
477185341e Branch and config updates 2023-04-29 19:03:25 -04:00
1db13fbe2e Update installer to detect .NET runtime 2023-04-29 18:42:53 -04:00
c64ec21bd9 Adjust feed read exception handling 2023-04-28 14:44:01 -04:00
45009a31b9 ID doesn't work in a release build for some reason 2023-04-28 13:02:18 -04:00
2a7ef356bd Set tray icon ID 2023-04-28 12:50:49 -04:00
105b6e7d30 Update package versions 2023-04-28 12:47:40 -04:00
72 changed files with 2304 additions and 1000 deletions

View File

@@ -7,18 +7,5 @@ public static class Database
public static string DatabaseFile { get; set; }
public static string DatabasePath { get; set; }
public static FeedCenterEntities Entities { get; set; }
public static bool Exists => File.Exists(DatabaseFile);
public static bool Loaded { get; set; }
public static void Load()
{
if (Loaded) return;
Entities = new FeedCenterEntities();
Loaded = true;
}
}

View File

@@ -4,24 +4,17 @@ using System.Collections.Specialized;
namespace FeedCenter.Data;
public class RealmObservableCollection<T> : ObservableCollection<T> where T : IRealmObject
public class RealmObservableCollection<T>(Realm realm) : ObservableCollection<T>(realm.All<T>()) where T : IRealmObject
{
private readonly Realm _realm;
public RealmObservableCollection(Realm realm) : base(realm.All<T>())
{
_realm = realm;
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (e.OldItems != null)
foreach (T item in e.OldItems)
_realm.Remove(item);
realm.Remove(item);
if (e.NewItems != null)
foreach (T item in e.NewItems)
_realm.Add(item);
realm.Add(item);
base.OnCollectionChanged(e);
}

View File

@@ -8,33 +8,97 @@ namespace FeedCenter;
public class FeedCenterEntities
{
public Realm RealmInstance { get; }
public RealmObservableCollection<Category> Categories { get; }
public RealmObservableCollection<Feed> Feeds { get; private set; }
public RealmObservableCollection<Setting> Settings { get; private set; }
public FeedCenterEntities()
{
var realmConfiguration = new RealmConfiguration($"{Database.DatabaseFile}");
var realmConfiguration = new RealmConfiguration($"{Database.DatabaseFile}")
{
SchemaVersion = 2,
MigrationCallback = (migration, oldSchemaVersion) =>
{
if (oldSchemaVersion == 1)
migration.NewRealm.Add(Account.CreateDefault());
var localAccount = migration.NewRealm.All<Account>().First(a => a.Type == AccountType.Local);
var newVersionCategories = migration.NewRealm.All<Category>();
foreach (var newVersionCategory in newVersionCategories)
{
switch (oldSchemaVersion)
{
case 1:
newVersionCategory.Account = localAccount;
newVersionCategory.RemoteId = null;
break;
}
}
var newVersionFeeds = migration.NewRealm.All<Feed>();
foreach (var newVersionFeed in newVersionFeeds)
{
switch (oldSchemaVersion)
{
case 0:
newVersionFeed.UserAgent = null;
break;
case 1:
newVersionFeed.Account = localAccount;
newVersionFeed.RemoteId = null;
break;
}
}
var newVersionFeedItems = migration.NewRealm.All<FeedItem>();
foreach (var newVersionFeedItem in newVersionFeedItems)
{
switch (oldSchemaVersion)
{
case 1:
newVersionFeedItem.RemoteId = null;
break;
}
}
}
};
RealmInstance = Realm.GetInstance(realmConfiguration);
Accounts = new RealmObservableCollection<Account>(RealmInstance);
Settings = new RealmObservableCollection<Setting>(RealmInstance);
Feeds = new RealmObservableCollection<Feed>(RealmInstance);
Categories = new RealmObservableCollection<Category>(RealmInstance);
if (!Accounts.Any())
{
RealmInstance.Write(() => Accounts.Add(Account.CreateDefault()));
}
if (!Categories.Any())
{
RealmInstance.Write(() => Categories.Add(Category.CreateDefault()));
var localAccount = Accounts.First(a => a.Type == AccountType.Local);
RealmInstance.Write(() => Categories.Add(Category.CreateDefault(localAccount)));
}
}
public void Refresh()
public RealmObservableCollection<Category> Categories { get; }
public Category DefaultCategory
{
RealmInstance.Refresh();
get { return Categories.First(c => c.IsDefault); }
}
public Account LocalAccount
{
get { return Accounts.First(a => a.Type == AccountType.Local); }
}
public RealmObservableCollection<Feed> Feeds { get; }
public RealmObservableCollection<Account> Accounts { get; }
private Realm RealmInstance { get; }
public RealmObservableCollection<Setting> Settings { get; }
public void SaveChanges(Action action)
{
RealmInstance.Write(action);
@@ -44,9 +108,4 @@ public class FeedCenterEntities
{
return RealmInstance.BeginWrite();
}
public Category DefaultCategory
{
get { return Categories.First(c => c.IsDefault); }
}
}

View File

@@ -1,6 +1,6 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net70-windows</TargetFramework>
<TargetFramework>net9.0-windows8.0</TargetFramework>
<OutputType>WinExe</OutputType>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<UseWindowsForms>false</UseWindowsForms>
@@ -24,35 +24,36 @@
<None Remove="Resources\Warning.ico" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="ChrisKaczor.ApplicationUpdate" Version="1.0.2" />
<PackageReference Include="ChrisKaczor.GenericSettingsProvider" Version="1.0.3" />
<PackageReference Include="ChrisKaczor.InstalledBrowsers" Version="1.0.3" />
<PackageReference Include="ChrisKaczor.Wpf.Application.SingleInstance" Version="1.0.4" />
<PackageReference Include="ChrisKaczor.Wpf.Application.StartWithWindows" Version="1.0.3" />
<PackageReference Include="ChrisKaczor.Wpf.Controls.HtmlTextBlock" Version="1.0.2" />
<PackageReference Include="ChrisKaczor.Wpf.Controls.Link" Version="1.0.3" />
<PackageReference Include="ChrisKaczor.Wpf.Controls.Toolbar" Version="1.0.2" />
<PackageReference Include="ChrisKaczor.Wpf.Validation" Version="1.0.3" />
<PackageReference Include="ChrisKaczor.Wpf.Windows.ControlBox" Version="1.0.2" />
<PackageReference Include="ChrisKaczor.Wpf.Windows.SnappingWindow" Version="1.0.2" />
<PackageReference Include="Dapper" Version="2.0.123" />
<PackageReference Include="DebounceThrottle" Version="2.0.0" />
<PackageReference Include="H.NotifyIcon.Wpf" Version="2.0.108" />
<PackageReference Include="HtmlAgilityPack" Version="1.11.46" />
<PackageReference Include="HtmlTextWriter" Version="2.1.1" />
<PackageReference Include="MahApps.Metro" Version="2.4.9" />
<PackageReference Include="ChrisKaczor.ApplicationUpdate" Version="1.0.7" />
<PackageReference Include="ChrisKaczor.FeverClient" Version="1.0.1" />
<PackageReference Include="ChrisKaczor.GenericSettingsProvider" Version="1.0.4" />
<PackageReference Include="ChrisKaczor.InstalledBrowsers" Version="1.0.4" />
<PackageReference Include="ChrisKaczor.Wpf.Application.SingleInstance" Version="1.0.5" />
<PackageReference Include="ChrisKaczor.Wpf.Application.StartWithWindows" Version="1.0.5" />
<PackageReference Include="ChrisKaczor.Wpf.Controls.HtmlTextBlock" Version="1.0.6" />
<PackageReference Include="ChrisKaczor.Wpf.Controls.Link" Version="1.0.4" />
<PackageReference Include="ChrisKaczor.Wpf.Controls.Toolbar" Version="1.0.3" />
<PackageReference Include="ChrisKaczor.Wpf.Validation" Version="1.0.4" />
<PackageReference Include="ChrisKaczor.Wpf.Windows.ControlBox" Version="1.0.3" />
<PackageReference Include="ChrisKaczor.Wpf.Windows.SnappingWindow" Version="1.0.4" />
<PackageReference Include="Dapper" Version="2.1.66" />
<PackageReference Include="DebounceThrottle" Version="3.0.1" />
<PackageReference Include="H.NotifyIcon.Wpf" Version="2.3.0" />
<PackageReference Include="HtmlAgilityPack" Version="1.12.3" />
<PackageReference Include="HtmlTextWriter" Version="3.0.2" />
<PackageReference Include="MahApps.Metro" Version="2.4.11" />
<PackageReference Include="Microsoft.SqlServer.Compact" Version="4.0.8876.1" GeneratePathProperty="true">
<NoWarn>NU1701</NoWarn>
</PackageReference>
<PackageReference Include="Microsoft.Windows.Compatibility" Version="7.0.1" />
<PackageReference Include="NameBasedGrid" Version="0.10.1">
<PackageReference Include="Microsoft.Windows.Compatibility" Version="9.0.9" />
<PackageReference Include="NameBasedGrid" Version="1.0.0">
<NoWarn>NU1701</NoWarn>
</PackageReference>
<PackageReference Include="Realm" Version="10.21.1" />
<PackageReference Include="Serilog" Version="2.12.0" />
<PackageReference Include="Serilog.Enrichers.Thread" Version="3.1.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.0" />
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
<PackageReference Include="Realm" Version="20.1.0" />
<PackageReference Include="Serilog" Version="4.3.0" />
<PackageReference Include="Serilog.Enrichers.Thread" Version="4.0.0" />
<PackageReference Include="Serilog.Sinks.Console" Version="6.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
</ItemGroup>
<ItemGroup>

View File

@@ -0,0 +1,49 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 18
VisualStudioVersion = 18.0.11018.127
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FeedCenter", "FeedCenter.csproj", "{BD3D12F2-DE23-4466-83B1-1EB617A877A4}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution", "Solution", "{1462AAAD-B01B-4FF6-9B9F-595D239C9D1E}"
ProjectSection(SolutionItems) = preProject
..\.gitignore = ..\.gitignore
..\appveyor.yml = ..\appveyor.yml
..\LICENSE.md = ..\LICENSE.md
..\README.md = ..\README.md
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Debug|Mixed Platforms = Debug|Mixed Platforms
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|Any CPU = Release|Any CPU
Release|Mixed Platforms = Release|Mixed Platforms
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Debug|x64.ActiveCfg = Debug|x64
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Debug|x64.Build.0 = Debug|x64
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Debug|x86.ActiveCfg = Debug|x86
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Debug|x86.Build.0 = Debug|x86
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Release|Any CPU.Build.0 = Release|Any CPU
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Release|x64.ActiveCfg = Release|x86
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Release|x86.ActiveCfg = Release|Any CPU
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B1D182A0-263B-4AB8-8413-56303DBD4CCC}
EndGlobalSection
EndGlobal

View File

@@ -3,4 +3,5 @@
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpKeepExistingMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpPlaceEmbeddedOnSameLineMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ECSharpUseContinuousIndentInsideBracesMigration/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EPsi_002ECSharp_002ECodeStyle_002ESettingsUpgrade_002EMigrateBlankLinesAroundFieldToBlankLinesAroundProperty/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Kaczor/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View File

@@ -5,7 +5,6 @@ using System.Windows;
using System.Windows.Data;
using System.Windows.Input;
using ChrisKaczor.InstalledBrowsers;
using FeedCenter.Data;
using FeedCenter.Options;
using FeedCenter.Properties;
@@ -14,6 +13,7 @@ namespace FeedCenter;
public partial class FeedErrorWindow
{
private CollectionViewSource _collectionViewSource;
private readonly FeedCenterEntities _entities = new();
public FeedErrorWindow()
{
@@ -23,7 +23,7 @@ public partial class FeedErrorWindow
public void Display(Window owner)
{
// Create a view and sort it by name
_collectionViewSource = new CollectionViewSource { Source = Database.Entities.Feeds };
_collectionViewSource = new CollectionViewSource { Source = _entities.Feeds };
_collectionViewSource.Filter += HandleCollectionViewSourceFilter;
_collectionViewSource.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));
@@ -62,7 +62,7 @@ public partial class FeedErrorWindow
var feed = (Feed) FeedDataGrid.SelectedItem;
var feedWindow = new FeedWindow();
var feedWindow = new FeedWindow(_entities);
feedWindow.Display(feed, GetWindow(this));
}
@@ -74,7 +74,7 @@ public partial class FeedErrorWindow
var feed = (Feed) FeedDataGrid.SelectedItem;
Database.Entities.SaveChanges(() => Database.Entities.Feeds.Remove(feed));
_entities.SaveChanges(() => _entities.Feeds.Remove(feed));
SetFeedButtonStates();
}
@@ -118,8 +118,6 @@ public partial class FeedErrorWindow
entities.SaveChanges(() => feed.Read(true));
});
Database.Entities.Refresh();
var selectedIndex = FeedDataGrid.SelectedIndex;
_collectionViewSource.View.Refresh();

View File

@@ -0,0 +1,203 @@
using Realms;
using System;
using System.Collections;
using System.ComponentModel;
using System.Linq;
namespace FeedCenter;
public class Account : RealmObject, INotifyDataErrorInfo
{
public const string DefaultName = "< Local >";
private readonly DataErrorDictionary _dataErrorDictionary;
public Account() : this(AccountType.Local)
{
}
public Account(AccountType type)
{
Type = type;
_dataErrorDictionary = new DataErrorDictionary();
_dataErrorDictionary.ErrorsChanged += DataErrorDictionaryErrorsChanged;
}
[PrimaryKey]
public Guid Id { get; set; } = Guid.NewGuid();
public AccountType Type
{
get => Enum.TryParse(TypeRaw, out AccountType result) ? result : AccountType.Local;
set => TypeRaw = value.ToString();
}
public bool SupportsFeedEdit => Type switch
{
AccountType.Fever => false,
AccountType.GoogleReader => false,
AccountType.Local => true,
_ => throw new NotSupportedException()
};
public bool SupportsFeedDelete => Type switch
{
AccountType.Fever => false,
AccountType.GoogleReader => false,
AccountType.Local => true,
_ => throw new NotSupportedException()
};
private string TypeRaw { get; set; }
public string Name
{
get => RawName;
set
{
RawName = value;
ValidateString(nameof(Name), RawName);
RaisePropertyChanged();
}
}
[MapTo("Name")]
private string RawName { get; set; } = string.Empty;
public string Url
{
get => RawUrl;
set
{
RawUrl = value;
ValidateString(nameof(Url), RawUrl);
RaisePropertyChanged();
}
}
[MapTo("Url")]
public string RawUrl { get; set; }
public string Username
{
get => RawUsername;
set
{
RawUsername = value;
if (!Authenticate)
{
_dataErrorDictionary.ClearErrors(nameof(Username));
return;
}
ValidateString(nameof(Username), RawUsername);
RaisePropertyChanged();
}
}
[MapTo("Username")]
public string RawUsername { get; set; }
public string Password
{
get => RawPassword;
set
{
RawPassword = value;
if (!Authenticate)
{
_dataErrorDictionary.ClearErrors(nameof(Password));
return;
}
ValidateString(nameof(Password), RawPassword);
RaisePropertyChanged();
}
}
[MapTo("Password")]
public string RawPassword { get; set; }
public bool Authenticate { get; set; }
public bool Enabled { get; set; } = true;
public int CheckInterval { get; set; } = 60;
public DateTimeOffset LastChecked { get; set; }
public bool HasErrors => _dataErrorDictionary.Any();
public IEnumerable GetErrors(string propertyName)
{
return _dataErrorDictionary.GetErrors(propertyName);
}
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
private void DataErrorDictionaryErrorsChanged(object sender, DataErrorsChangedEventArgs e)
{
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(e.PropertyName));
}
private void ValidateString(string propertyName, string value)
{
_dataErrorDictionary.ClearErrors(propertyName);
if (string.IsNullOrWhiteSpace(value))
_dataErrorDictionary.AddError(propertyName, $"{propertyName} cannot be empty");
}
public static Account CreateDefault()
{
return new Account { Name = DefaultName, Type = AccountType.Local };
}
public int GetProgressSteps(FeedCenterEntities entities)
{
var progressSteps = Type switch
{
// Delegate to the right reader based on the account type
AccountType.Fever => new FeverReader().GetProgressSteps(entities),
AccountType.GoogleReader => new GoogleReaderReader().GetProgressSteps(entities),
AccountType.Local => new LocalReader().GetProgressSteps(entities),
_ => throw new NotSupportedException()
};
return progressSteps;
}
public AccountReadResult Read(AccountReadInput accountReadInput)
{
// If not enabled then do nothing
if (!Enabled)
return AccountReadResult.NotEnabled;
// Check if we're forcing a read
if (!accountReadInput.ForceRead)
{
// Figure out how long since we last checked
var timeSpan = DateTimeOffset.Now - LastChecked;
// Check if we are due to read the feed
if (timeSpan.TotalMinutes < CheckInterval)
return AccountReadResult.NotDue;
}
var accountReadResult = Type switch
{
// Delegate to the right reader based on the account type
AccountType.Fever => new FeverReader().Read(this, accountReadInput),
AccountType.GoogleReader => new GoogleReaderReader().Read(this, accountReadInput),
AccountType.Local => new LocalReader().Read(this, accountReadInput),
_ => throw new NotSupportedException()
};
return accountReadResult;
}
}

View File

@@ -0,0 +1,11 @@
using System;
namespace FeedCenter;
public class AccountReadInput(FeedCenterEntities entities, Guid? feedId, bool forceRead, Action incrementProgress)
{
public FeedCenterEntities Entities { get; set; } = entities;
public Guid? FeedId { get; set; } = feedId;
public bool ForceRead { get; set; } = forceRead;
public Action IncrementProgress { get; private set; } = incrementProgress ?? throw new ArgumentNullException(nameof(incrementProgress));
}

View File

@@ -0,0 +1,8 @@
namespace FeedCenter;
public enum AccountReadResult
{
Success,
NotDue,
NotEnabled
}

View File

@@ -0,0 +1,8 @@
namespace FeedCenter;
public enum AccountType
{
Local,
Fever,
GoogleReader
}

View File

@@ -22,6 +22,10 @@ public class Category : RealmObject, INotifyDataErrorInfo
[PrimaryKey]
public Guid Id { get; set; } = Guid.NewGuid();
public string RemoteId { get; set; }
public Account Account { get; set; }
public bool IsDefault { get; internal set; }
public string Name
@@ -56,9 +60,9 @@ public class Category : RealmObject, INotifyDataErrorInfo
ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(e.PropertyName));
}
public static Category CreateDefault()
public static Category CreateDefault(Account account)
{
return new Category { Name = DefaultName, IsDefault = true };
return new Category { Name = DefaultName, IsDefault = true, Account = account };
}
private void ValidateName()

View File

@@ -1,16 +1,7 @@
using ChrisKaczor.ApplicationUpdate;
using FeedCenter.Data;
using FeedCenter.FeedParsers;
using FeedCenter.Properties;
using FeedCenter.Xml;
using JetBrains.Annotations;
using Realms;
using Serilog;
using System;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
@@ -19,6 +10,14 @@ using System.Net.Http.Headers;
using System.Net.Sockets;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using ChrisKaczor.ApplicationUpdate;
using FeedCenter.FeedParsers;
using FeedCenter.Properties;
using FeedCenter.Xml;
using JetBrains.Annotations;
using Realms;
using Serilog;
namespace FeedCenter;
@@ -34,14 +33,16 @@ public partial class Feed : RealmObject, INotifyDataErrorInfo
_dataErrorDictionary.ErrorsChanged += DataErrorDictionaryErrorsChanged;
}
[PrimaryKey]
public Guid Id { get; set; }
public string RemoteId { get; set; }
public bool Authenticate { get; set; }
public Guid CategoryId { get; set; }
public int CheckInterval { get; set; } = 60;
public string Description { get; set; }
public bool Enabled { get; set; } = true;
[PrimaryKey]
public Guid Id { get; set; }
public Account Account { get; set; }
[UsedImplicitly]
public IList<FeedItem> Items { get; }
@@ -98,9 +99,6 @@ public partial class Feed : RealmObject, INotifyDataErrorInfo
}
}
[MapTo("Password")]
public string RawPassword { get; set; }
public string Password
{
get => RawPassword;
@@ -122,9 +120,15 @@ public partial class Feed : RealmObject, INotifyDataErrorInfo
[MapTo("Name")]
private string RawName { get; set; } = string.Empty;
[MapTo("Password")]
public string RawPassword { get; set; }
[MapTo("Source")]
private string RawSource { get; set; } = string.Empty;
[MapTo("Username")]
public string RawUsername { get; set; }
public string Source
{
get => RawSource;
@@ -139,8 +143,7 @@ public partial class Feed : RealmObject, INotifyDataErrorInfo
public string Title { get; set; }
[MapTo("Username")]
public string RawUsername { get; set; }
public string UserAgent { get; set; }
public string Username
{
@@ -169,9 +172,9 @@ public partial class Feed : RealmObject, INotifyDataErrorInfo
public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;
public static Feed Create()
public static Feed Create(FeedCenterEntities entities)
{
return new Feed { Id = Guid.NewGuid(), CategoryId = Database.Entities.DefaultCategory.Id };
return new Feed { Id = Guid.NewGuid(), CategoryId = entities.DefaultCategory.Id, Account = entities.LocalAccount };
}
private void DataErrorDictionaryErrorsChanged(object sender, DataErrorsChangedEventArgs e)
@@ -200,6 +203,9 @@ public partial class Feed : RealmObject, INotifyDataErrorInfo
case FeedReadResult.NotEnabled:
case FeedReadResult.NotModified:
// Reset status to success
LastReadResult = FeedReadResult.Success;
// Ignore
break;
@@ -210,7 +216,7 @@ public partial class Feed : RealmObject, INotifyDataErrorInfo
break;
}
// If the feed was successfully read and we have no last update timestamp - set the last update timestamp to now
// If the feed was successfully read, and we have no last update timestamp - set the last update timestamp to now
if (result == FeedReadResult.Success && LastUpdated == default)
LastUpdated = DateTimeOffset.Now;
@@ -242,6 +248,17 @@ public partial class Feed : RealmObject, INotifyDataErrorInfo
return new Tuple<FeedType, string>(feedType, retrieveResult.Item2);
}
private string GetUserAgent()
{
if (!string.IsNullOrWhiteSpace(UserAgent))
return UserAgent;
if (!string.IsNullOrWhiteSpace(Settings.Default.DefaultUserAgent))
return Settings.Default.DefaultUserAgent;
return "FeedCenter/" + UpdateCheck.LocalVersion;
}
private Tuple<FeedReadResult, string> RetrieveFeed()
{
try
@@ -252,25 +269,38 @@ public partial class Feed : RealmObject, INotifyDataErrorInfo
var clientHandler = new HttpClientHandler
{
// Set that we'll accept compressed data
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate,
AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate | DecompressionMethods.Brotli,
AllowAutoRedirect = true
};
_httpClient = new HttpClient(clientHandler);
_httpClient = new HttpClient(clientHandler)
{
// Set a timeout
Timeout = TimeSpan.FromSeconds(10)
};
// Set a user agent string
var userAgent = string.IsNullOrWhiteSpace(Settings.Default.DefaultUserAgent) ? "FeedCenter/" + UpdateCheck.LocalVersion : Settings.Default.DefaultUserAgent;
_httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(userAgent);
// Set a timeout
_httpClient.Timeout = TimeSpan.FromSeconds(10);
_httpClient.DefaultRequestHeaders.Accept.ParseAdd("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
_httpClient.DefaultRequestHeaders.AcceptEncoding.ParseAdd("gzip, deflate, br");
_httpClient.DefaultRequestHeaders.AcceptLanguage.ParseAdd("en-US,en;q=0.9");
_httpClient.DefaultRequestHeaders.CacheControl = CacheControlHeaderValue.Parse("max-age=0");
}
// Set a user agent string
var userAgent = GetUserAgent();
_httpClient.DefaultRequestHeaders.UserAgent.Clear();
_httpClient.DefaultRequestHeaders.UserAgent.ParseAdd(userAgent);
// If we need to authenticate then set the credentials
_httpClient.DefaultRequestHeaders.Authorization = Authenticate ? new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes($"{Username}:{Password}"))) : null;
_httpClient.DefaultRequestHeaders.IfModifiedSince = LastChecked;
// Attempt to get the response
var feedStream = _httpClient.GetStreamAsync(Source).Result;
var response = _httpClient.GetAsync(Source).Result;
response.EnsureSuccessStatusCode();
var feedStream = response.Content.ReadAsStream();
// Create the text reader
using StreamReader textReader = new XmlSanitizingStream(feedStream, Encoding.UTF8);
@@ -293,78 +323,28 @@ public partial class Feed : RealmObject, INotifyDataErrorInfo
return Tuple.Create(FeedReadResult.Success, feedText);
}
catch (IOException ioException)
catch (HttpRequestException httpRequestException)
{
Log.Logger.Error(ioException, "Exception");
if (httpRequestException.StatusCode == HttpStatusCode.NotModified)
{
return Tuple.Create(FeedReadResult.NotModified, string.Empty);
}
return Tuple.Create(FeedReadResult.ConnectionFailed, string.Empty);
Log.Logger.Error(httpRequestException, "Exception");
return HandleHttpRequestException(httpRequestException);
}
catch (AggregateException aggregateException)
{
Log.Logger.Error(aggregateException, "Exception");
var innerException = aggregateException.InnerException;
if (innerException is not HttpRequestException httpRequestException)
return Tuple.Create(FeedReadResult.UnknownError, string.Empty);
switch (httpRequestException.StatusCode)
return aggregateException.InnerException switch
{
case HttpStatusCode.ServiceUnavailable:
return Tuple.Create(FeedReadResult.TemporarilyUnavailable, string.Empty);
case HttpStatusCode.InternalServerError:
return Tuple.Create(FeedReadResult.ServerError, string.Empty);
case HttpStatusCode.NotModified:
return Tuple.Create(FeedReadResult.NotModified, string.Empty);
case HttpStatusCode.NotFound:
return Tuple.Create(FeedReadResult.NotFound, string.Empty);
case HttpStatusCode.Unauthorized:
case HttpStatusCode.Forbidden:
return Tuple.Create(FeedReadResult.Unauthorized, string.Empty);
case HttpStatusCode.Moved:
return Tuple.Create(FeedReadResult.Moved, string.Empty);
}
if (httpRequestException.InnerException is not SocketException socketException)
return Tuple.Create(FeedReadResult.UnknownError, string.Empty);
return socketException.SocketErrorCode switch
{
SocketError.NoData => Tuple.Create(FeedReadResult.NoResponse, string.Empty),
SocketError.HostNotFound => Tuple.Create(FeedReadResult.NotFound, string.Empty),
TaskCanceledException => Tuple.Create(FeedReadResult.Timeout, string.Empty),
HttpRequestException httpRequestException => HandleHttpRequestException(httpRequestException),
_ => Tuple.Create(FeedReadResult.UnknownError, string.Empty)
};
}
catch (WebException webException)
{
var result = FeedReadResult.UnknownError;
switch (webException.Status)
{
case WebExceptionStatus.ConnectFailure:
case WebExceptionStatus.NameResolutionFailure:
result = FeedReadResult.ConnectionFailed;
break;
case WebExceptionStatus.Timeout:
result = FeedReadResult.Timeout;
break;
}
Log.Logger.Error(webException, "Exception");
if (result == FeedReadResult.UnknownError)
Debug.Print("Unknown error");
return Tuple.Create(result, string.Empty);
}
catch (Exception exception)
{
Log.Logger.Error(exception, "Exception");
@@ -373,6 +353,45 @@ public partial class Feed : RealmObject, INotifyDataErrorInfo
}
}
private static Tuple<FeedReadResult, string> HandleHttpRequestException(HttpRequestException httpRequestException)
{
switch (httpRequestException.StatusCode)
{
case HttpStatusCode.TooManyRequests:
return Tuple.Create(FeedReadResult.TooManyRequests, string.Empty);
case HttpStatusCode.ServiceUnavailable:
return Tuple.Create(FeedReadResult.TemporarilyUnavailable, string.Empty);
case HttpStatusCode.InternalServerError:
return Tuple.Create(FeedReadResult.ServerError, string.Empty);
case HttpStatusCode.NotModified:
return Tuple.Create(FeedReadResult.NotModified, string.Empty);
case HttpStatusCode.NotFound:
return Tuple.Create(FeedReadResult.NotFound, string.Empty);
case HttpStatusCode.Unauthorized:
case HttpStatusCode.Forbidden:
return Tuple.Create(FeedReadResult.Unauthorized, string.Empty);
case HttpStatusCode.Moved:
case HttpStatusCode.Redirect:
return Tuple.Create(FeedReadResult.Moved, string.Empty);
}
if (httpRequestException.InnerException is not SocketException socketException)
return Tuple.Create(FeedReadResult.UnknownError, string.Empty);
return socketException.SocketErrorCode switch
{
SocketError.NoData => Tuple.Create(FeedReadResult.NoResponse, string.Empty),
SocketError.HostNotFound => Tuple.Create(FeedReadResult.NotFound, string.Empty),
_ => Tuple.Create(FeedReadResult.UnknownError, string.Empty)
};
}
private FeedReadResult ReadFeed(bool forceRead)
{
try
@@ -392,12 +411,12 @@ public partial class Feed : RealmObject, INotifyDataErrorInfo
return FeedReadResult.NotDue;
}
// We're checking it now so update the time
LastChecked = DateTimeOffset.Now;
// Read the feed text
var retrieveResult = RetrieveFeed();
// We're checking it now so update the time
LastChecked = DateTimeOffset.Now;
// Get the information out of the async result
var result = retrieveResult.Item1;
var feedText = retrieveResult.Item2;

View File

@@ -1,25 +1,28 @@
using System;
using System.Text.RegularExpressions;
using FeedCenter.Options;
using FeedCenter.Options;
using Realms;
using System;
using System.Diagnostics;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
namespace FeedCenter;
public partial class FeedItem : RealmObject
{
[PrimaryKey]
public Guid Id { get; set; }
public bool BeenRead { get; set; }
public string Description { get; set; }
public Guid FeedId { get; set; }
public string Guid { get; set; }
[PrimaryKey]
public Guid Id { get; set; }
public DateTimeOffset LastFound { get; set; }
public string Link { get; set; }
public bool New { get; set; }
public int Sequence { get; set; }
public string Title { get; set; }
public string RemoteId { get; set; }
public static FeedItem Create()
{
@@ -42,7 +45,7 @@ public partial class FeedItem : RealmObject
case MultipleLineDisplay.FirstLine:
// Find the first newline
var newlineIndex = title.IndexOf("\n", StringComparison.Ordinal);
var newlineIndex = title.IndexOf('\n', StringComparison.Ordinal);
// If a newline was found return everything before it
if (newlineIndex > -1)
@@ -70,6 +73,34 @@ public partial class FeedItem : RealmObject
return title;
}
public async Task MarkAsRead(FeedCenterEntities entities)
{
var feed = entities.Feeds.FirstOrDefault(f => f.Id == FeedId);
entities.SaveChanges(() =>
{
BeenRead = true;
New = false;
});
if (feed == null || feed.Account.Type == AccountType.Local)
return;
switch (feed.Account.Type)
{
case AccountType.Fever:
// Delegate to the right reader based on the account type
await FeverReader.MarkFeedItemRead(feed.Account, RemoteId);
break;
case AccountType.Local:
break;
default:
Debug.Assert(false);
break;
}
}
[GeneratedRegex("\\n")]
private static partial Regex NewlineRegex();

View File

@@ -15,5 +15,6 @@ public enum FeedReadResult
ConnectionFailed,
ServerError,
Moved,
TemporarilyUnavailable
TemporarilyUnavailable,
TooManyRequests
}

View File

@@ -0,0 +1,145 @@
using System;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using ChrisKaczor.FeverClient;
namespace FeedCenter;
internal class FeverReader : IAccountReader
{
public int GetProgressSteps(FeedCenterEntities entities)
{
return 7;
}
public AccountReadResult Read(Account account, AccountReadInput accountReadInput)
{
var checkTime = DateTimeOffset.UtcNow;
var apiKey = account.Authenticate ? GetApiKey(account) : string.Empty;
var feverClient = new FeverClient(account.Url, apiKey);
accountReadInput.IncrementProgress();
var feverFeeds = feverClient.GetFeeds().Result.ToList();
accountReadInput.IncrementProgress();
var allFeverFeedItems = feverClient.GetAllFeedItems().Result.ToList();
accountReadInput.IncrementProgress();
var transaction = accountReadInput.Entities.BeginTransaction();
foreach (var feverFeed in feverFeeds)
{
var feed = accountReadInput.Entities.Feeds.FirstOrDefault(f => f.RemoteId == feverFeed.Id.ToString() && f.Account.Id == account.Id);
if (feed == null)
{
feed = new Feed
{
Id = Guid.NewGuid(),
RemoteId = feverFeed.Id.ToString(),
Title = feverFeed.Title,
Source = feverFeed.Url,
Link = feverFeed.SiteUrl,
Account = account,
Name = feverFeed.Title,
CategoryId = accountReadInput.Entities.DefaultCategory.Id,
Enabled = true,
CheckInterval = 0,
};
accountReadInput.Entities.Feeds.Add(feed);
}
feed.Name = feverFeed.Title;
feed.Title = feverFeed.Title;
feed.Link = feverFeed.SiteUrl;
feed.Source = feverFeed.Url;
feed.LastReadResult = FeedReadResult.Success;
feed.LastChecked = checkTime;
accountReadInput.IncrementProgress();
var feverFeedItems = allFeverFeedItems
.Where(f => f.FeedId == feverFeed.Id)
.OrderByDescending(fi => fi.CreatedOnTime).ToList();
var sequence = 1;
foreach (var feverFeedItem in feverFeedItems)
{
var feedItem = feed.Items.FirstOrDefault(f => f.RemoteId == feverFeedItem.Id.ToString());
if (feedItem == null)
{
feedItem = new FeedItem
{
Id = Guid.NewGuid(),
RemoteId = feverFeedItem.Id.ToString(),
Title = feverFeedItem.Title,
Link = feverFeedItem.Url,
Description = feverFeedItem.Html,
BeenRead = feverFeedItem.IsRead,
FeedId = feed.Id,
Guid = Guid.NewGuid().ToString(),
Sequence = sequence++,
};
feed.Items.Add(feedItem);
}
feedItem.LastFound = checkTime;
feedItem.BeenRead = feverFeedItem.IsRead;
feedItem.Sequence = sequence++;
}
accountReadInput.IncrementProgress();
var feedItemsNotSeen = feed.Items.Where(fi => fi.LastFound != checkTime).ToList();
foreach (var feedItemNotSeen in feedItemsNotSeen)
{
feed.Items.Remove(feedItemNotSeen);
}
}
accountReadInput.IncrementProgress();
var feedsNotSeen = accountReadInput.Entities.Feeds.Where(f => f.Account.Id == account.Id && f.LastChecked != checkTime).ToList();
foreach (var feedNotSeen in feedsNotSeen)
{
accountReadInput.Entities.Feeds.Remove(feedNotSeen);
}
account.LastChecked = checkTime;
transaction.Commit();
accountReadInput.IncrementProgress();
return AccountReadResult.Success;
}
public static async Task MarkFeedItemRead(Account account, string feedItemId)
{
var apiKey = account.Authenticate ? GetApiKey(account) : string.Empty;
var feverClient = new FeverClient(account.Url, apiKey);
await feverClient.MarkFeedItemAsRead(int.Parse(feedItemId));
}
private static string GetApiKey(Account account)
{
var input = $"{account.Username}:{account.Password}";
var hash = MD5.HashData(Encoding.UTF8.GetBytes(input));
return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
}
}

View File

@@ -0,0 +1,144 @@
using System;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
namespace FeedCenter;
internal class GoogleReaderReader : IAccountReader
{
public int GetProgressSteps(FeedCenterEntities entities)
{
return 7;
}
public AccountReadResult Read(Account account, AccountReadInput accountReadInput)
{
var checkTime = DateTimeOffset.UtcNow;
//var apiKey = account.Authenticate ? GetApiKey(account) : string.Empty;
//var feverClient = new FeverClient.FeverClient(account.Url, apiKey);
//accountReadInput.IncrementProgress();
//var feverFeeds = feverClient.GetFeeds().Result.ToList();
//accountReadInput.IncrementProgress();
//var allFeverFeedItems = feverClient.GetAllFeedItems().Result.ToList();
//accountReadInput.IncrementProgress();
var transaction = accountReadInput.Entities.BeginTransaction();
//foreach (var feverFeed in feverFeeds)
//{
// var feed = accountReadInput.Entities.Feeds.FirstOrDefault(f => f.RemoteId == feverFeed.Id.ToString() && f.Account.Id == account.Id);
// if (feed == null)
// {
// feed = new Feed
// {
// Id = Guid.NewGuid(),
// RemoteId = feverFeed.Id.ToString(),
// Title = feverFeed.Title,
// Source = feverFeed.Url,
// Link = feverFeed.SiteUrl,
// Account = account,
// Name = feverFeed.Title,
// CategoryId = accountReadInput.Entities.DefaultCategory.Id,
// Enabled = true,
// CheckInterval = 0,
// };
// accountReadInput.Entities.Feeds.Add(feed);
// }
// feed.Name = feverFeed.Title;
// feed.Title = feverFeed.Title;
// feed.Link = feverFeed.SiteUrl;
// feed.Source = feverFeed.Url;
// feed.LastReadResult = FeedReadResult.Success;
// feed.LastChecked = checkTime;
// accountReadInput.IncrementProgress();
// var feverFeedItems = allFeverFeedItems
// .Where(f => f.FeedId == feverFeed.Id)
// .OrderByDescending(fi => fi.CreatedOnTime).ToList();
// var sequence = 1;
// foreach (var feverFeedItem in feverFeedItems)
// {
// var feedItem = feed.Items.FirstOrDefault(f => f.RemoteId == feverFeedItem.Id.ToString());
// if (feedItem == null)
// {
// feedItem = new FeedItem
// {
// Id = Guid.NewGuid(),
// RemoteId = feverFeedItem.Id.ToString(),
// Title = feverFeedItem.Title,
// Link = feverFeedItem.Url,
// Description = feverFeedItem.Html,
// BeenRead = feverFeedItem.IsRead,
// FeedId = feed.Id,
// Guid = Guid.NewGuid().ToString(),
// Sequence = sequence++,
// };
// feed.Items.Add(feedItem);
// }
// feedItem.LastFound = checkTime;
// feedItem.BeenRead = feverFeedItem.IsRead;
// feedItem.Sequence = sequence++;
// }
// accountReadInput.IncrementProgress();
// var feedItemsNotSeen = feed.Items.Where(fi => fi.LastFound != checkTime).ToList();
// foreach (var feedItemNotSeen in feedItemsNotSeen)
// {
// feed.Items.Remove(feedItemNotSeen);
// }
//}
accountReadInput.IncrementProgress();
//var feedsNotSeen = accountReadInput.Entities.Feeds.Where(f => f.Account.Id == account.Id && f.LastChecked != checkTime).ToList();
//foreach (var feedNotSeen in feedsNotSeen)
//{
// accountReadInput.Entities.Feeds.Remove(feedNotSeen);
//}
account.LastChecked = checkTime;
transaction.Commit();
accountReadInput.IncrementProgress();
return AccountReadResult.Success;
}
public static async Task MarkFeedItemRead(Account account, string feedItemId)
{
//var apiKey = account.Authenticate ? GetApiKey(account) : string.Empty;
//var feverClient = new FeverClient.FeverClient(account.Url, apiKey);
//await feverClient.MarkFeedItemAsRead(int.Parse(feedItemId));
}
//private static string GetApiKey(Account account)
//{
// var input = $"{account.Username}:{account.Password}";
// var hash = MD5.HashData(Encoding.UTF8.GetBytes(input));
// return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
//}
}

View File

@@ -0,0 +1,7 @@
namespace FeedCenter;
public interface IAccountReader
{
public int GetProgressSteps(FeedCenterEntities entities);
public AccountReadResult Read(Account account, AccountReadInput accountReadInput);
}

View File

@@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace FeedCenter;
public class LocalReader : IAccountReader
{
public int GetProgressSteps(FeedCenterEntities entities)
{
var enabledFeedCount = entities.Feeds.Count(f => f.Account.Type == AccountType.Local && f.Enabled);
return enabledFeedCount;
}
public AccountReadResult Read(Account account, AccountReadInput accountReadInput)
{
var checkTime = DateTimeOffset.UtcNow;
// Create the list of feeds to read
var feedsToRead = new List<Feed>();
// If we have a single feed then add it to the list - otherwise add them all
if (accountReadInput.FeedId != null)
feedsToRead.Add(accountReadInput.Entities.Feeds.First(feed => feed.Id == accountReadInput.FeedId));
else
feedsToRead.AddRange(accountReadInput.Entities.Feeds.Where(f => f.Account.Type == AccountType.Local));
// Loop over each feed and read it
foreach (var feed in feedsToRead)
{
// Read the feed
accountReadInput.Entities.SaveChanges(() => feed.Read(accountReadInput.ForceRead));
accountReadInput.IncrementProgress();
}
accountReadInput.Entities.SaveChanges(() => account.LastChecked = checkTime);
return AccountReadResult.Success;
}
}

View File

@@ -5,13 +5,7 @@
<xs:complexType>
<xs:all>
<xs:element name="Realm" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:attribute name="DisableAnalytics" type="xs:boolean">
<xs:annotation>
<xs:documentation>Disables anonymized usage information from being sent on build. Read more about what data is being collected and why here: https://github.com/realm/realm-dotnet/blob/main/Realm/Realm.Weaver/Analytics.cs</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
<xs:complexType></xs:complexType>
</xs:element>
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">

View File

@@ -21,9 +21,8 @@ public partial class MainWindow
private void HandleNewFeed(string feedUrl)
{
// Create and configure the new feed
var feed = Feed.Create();
var feed = Feed.Create(_database);
feed.Source = feedUrl;
feed.CategoryId = _database.DefaultCategory.Id;
// Try to detect the feed type
var feedTypeResult = feed.DetectFeedType();
@@ -31,7 +30,7 @@ public partial class MainWindow
// If we can't figure it out it could be an HTML page
if (feedTypeResult.Item1 == FeedType.Unknown)
{
// Only check if the feed was able to be read - otherwise fall through and show the dialog
// Only check if the feed was read - otherwise fall through and show the dialog
if (feedTypeResult.Item2.Length > 0)
{
// Create and load an HTML document with the text
@@ -87,19 +86,11 @@ public partial class MainWindow
// Show a tip
NotificationIcon.ShowBalloonTip(string.Format(Properties.Resources.FeedAddedNotification, feed.Name), H.NotifyIcon.Core.NotificationIcon.Info);
_currentFeed = feed;
// Refresh the database to current settings
ResetDatabase();
// Re-initialize the feed display
DisplayFeed();
}
else
{
// Feed read failed - create a new feed window
var feedForm = new FeedWindow();
var feedForm = new FeedWindow(_database);
var dialogResult = feedForm.Display(feed, this);
@@ -109,14 +100,14 @@ public partial class MainWindow
// Add the feed to the feed table
_database.SaveChanges(() => _database.Feeds.Add(feed));
_currentFeed = feed;
// Refresh the database to current settings
ResetDatabase();
// Re-initialize the feed display
DisplayFeed();
}
_currentFeed = feed;
// Refresh the database to current settings
ResetDatabase();
// Re-initialize the feed display
DisplayFeed();
}
}

View File

@@ -2,6 +2,7 @@
using FeedCenter.Properties;
using System;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
@@ -30,7 +31,7 @@ public partial class MainWindow
}
}
private void HandleItemMouseUp(object sender, MouseButtonEventArgs e)
private async void HandleItemMouseUp(object sender, MouseButtonEventArgs e)
{
// Only handle the middle button
if (e.ChangedButton != MouseButton.Middle)
@@ -39,18 +40,14 @@ public partial class MainWindow
// Get the feed item
var feedItem = (FeedItem) ((ListBoxItem) sender).DataContext;
// The feed item has been read and is no longer new
_database.SaveChanges(() =>
{
feedItem.BeenRead = true;
feedItem.New = false;
});
// Remove the item from the list
LinkTextList.Items.Remove(feedItem);
// The feed item has been read and is no longer new
await feedItem.MarkAsRead(_database);
}
private void HandleItemMouseDoubleClick(object sender, MouseButtonEventArgs e)
private async void HandleItemMouseDoubleClick(object sender, MouseButtonEventArgs e)
{
// Get the feed item
var feedItem = (FeedItem) ((ListBoxItem) sender).DataContext;
@@ -59,15 +56,11 @@ public partial class MainWindow
if (!InstalledBrowser.OpenLink(Settings.Default.Browser, feedItem.Link))
return;
// The feed item has been read and is no longer new
_database.SaveChanges(() =>
{
feedItem.BeenRead = true;
feedItem.New = false;
});
// Remove the item from the list
LinkTextList.Items.Remove(feedItem);
// The feed item has been read and is no longer new
await feedItem.MarkAsRead(_database);
}
private void HandleFeedButtonClick(object sender, RoutedEventArgs e)

View File

@@ -34,13 +34,13 @@ public partial class MainWindow
}
}
private void SetProgressMode(bool value, int feedCount)
private void SetProgressMode(bool showProgress, int maximum)
{
// Refresh the progress bar if we need it
if (value)
if (showProgress)
{
FeedReadProgress.Value = 0;
FeedReadProgress.Maximum = feedCount + 2;
FeedReadProgress.Maximum = maximum + 2;
FeedReadProgress.Visibility = Visibility.Visible;
}
else
@@ -76,11 +76,13 @@ public partial class MainWindow
return;
// Don't read if there is nothing to read
if (!_database.Feeds.Any())
if (!_database.Accounts.Any())
return;
var progressSteps = _database.Accounts.Sum(a => a.GetProgressSteps(_database));
// Switch to progress mode
SetProgressMode(true, _database.Feeds.Count);
SetProgressMode(true, progressSteps);
// Create the input class
var workerInput = new FeedReadWorkerInput(forceRead);
@@ -138,36 +140,69 @@ public partial class MainWindow
var database = new FeedCenterEntities();
// Get the worker
var worker = (BackgroundWorker) sender;
var worker = (BackgroundWorker)sender;
// Get the input information
var workerInput = (FeedReadWorkerInput) e.Argument ?? new FeedReadWorkerInput();
var workerInput = (FeedReadWorkerInput)e.Argument ?? new FeedReadWorkerInput();
// Setup for progress
var currentProgress = 0;
// Create the list of feeds to read
var feedsToRead = new List<Feed>();
var accountsToRead = new List<Account>();
// If we have a single feed then add it to the list - otherwise add them all
// If we have a single feed then get the account for that feed
if (workerInput.FeedId != null)
feedsToRead.Add(database.Feeds.First(feed => feed.Id == workerInput.FeedId));
else
feedsToRead.AddRange(database.Feeds);
// Loop over each feed and read it
foreach (var feed in feedsToRead)
{
// Read the feed
database.SaveChanges(() => feed.Read(workerInput.ForceRead));
var feed = database.Feeds.FirstOrDefault(f => f.Id == workerInput.FeedId);
if (feed != null)
accountsToRead.Add(feed.Account);
}
else
{
// Otherwise get all accounts
accountsToRead.AddRange(database.Accounts);
}
var incrementProgress = () =>
{
// Increment progress
currentProgress += 1;
// Report progress
worker.ReportProgress(currentProgress);
};
// Loop over each account and read it
foreach (var account in accountsToRead)
{
var accountReadInput = new AccountReadInput(database, workerInput.FeedId, workerInput.ForceRead, incrementProgress);
account.Read(accountReadInput);
}
//// Create the list of feeds to read
//var feedsToRead = new List<Feed>();
//// If we have a single feed then add it to the list - otherwise add them all
//if (workerInput.FeedId != null)
// feedsToRead.Add(database.Feeds.First(feed => feed.Id == workerInput.FeedId));
//else
// feedsToRead.AddRange(database.Feeds);
//// Loop over each feed and read it
//foreach (var feed in feedsToRead)
//{
// // Read the feed
// database.SaveChanges(() => feed.Read(workerInput.ForceRead));
// // Increment progress
// currentProgress += 1;
// // Report progress
// worker.ReportProgress(currentProgress);
//}
// Increment progress
currentProgress += 1;
@@ -178,7 +213,7 @@ public partial class MainWindow
if (DateTime.Now - Settings.Default.LastVersionCheck >= Settings.Default.VersionCheckInterval)
{
// Get the update information
UpdateCheck.CheckForUpdate().Wait();
UpdateCheck.CheckForUpdate(Settings.Default.IncludePrerelease).Wait();
// Update the last check time
Settings.Default.LastVersionCheck = DateTime.Now;

View File

@@ -285,11 +285,13 @@
<MenuItem Header="{x:Static properties:Resources.lockWindowCheckBox}"
IsCheckable="True"
IsChecked="{Binding Source={x:Static properties:Settings.Default}, Path=WindowLocked}" />
<Separator />
<MenuItem Header="{x:Static properties:Resources.CurrentFeed}">
<MenuItem Header="{x:Static properties:Resources.EditMenu}"
<Separator Name="SettingsMenuSeparator" />
<MenuItem Name="CurrentFeedMenu" Header="{x:Static properties:Resources.CurrentFeed}">
<MenuItem Name="EditCurrentFeedMenuItem"
Header="{x:Static properties:Resources.EditMenu}"
Click="HandleEditCurrentFeedMenuItemClick" />
<MenuItem Header="{x:Static properties:Resources.DeleteMenu}"
<MenuItem Name="DeleteCurrentFeedMenuItem"
Header="{x:Static properties:Resources.DeleteMenu}"
Click="HandleDeleteCurrentFeedMenuItemClick" />
</MenuItem>
</ContextMenu>

View File

@@ -1,12 +1,12 @@
using ChrisKaczor.ApplicationUpdate;
using ChrisKaczor.Wpf.Application;
using FeedCenter.Data;
using FeedCenter.Properties;
using Serilog;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
@@ -39,19 +39,19 @@ public partial class MainWindow : IDisposable
base.OnSourceInitialized(e);
// Initialize the window
Initialize();
Initialize().ContinueWith(_ => { }, TaskScheduler.FromCurrentSynchronizationContext());
}
protected override async void OnClosed(EventArgs e)
protected override void OnClosed(EventArgs e)
{
base.OnClosed(e);
await SingleInstance.Stop();
SingleInstance.Stop().ContinueWith(_ => { }, TaskScheduler.FromCurrentSynchronizationContext());
}
public async void Initialize()
public async Task Initialize()
{
// Setup the update handler
// Set up the update handler
InitializeUpdate();
// Show the notification icon
@@ -72,8 +72,8 @@ public partial class MainWindow : IDisposable
_feedReadWorker.ProgressChanged += HandleFeedReadWorkerProgressChanged;
_feedReadWorker.RunWorkerCompleted += HandleFeedReadWorkerCompleted;
// Setup the database
_database = Database.Entities;
// Set up the database
_database = new FeedCenterEntities();
// Initialize the single instance listener
SingleInstance.MessageReceived += SingleInstance_MessageReceived;
@@ -90,7 +90,7 @@ public partial class MainWindow : IDisposable
// Check for update
if (Settings.Default.CheckVersionAtStartup)
await UpdateCheck.CheckForUpdate();
await UpdateCheck.CheckForUpdate(Settings.Default.IncludePrerelease);
// Show the link if updates are available
if (UpdateCheck.UpdateAvailable)
@@ -151,7 +151,7 @@ public partial class MainWindow : IDisposable
var currentId = _currentFeed?.IsValid ?? false ? _currentFeed.Id : Guid.Empty;
// Create a new database object
_database.Refresh();
_database = new FeedCenterEntities();
_feedList = _currentCategory == null
? _database.Feeds.ToList()
@@ -188,7 +188,9 @@ public partial class MainWindow : IDisposable
private void UpdateToolbarButtonState()
{
// Cache the feed count to save (a little) time
var feedCount = Settings.Default.DisplayEmptyFeeds ? _feedList.Count() : _feedList.Count(x => x.Items.Any(y => !y.BeenRead));
var feedCount = Settings.Default.DisplayEmptyFeeds
? _feedList.Count()
: _feedList.Count(x => x.Items.Any(y => !y.BeenRead));
// Set button states
PreviousToolbarButton.IsEnabled = feedCount > 1;
@@ -200,6 +202,11 @@ public partial class MainWindow : IDisposable
FeedLabel.Visibility = feedCount == 0 ? Visibility.Hidden : Visibility.Visible;
FeedButton.Visibility = feedCount == 0 ? Visibility.Hidden : Visibility.Visible;
CategoryGrid.Visibility = _database.Categories.Count > 1 ? Visibility.Visible : Visibility.Collapsed;
EditCurrentFeedMenuItem.Visibility = _currentFeed?.Account.SupportsFeedEdit ?? false ? Visibility.Visible : Visibility.Collapsed;
DeleteCurrentFeedMenuItem.Visibility = _currentFeed?.Account.SupportsFeedDelete ?? false ? Visibility.Visible : Visibility.Collapsed;
CurrentFeedMenu.Visibility = EditCurrentFeedMenuItem.IsVisible || DeleteCurrentFeedMenuItem.IsVisible ? Visibility.Visible : Visibility.Collapsed;
SettingsMenuSeparator.Visibility = CurrentFeedMenu.Visibility;
}
private void InitializeDisplay()
@@ -405,16 +412,14 @@ public partial class MainWindow : IDisposable
}
UpdateOpenAllButton();
UpdateToolbarButtonState();
}
private void MarkAllItemsAsRead()
private async Task MarkAllItemsAsRead()
{
// Loop over all items and mark them as read
_database.SaveChanges(() =>
{
foreach (FeedItem feedItem in LinkTextList.Items)
feedItem.BeenRead = true;
});
foreach (FeedItem feedItem in LinkTextList.Items)
await feedItem.MarkAsRead(_database);
// Clear the list
LinkTextList.Items.Clear();

View File

@@ -24,17 +24,18 @@ public partial class MainWindow
{
StopTimer();
_mainTimer.Dispose();
_mainTimer?.Dispose();
_mainTimer = null;
}
private void StartTimer()
{
_mainTimer.Start();
_mainTimer?.Start();
}
private void StopTimer()
{
_mainTimer.Stop();
_mainTimer?.Stop();
}
private void HandleMainTimerElapsed(object sender, EventArgs e)

View File

@@ -1,12 +1,16 @@
using System.IO;
using ChrisKaczor.InstalledBrowsers;
using FeedCenter.Options;
using FeedCenter.Properties;
using System;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Web.UI;
using System.Windows;
using System.Windows.Controls;
using ChrisKaczor.InstalledBrowsers;
using FeedCenter.Options;
using FeedCenter.Properties;
using Serilog;
using Serilog.Events;
namespace FeedCenter;
@@ -22,7 +26,7 @@ public partial class MainWindow
NextFeed();
}
private void OpenAllFeedItemsIndividually()
private async Task OpenAllFeedItemsIndividually()
{
// Create a new list of feed items
var feedItems = (from FeedItem feedItem in LinkTextList.Items select feedItem).ToList();
@@ -40,7 +44,7 @@ public partial class MainWindow
if (InstalledBrowser.OpenLink(Settings.Default.Browser, feedItem.Link))
{
// Mark the feed as read
_database.SaveChanges(() => feedItem.BeenRead = true);
await feedItem.MarkAsRead(_database);
// Remove the item
LinkTextList.Items.Remove(feedItem);
@@ -71,9 +75,21 @@ public partial class MainWindow
UpdateErrorLink();
}
private void HandleMarkReadToolbarButtonClick(object sender, RoutedEventArgs e)
private static void HandleException(Exception exception)
{
MarkAllItemsAsRead();
Log.Logger.Write(LogEventLevel.Debug, exception, "");
}
private async void HandleMarkReadToolbarButtonClick(object sender, RoutedEventArgs e)
{
try
{
await MarkAllItemsAsRead();
}
catch (Exception exception)
{
HandleException(exception);
}
}
private void HandleShowErrorsButtonClick(object sender, RoutedEventArgs e)
@@ -108,35 +124,49 @@ public partial class MainWindow
ReadFeeds(true);
}
private void HandleOpenAllMenuItemClick(object sender, RoutedEventArgs e)
private async void HandleOpenAllMenuItemClick(object sender, RoutedEventArgs e)
{
var menuItem = (MenuItem) e.Source;
try
{
var menuItem = (MenuItem) e.Source;
if (Equals(menuItem, MenuOpenAllSinglePage))
OpenAllFeedItemsOnSinglePage();
else if (Equals(menuItem, MenuOpenAllMultiplePages))
OpenAllFeedItemsIndividually();
if (Equals(menuItem, MenuOpenAllSinglePage))
await OpenAllFeedItemsOnSinglePage();
else if (Equals(menuItem, MenuOpenAllMultiplePages))
await OpenAllFeedItemsIndividually();
}
catch (Exception exception)
{
HandleException(exception);
}
}
private void HandleOpenAllToolbarButtonClick(object sender, RoutedEventArgs e)
private async void HandleOpenAllToolbarButtonClick(object sender, RoutedEventArgs e)
{
var multipleOpenAction = _currentFeed.MultipleOpenAction;
switch (multipleOpenAction)
try
{
case MultipleOpenAction.IndividualPages:
OpenAllFeedItemsIndividually();
break;
case MultipleOpenAction.SinglePage:
OpenAllFeedItemsOnSinglePage();
break;
var multipleOpenAction = _currentFeed.MultipleOpenAction;
switch (multipleOpenAction)
{
case MultipleOpenAction.IndividualPages:
await OpenAllFeedItemsIndividually();
break;
case MultipleOpenAction.SinglePage:
await OpenAllFeedItemsOnSinglePage();
break;
}
}
catch (Exception exception)
{
HandleException(exception);
}
}
private void HandleEditCurrentFeedMenuItemClick(object sender, RoutedEventArgs e)
{
// Create a new feed window
var feedWindow = new FeedWindow();
var feedWindow = new FeedWindow(_database);
// Display the feed window and get the result
var result = feedWindow.Display(_currentFeed, this);
@@ -166,21 +196,27 @@ public partial class MainWindow
// Delete the feed
_database.SaveChanges(() => _database.Feeds.Remove(feedToDelete));
// Refresh the database to current settings
ResetDatabase();
// Re-initialize the feed display
DisplayFeed();
}
private void OpenAllFeedItemsOnSinglePage()
private async Task OpenAllFeedItemsOnSinglePage()
{
var fileName = Path.GetTempFileName() + ".html";
TextWriter textWriter = new StreamWriter(fileName);
using (var htmlTextWriter = new HtmlTextWriter(textWriter))
await using (var htmlTextWriter = new HtmlTextWriter(textWriter))
{
htmlTextWriter.RenderBeginTag(HtmlTextWriterTag.Html);
htmlTextWriter.RenderBeginTag(HtmlTextWriterTag.Head);
htmlTextWriter.RenderBeginTag(HtmlTextWriterTag.Title);
htmlTextWriter.Write(_currentFeed.Title);
await htmlTextWriter.WriteAsync(_currentFeed.Title);
htmlTextWriter.RenderEndTag();
htmlTextWriter.AddAttribute("http-equiv", "Content-Type");
@@ -208,13 +244,13 @@ public partial class MainWindow
htmlTextWriter.AddAttribute(HtmlTextWriterAttribute.Href, item.Link);
htmlTextWriter.RenderBeginTag(HtmlTextWriterTag.A);
htmlTextWriter.Write(item.Title.Length == 0 ? item.Link : item.Title);
await htmlTextWriter.WriteAsync(item.Title.Length == 0 ? item.Link : item.Title);
htmlTextWriter.RenderEndTag();
htmlTextWriter.RenderBeginTag(HtmlTextWriterTag.Br);
htmlTextWriter.RenderEndTag();
htmlTextWriter.Write(item.Description);
await htmlTextWriter.WriteAsync(item.Description);
htmlTextWriter.RenderEndTag();
@@ -225,11 +261,11 @@ public partial class MainWindow
htmlTextWriter.RenderEndTag();
}
textWriter.Flush();
await textWriter.FlushAsync();
textWriter.Close();
InstalledBrowser.OpenLink(Settings.Default.Browser, fileName);
MarkAllItemsAsRead();
await MarkAllItemsAsRead();
}
}

View File

@@ -34,6 +34,6 @@ public partial class MainWindow
private void HandleNewVersionLinkClick(object sender, RoutedEventArgs e)
{
UpdateCheck.DisplayUpdateInformation(true);
UpdateCheck.DisplayUpdateInformation(true, Settings.Default.IncludePrerelease);
}
}

View File

@@ -49,14 +49,14 @@ public partial class MainWindow
{
// Set the last window location
Settings.Default.WindowLocation = new Point(Left, Top);
Settings.Default.Save();
// Set the last window size
Settings.Default.WindowSize = new Size(Width, Height);
Settings.Default.Save();
// Save the dock on the navigation tray
Settings.Default.ToolbarLocation = NameBasedGrid.NameBasedGrid.GetRow(NavigationToolbarTray) == "TopToolbarRow" ? Dock.Top : Dock.Bottom;
// Save settings
Settings.Default.Save();
}
@@ -96,7 +96,7 @@ public partial class MainWindow
NotificationIcon.Dispose();
}
private readonly DebounceDispatcher _updateWindowSettingsDispatcher = new(500);
private readonly DebounceDispatcher _updateWindowSettingsDispatcher = new(TimeSpan.FromMilliseconds(500));
private void HandleWindowSizeChanged(object sender, SizeChangedEventArgs e)
{

View File

@@ -4,7 +4,7 @@ namespace FeedCenter.Options;
public partial class AboutOptionsPanel
{
public AboutOptionsPanel(Window parentWindow) : base(parentWindow)
public AboutOptionsPanel(Window parentWindow, FeedCenterEntities entities) : base(parentWindow, entities)
{
InitializeComponent();
}

View File

@@ -0,0 +1,24 @@
using System.Collections.Generic;
namespace FeedCenter.Options
{
public class AccountTypeItem
{
public AccountType AccountType { get; set; }
public string Name { get; set; }
public static List<AccountTypeItem> AccountTypes =>
[
new()
{
Name = Properties.Resources.AccountTypeFever,
AccountType = AccountType.Fever
},
//new()
//{
// Name = Properties.Resources.AccountTypeGoogleReader,
// AccountType = AccountType.GoogleReader
//}
];
}
}

View File

@@ -0,0 +1,22 @@
using System;
using System.Globalization;
using System.Linq;
using System.Windows.Data;
namespace FeedCenter.Options;
public class AccountTypeToNameConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
if (value is AccountType accountType)
return AccountTypeItem.AccountTypes.First(at => at.AccountType == accountType).Name;
return value;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}

View File

@@ -0,0 +1,139 @@
<Window x:Class="FeedCenter.Options.AccountWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:properties="clr-namespace:FeedCenter.Properties"
xmlns:feedCenter="clr-namespace:FeedCenter"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
d:DataContext="{d:DesignInstance Type=feedCenter:Account}"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mah="http://metro.mahapps.com/winfx/xaml/controls"
xmlns:options="clr-namespace:FeedCenter.Options"
mc:Ignorable="d"
Title="AccountWindow"
Height="350"
Width="450"
WindowStartupLocation="CenterOwner"
Icon="/FeedCenter;component/Resources/Application.ico"
FocusManager.FocusedElement="{Binding ElementName=NameTextBox}">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Controls.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Fonts.xaml" />
<ResourceDictionary
Source="pack://application:,,,/MahApps.Metro;component/Styles/Controls.FlatButton.xaml" />
<ResourceDictionary
Source="pack://application:,,,/MahApps.Metro;component/Styles/Themes/light.cobalt.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Grid Margin="6">
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TabControl Name="OptionsTabControl"
Grid.Row="0"
Grid.Column="0"
mah:HeaderedControlHelper.HeaderFontSize="16"
mah:TabControlHelper.Underlined="SelectedTabItem">
<TabItem Header="{x:Static properties:Resources.generalTab}">
<StackPanel Margin="0,4"
options:Spacing.Vertical="8">
<ComboBox Name="AccountTypeComboBox"
mah:TextBoxHelper.UseFloatingWatermark="True"
mah:TextBoxHelper.Watermark="{x:Static properties:Resources.accountTypeLabel}"
DisplayMemberPath="Name"
ItemsSource="{Binding Source={x:Static options:AccountTypeItem.AccountTypes}}"
SelectedValuePath="AccountType"
SelectedValue="{Binding Path=Type, UpdateSourceTrigger=Explicit, ValidatesOnExceptions=true}" />
<TextBox Name="NameTextBox"
mah:TextBoxHelper.UseFloatingWatermark="True"
mah:TextBoxHelper.Watermark="{x:Static properties:Resources.accountNameLabel}"
mah:TextBoxHelper.SelectAllOnFocus="True"
Text="{Binding Path=Name, UpdateSourceTrigger=Explicit, ValidatesOnExceptions=True}" />
<TextBox Name="UrlTextBox"
mah:TextBoxHelper.UseFloatingWatermark="True"
mah:TextBoxHelper.Watermark="{x:Static properties:Resources.accountUrlLabel}"
mah:TextBoxHelper.SelectAllOnFocus="True"
Text="{Binding Path=Url, UpdateSourceTrigger=Explicit, ValidatesOnExceptions=True}" />
<CheckBox Name="ReadIntervalCheckBox"
VerticalContentAlignment="Center"
IsChecked="{Binding Path=Enabled, UpdateSourceTrigger=Explicit, ValidatesOnExceptions=True}">
<StackPanel Orientation="Horizontal">
<Label Content="{x:Static properties:Resources.accountReadIntervalPrefix}"
HorizontalAlignment="Left"
Margin="0,0,5,0"
VerticalAlignment="Center"
Padding="0" />
<mah:NumericUpDown Width="100"
Maximum="10080"
Minimum="1"
IsEnabled="{Binding ElementName=ReadIntervalCheckBox, Path=IsChecked}"
Value="{Binding CheckInterval, UpdateSourceTrigger=Explicit, ValidatesOnExceptions=True}" />
<Label Content="{x:Static properties:Resources.accountReadIntervalSuffix}"
HorizontalAlignment="Left"
Margin="5,0,0,0"
VerticalAlignment="Center"
Padding="0" />
</StackPanel>
</CheckBox>
</StackPanel>
</TabItem>
<TabItem Header="{x:Static properties:Resources.authenticationTab}">
<StackPanel Margin="0,4">
<CheckBox Content="{x:Static properties:Resources.accountRequiresAuthenticationCheckBox}"
Margin="0,0,0,4"
Name="RequiresAuthenticationCheckBox"
IsChecked="{Binding Path=Authenticate, UpdateSourceTrigger=Explicit, ValidatesOnExceptions=True}" />
<TextBox Name="AuthenticationUserNameTextBox"
Margin="25,0,0,4"
IsEnabled="{Binding ElementName=RequiresAuthenticationCheckBox, Path=IsChecked}"
mah:TextBoxHelper.UseFloatingWatermark="True"
mah:TextBoxHelper.Watermark="{x:Static properties:Resources.authenticationUserNameLabel}"
Text="{Binding Path=Username, UpdateSourceTrigger=Explicit, ValidatesOnExceptions=True}" />
<PasswordBox Name="AuthenticationPasswordTextBox"
Margin="25,0,0,8"
Style="{StaticResource MahApps.Styles.PasswordBox.Button.Revealed}"
mah:PasswordBoxBindingBehavior.Password="{Binding Password, UpdateSourceTrigger=Explicit, ValidatesOnDataErrors=True}"
mah:TextBoxHelper.UseFloatingWatermark="True"
mah:TextBoxHelper.Watermark="{x:Static properties:Resources.authenticationPasswordLabel}"
IsEnabled="{Binding ElementName=RequiresAuthenticationCheckBox, Path=IsChecked}" />
</StackPanel>
</TabItem>
</TabControl>
<Grid Name="AccountReadProgress"
Grid.Row="1"
Height="20"
Visibility="Collapsed">
<ProgressBar Name="AccountReadProgressBar" />
<TextBlock Text="Loading account..."
HorizontalAlignment="Center"
VerticalAlignment="Center" />
</Grid>
<StackPanel
Name="ButtonPanel"
Grid.Column="0"
Grid.Row="1"
Orientation="Horizontal"
Visibility="Visible"
Margin="0,5,0,0"
HorizontalAlignment="Right">
<Button Content="{x:Static properties:Resources.OkayButton}"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Width="75"
Margin="0,0,5,0"
IsDefault="True"
Click="HandleOkayButtonClick" />
<Button Content="{x:Static properties:Resources.CancelButton}"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Width="75"
IsCancel="True" />
</StackPanel>
</Grid>
</Window>

View File

@@ -0,0 +1,78 @@
using ChrisKaczor.Wpf.Validation;
using System.Linq;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Threading;
namespace FeedCenter.Options;
public partial class AccountWindow
{
private Account _account;
private bool _isNew;
private readonly FeedCenterEntities _entities;
public AccountWindow(FeedCenterEntities entities)
{
_entities = entities;
InitializeComponent();
}
public bool? Display(Account account, Window owner, bool isNew)
{
_account = account;
_isNew = isNew;
DataContext = account;
Title = isNew ? Properties.Resources.AccountWindowAdd : Properties.Resources.AccountWindowEdit;
Owner = owner;
return ShowDialog();
}
private void HandleOkayButtonClick(object sender, RoutedEventArgs e)
{
var transaction = _entities.BeginTransaction();
if (!this.IsValid(OptionsTabControl))
{
transaction.Rollback();
return;
}
if (_isNew)
{
_entities.Accounts.Add(_account);
}
transaction.Commit();
var accountId = _account.Id;
AccountReadProgressBar.Value = 0;
AccountReadProgressBar.Maximum = _account.GetProgressSteps(_entities) + 1;
AccountReadProgress.Visibility = Visibility.Visible;
ButtonPanel.Visibility = Visibility.Collapsed;
var dispatcher = Dispatcher.CurrentDispatcher;
Task.Run(() =>
{
var entities = new FeedCenterEntities();
var account = entities.Accounts.First(a => a.Id == accountId);
var accountReadInput = new AccountReadInput(entities, null, true, () => dispatcher.Invoke(() => AccountReadProgressBar.Value++));
account.Read(accountReadInput);
dispatcher.Invoke(() =>
{
DialogResult = true;
Close();
});
});
}
}

View File

@@ -0,0 +1,98 @@
<options:OptionsPanelBase x:Class="FeedCenter.Options.AccountsOptionsPanel"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:options="clr-namespace:FeedCenter.Options"
xmlns:properties="clr-namespace:FeedCenter.Properties"
xmlns:controls="clr-namespace:ChrisKaczor.Wpf.Controls;assembly=ChrisKaczor.Wpf.Controls.Link"
xmlns:feedCenter="clr-namespace:FeedCenter"
mc:Ignorable="d"
d:DesignHeight="311"
d:DesignWidth="425">
<options:OptionsPanelBase.Resources>
<ResourceDictionary>
<options:AccountTypeToNameConverter x:Key="AccountTypeToNameConverter" />
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Controls.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Fonts.xaml" />
<ResourceDictionary
Source="pack://application:,,,/MahApps.Metro;component/Styles/Controls.FlatButton.xaml" />
<ResourceDictionary
Source="pack://application:,,,/MahApps.Metro;component/Styles/Themes/light.cobalt.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</options:OptionsPanelBase.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<DataGrid Name="AccountDataGrid"
SelectionChanged="HandleAccountDataGridSelectionChanged"
Grid.Row="0"
SelectionMode="Single"
SelectionUnit="FullRow"
Grid.Column="0"
AutoGenerateColumns="False"
GridLinesVisibility="None"
CanUserResizeRows="False"
IsReadOnly="True"
HeadersVisibility="Column"
BorderThickness="1,1,1,1"
BorderBrush="{DynamicResource {x:Static SystemColors.ActiveBorderBrushKey}}"
AllowDrop="True"
Background="{x:Null}"
d:DataContext="{d:DesignInstance feedCenter:Account }">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Name}"
Header="{x:Static properties:Resources.AccountNameColumnHeader}"
SortDirection="Ascending"
Width="*" />
<DataGridTextColumn Binding="{Binding Type, Converter={StaticResource AccountTypeToNameConverter}}"
Header="{x:Static properties:Resources.AccountTypeColumnHeader}"
Width="*" />
</DataGrid.Columns>
<DataGrid.RowStyle>
<Style TargetType="DataGridRow"
BasedOn="{StaticResource MahApps.Styles.DataGridRow}">
<EventSetter Event="MouseDoubleClick"
Handler="HandleAccountDataGridRowMouseDoubleClick" />
</Style>
</DataGrid.RowStyle>
</DataGrid>
<Border Grid.Column="0"
Grid.Row="1"
BorderThickness="1,0,1,1"
BorderBrush="{DynamicResource {x:Static SystemColors.ActiveBorderBrushKey}}" />
<Border Grid.Row="1"
Grid.Column="0"
BorderThickness="1,0,1,1"
BorderBrush="{DynamicResource {x:Static SystemColors.ActiveBorderBrushKey}}">
<StackPanel Orientation="Horizontal"
Background="{DynamicResource {x:Static SystemColors.ControlBrushKey}}">
<controls:Link Name="AddAccountButton"
Margin="2"
Click="HandleAddAccountButtonClick"
Text="{x:Static properties:Resources.AddLink}"
ToolTip="{x:Static properties:Resources.AddAccountButton}">
</controls:Link>
<controls:Link Name="EditAccountButton"
Margin="2"
Click="HandleEditAccountButtonClick"
Text="{x:Static properties:Resources.EditLink}"
ToolTip="{x:Static properties:Resources.EditAccountButton}">
</controls:Link>
<controls:Link Name="DeleteAccountButton"
Margin="2"
Click="HandleDeleteAccountButtonClick"
Text="{x:Static properties:Resources.DeleteLink}"
ToolTip="{x:Static properties:Resources.DeleteAccountButton}">
</controls:Link>
</StackPanel>
</Border>
</Grid>
</options:OptionsPanelBase>

View File

@@ -0,0 +1,129 @@
using System.ComponentModel;
using System.Linq;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Input;
namespace FeedCenter.Options;
public partial class AccountsOptionsPanel
{
private readonly FeedCenterEntities _entities;
public AccountsOptionsPanel(Window parentWindow, FeedCenterEntities entities) : base(parentWindow, entities)
{
_entities = entities;
InitializeComponent();
}
public override string CategoryName => Properties.Resources.optionCategoryAccounts;
public override void LoadPanel()
{
base.LoadPanel();
var collectionViewSource = new CollectionViewSource { Source = _entities.Accounts };
collectionViewSource.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));
collectionViewSource.IsLiveSortingRequested = true;
collectionViewSource.View.Filter = item =>
{
if (item is not Account account)
return false;
// Filter out local accounts
return account.Type != AccountType.Local;
};
AccountDataGrid.ItemsSource = collectionViewSource.View;
AccountDataGrid.SelectedIndex = 0;
SetAccountButtonStates();
}
private void SetAccountButtonStates()
{
AddAccountButton.IsEnabled = true;
EditAccountButton.IsEnabled = AccountDataGrid.SelectedItem != null;
DeleteAccountButton.IsEnabled = AccountDataGrid.SelectedItem != null;
}
private void AddAccount()
{
var account = new Account(AccountType.Fever);
var accountWindow = new AccountWindow(_entities);
var result = accountWindow.Display(account, Window.GetWindow(this), true);
if (!result.HasValue || !result.Value)
return;
AccountDataGrid.SelectedItem = account;
SetAccountButtonStates();
}
private void EditSelectedAccount()
{
if (AccountDataGrid.SelectedItem == null)
return;
var account = (Account) AccountDataGrid.SelectedItem;
var accountWindow = new AccountWindow(_entities);
accountWindow.Display(account, Window.GetWindow(this), false);
}
private void DeleteSelectedAccount()
{
var account = (Account) AccountDataGrid.SelectedItem;
if (MessageBox.Show(ParentWindow, string.Format(Properties.Resources.ConfirmDeleteAccount, account.Name),
Properties.Resources.ConfirmDeleteTitle, MessageBoxButton.YesNo, MessageBoxImage.Question,
MessageBoxResult.No) == MessageBoxResult.No)
return;
var index = AccountDataGrid.SelectedIndex;
if (index == AccountDataGrid.Items.Count - 1)
AccountDataGrid.SelectedIndex = index - 1;
else
AccountDataGrid.SelectedIndex = index + 1;
_entities.SaveChanges(() => _entities.Accounts.Remove(account));
SetAccountButtonStates();
}
private void HandleAddAccountButtonClick(object sender, RoutedEventArgs e)
{
AddAccount();
}
private void HandleEditAccountButtonClick(object sender, RoutedEventArgs e)
{
EditSelectedAccount();
}
private void HandleDeleteAccountButtonClick(object sender, RoutedEventArgs e)
{
DeleteSelectedAccount();
}
private void HandleAccountDataGridSelectionChanged(object sender, SelectionChangedEventArgs e)
{
SetAccountButtonStates();
}
private void HandleAccountDataGridRowMouseDoubleClick(object sender, MouseButtonEventArgs e)
{
if (!EditAccountButton.IsEnabled)
return;
EditSelectedAccount();
}
}

View File

@@ -1,5 +1,4 @@
using FeedCenter.Data;
using System.Collections.Generic;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Windows;
@@ -12,9 +11,12 @@ public partial class BulkFeedWindow
{
private List<CheckedListItem<Feed>> _checkedListBoxItems;
private CollectionViewSource _collectionViewSource;
private readonly FeedCenterEntities _entities;
public BulkFeedWindow()
public BulkFeedWindow(FeedCenterEntities entities)
{
_entities = entities;
InitializeComponent();
}
@@ -22,7 +24,7 @@ public partial class BulkFeedWindow
{
_checkedListBoxItems = new List<CheckedListItem<Feed>>();
foreach (var feed in Database.Entities.Feeds)
foreach (var feed in _entities.Feeds)
_checkedListBoxItems.Add(new CheckedListItem<Feed> { Item = feed });
_collectionViewSource = new CollectionViewSource { Source = _checkedListBoxItems };
@@ -52,7 +54,7 @@ public partial class BulkFeedWindow
private void HandleOkButtonClick(object sender, RoutedEventArgs e)
{
Database.Entities.SaveChanges(() =>
_entities.SaveChanges(() =>
{
foreach (var item in _checkedListBoxItems.Where(i => i.IsChecked))
{

View File

@@ -24,14 +24,17 @@
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Controls.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Fonts.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Controls.FlatButton.xaml" />
<ResourceDictionary Source="pack://application:,,,/MahApps.Metro;component/Styles/Themes/light.cobalt.xaml" />
<ResourceDictionary
Source="pack://application:,,,/MahApps.Metro;component/Styles/Controls.FlatButton.xaml" />
<ResourceDictionary
Source="pack://application:,,,/MahApps.Metro;component/Styles/Themes/light.cobalt.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<StackPanel Margin="6"
options:Spacing.Vertical="5">
<TextBox mah:TextBoxHelper.UseFloatingWatermark="True"
<TextBox Name="NameTextBox"
mah:TextBoxHelper.UseFloatingWatermark="True"
mah:TextBoxHelper.Watermark="{x:Static properties:Resources.categoryNameLabel}"
mah:TextBoxHelper.SelectAllOnFocus="True"
Text="{Binding Path=Name, UpdateSourceTrigger=Explicit, ValidatesOnExceptions=True}" />

View File

@@ -1,13 +1,16 @@
using System.Windows;
using ChrisKaczor.Wpf.Validation;
using FeedCenter.Data;
using ChrisKaczor.Wpf.Validation;
using System.Windows;
namespace FeedCenter.Options;
public partial class CategoryWindow
{
public CategoryWindow()
private readonly FeedCenterEntities _entities;
public CategoryWindow(FeedCenterEntities entities)
{
_entities = entities;
InitializeComponent();
}
@@ -30,7 +33,7 @@ public partial class CategoryWindow
private void HandleOkayButtonClick(object sender, RoutedEventArgs e)
{
var transaction = Database.Entities.BeginTransaction();
var transaction = _entities.BeginTransaction();
if (!this.IsValid())
{
@@ -39,7 +42,6 @@ public partial class CategoryWindow
}
transaction.Commit();
Database.Entities.Refresh();
// Dialog is good
DialogResult = true;

View File

@@ -5,7 +5,7 @@ namespace FeedCenter.Options;
public partial class DisplayOptionsPanel
{
public DisplayOptionsPanel(Window parentWindow) : base(parentWindow)
public DisplayOptionsPanel(Window parentWindow, FeedCenterEntities entities) : base(parentWindow, entities)
{
InitializeComponent();
}

View File

@@ -94,6 +94,13 @@
<ComboBoxItem Content="{x:Static properties:Resources.openAllMultipleToolbarButton}"
Tag="{x:Static feedCenter:MultipleOpenAction.IndividualPages}" />
</ComboBox>
<ComboBox Name="UserAgentComboBox"
mah:TextBoxHelper.UseFloatingWatermark="True"
mah:TextBoxHelper.Watermark="{x:Static properties:Resources.userAgentLabel}"
DisplayMemberPath="Caption"
ItemsSource="{Binding Source={x:Static options:UserAgentItem.UserAgents}}"
SelectedValuePath="UserAgent"
SelectedValue="{Binding Path=UserAgent, UpdateSourceTrigger=Explicit, ValidatesOnExceptions=true}" />
</StackPanel>
</TabItem>
<TabItem Header="{x:Static properties:Resources.authenticationTab}">

View File

@@ -1,19 +1,22 @@
using ChrisKaczor.Wpf.Validation;
using FeedCenter.Data;
using System.Windows;
namespace FeedCenter.Options;
public partial class FeedWindow
{
public FeedWindow()
private readonly FeedCenterEntities _entities;
public FeedWindow(FeedCenterEntities entities)
{
_entities = entities;
InitializeComponent();
}
public bool? Display(Feed feed, Window owner)
{
CategoryComboBox.ItemsSource = Database.Entities.Categories;
CategoryComboBox.ItemsSource = _entities.Categories;
DataContext = feed;
@@ -26,7 +29,7 @@ public partial class FeedWindow
private void HandleOkayButtonClick(object sender, RoutedEventArgs e)
{
var transaction = Database.Entities.BeginTransaction();
var transaction = _entities.BeginTransaction();
if (!this.IsValid(OptionsTabControl))
{
@@ -35,7 +38,6 @@ public partial class FeedWindow
}
transaction.Commit();
Database.Entities.Refresh();
DialogResult = true;

View File

@@ -1,5 +1,4 @@
using FeedCenter.Data;
using Microsoft.Win32;
using Microsoft.Win32;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
@@ -14,9 +13,12 @@ namespace FeedCenter.Options;
public partial class FeedsOptionsPanel
{
private CollectionViewSource _collectionViewSource;
private readonly FeedCenterEntities _entities;
public FeedsOptionsPanel(Window parentWindow) : base(parentWindow)
public FeedsOptionsPanel(Window parentWindow, FeedCenterEntities entities) : base(parentWindow, entities)
{
_entities = entities;
InitializeComponent();
}
@@ -26,7 +28,7 @@ public partial class FeedsOptionsPanel
{
base.LoadPanel();
var collectionViewSource = new CollectionViewSource { Source = Database.Entities.Categories };
var collectionViewSource = new CollectionViewSource { Source = _entities.Categories };
collectionViewSource.SortDescriptions.Add(new SortDescription("SortKey", ListSortDirection.Ascending));
collectionViewSource.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));
collectionViewSource.IsLiveSortingRequested = true;
@@ -44,20 +46,20 @@ public partial class FeedsOptionsPanel
private void AddFeed()
{
var feed = Feed.Create();
var feed = Feed.Create(_entities);
var category = (Category) CategoryDataGrid.SelectedItem;
feed.CategoryId = category.Id;
var feedWindow = new FeedWindow();
var feedWindow = new FeedWindow(_entities);
var result = feedWindow.Display(feed, Window.GetWindow(this));
if (!result.HasValue || !result.Value)
return;
Database.Entities.SaveChanges(() => Database.Entities.Feeds.Add(feed));
_entities.SaveChanges(() => _entities.Feeds.Add(feed));
FeedDataGrid.SelectedItem = feed;
@@ -71,7 +73,7 @@ public partial class FeedsOptionsPanel
var feed = (Feed) FeedDataGrid.SelectedItem;
var feedWindow = new FeedWindow();
var feedWindow = new FeedWindow(_entities);
feedWindow.Display(feed, Window.GetWindow(this));
}
@@ -86,7 +88,7 @@ public partial class FeedsOptionsPanel
FeedDataGrid.SelectedItems.CopyTo(selectedItems, 0);
foreach (var feed in selectedItems)
Database.Entities.SaveChanges(() => Database.Entities.Feeds.Remove(feed));
_entities.SaveChanges(() => _entities.Feeds.Remove(feed));
SetFeedButtonStates();
}
@@ -116,7 +118,7 @@ public partial class FeedsOptionsPanel
ExportFeeds();
}
private static void ExportFeeds()
private void ExportFeeds()
{
var saveFileDialog = new SaveFileDialog
{
@@ -143,7 +145,7 @@ public partial class FeedsOptionsPanel
xmlWriter.WriteStartElement("opml");
xmlWriter.WriteStartElement("body");
foreach (var feed in Database.Entities.Feeds.OrderBy(feed => feed.Name))
foreach (var feed in _entities.Feeds.OrderBy(feed => feed.Name))
{
xmlWriter.WriteStartElement("outline");
@@ -162,7 +164,7 @@ public partial class FeedsOptionsPanel
xmlWriter.Close();
}
private static void ImportFeeds()
private void ImportFeeds()
{
var openFileDialog = new OpenFileDialog
{
@@ -188,8 +190,7 @@ public partial class FeedsOptionsPanel
while (xmlReader.NodeType != XmlNodeType.EndElement)
{
var feed = Feed.Create();
feed.CategoryId = Database.Entities.Categories.First(c => c.IsDefault).Id;
var feed = Feed.Create(_entities);
while (xmlReader.MoveToNextAttribute())
{
@@ -218,7 +219,7 @@ public partial class FeedsOptionsPanel
if (string.IsNullOrEmpty(feed.Name))
feed.Name = feed.Title;
Database.Entities.Feeds.Add(feed);
_entities.SaveChanges(() => _entities.Feeds.Add(feed));
xmlReader.MoveToElement();
@@ -242,23 +243,23 @@ public partial class FeedsOptionsPanel
var selectedId = ((Category) CategoryDataGrid.SelectedItem).Id;
EditCategoryButton.IsEnabled = CategoryDataGrid.SelectedItem != null &&
selectedId != Database.Entities.DefaultCategory.Id;
selectedId != _entities.DefaultCategory.Id;
DeleteCategoryButton.IsEnabled = CategoryDataGrid.SelectedItem != null &&
selectedId != Database.Entities.DefaultCategory.Id;
selectedId != _entities.DefaultCategory.Id;
}
private void AddCategory()
{
var category = new Category();
var categoryWindow = new CategoryWindow();
var categoryWindow = new CategoryWindow(_entities);
var result = categoryWindow.Display(category, Window.GetWindow(this));
if (!result.HasValue || !result.Value)
return;
Database.Entities.SaveChanges(() => Database.Entities.Categories.Add(category));
_entities.SaveChanges(() => _entities.Categories.Add(category));
CategoryDataGrid.SelectedItem = category;
@@ -272,7 +273,7 @@ public partial class FeedsOptionsPanel
var category = (Category) CategoryDataGrid.SelectedItem;
var categoryWindow = new CategoryWindow();
var categoryWindow = new CategoryWindow(_entities);
categoryWindow.Display(category, Window.GetWindow(this));
}
@@ -284,10 +285,10 @@ public partial class FeedsOptionsPanel
if (MessageBox.Show(ParentWindow, string.Format(Properties.Resources.ConfirmDeleteCategory, category.Name), Properties.Resources.ConfirmDeleteTitle, MessageBoxButton.YesNo, MessageBoxImage.Question, MessageBoxResult.No) == MessageBoxResult.No)
return;
var defaultCategory = Database.Entities.DefaultCategory;
var defaultCategory = _entities.DefaultCategory;
foreach (var feed in Database.Entities.Feeds.Where(f => f.CategoryId == category.Id))
Database.Entities.SaveChanges(() => feed.CategoryId = defaultCategory.Id);
foreach (var feed in _entities.Feeds.Where(f => f.CategoryId == category.Id))
_entities.SaveChanges(() => feed.CategoryId = defaultCategory.Id);
var index = CategoryDataGrid.SelectedIndex;
@@ -296,7 +297,7 @@ public partial class FeedsOptionsPanel
else
CategoryDataGrid.SelectedIndex = index + 1;
Database.Entities.SaveChanges(() => Database.Entities.Categories.Remove(category));
_entities.SaveChanges(() => _entities.Categories.Remove(category));
SetCategoryButtonStates();
}
@@ -320,7 +321,7 @@ public partial class FeedsOptionsPanel
{
if (_collectionViewSource == null)
{
_collectionViewSource = new CollectionViewSource { Source = Database.Entities.Feeds };
_collectionViewSource = new CollectionViewSource { Source = _entities.Feeds };
_collectionViewSource.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));
_collectionViewSource.Filter += HandleCollectionViewSourceFilter;
@@ -342,7 +343,7 @@ public partial class FeedsOptionsPanel
var feed = (Feed) e.Item;
e.Accepted = feed.CategoryId == selectedCategory.Id;
e.Accepted = feed.CategoryId == selectedCategory.Id && feed.Account.Type == AccountType.Local;
}
private void HandleCategoryDataGridRowDrop(object sender, DragEventArgs e)
@@ -352,7 +353,7 @@ public partial class FeedsOptionsPanel
var category = (Category) ((DataGridRow) sender).Item;
foreach (var feed in feedList!)
Database.Entities.SaveChanges(() => feed.CategoryId = category.Id);
_entities.SaveChanges(() => feed.CategoryId = category.Id);
_collectionViewSource.View.Refresh();
@@ -382,7 +383,7 @@ public partial class FeedsOptionsPanel
private void HandleMultipleEditClick(object sender, RoutedEventArgs e)
{
var bulkFeedWindow = new BulkFeedWindow();
var bulkFeedWindow = new BulkFeedWindow(_entities);
bulkFeedWindow.Display(Window.GetWindow(this));
}

View File

@@ -12,11 +12,9 @@
d:DesignWidth="300">
<StackPanel options:Spacing.Vertical="10">
<CheckBox Content="{x:Static properties:Resources.startWithWindowsCheckBox}"
Name="StartWithWindowsCheckBox"
IsChecked="{Binding Source={x:Static properties:Settings.Default}, Path=StartWithWindows}"
Click="OnSaveSettings" />
<ComboBox Name="BrowserComboBox"
mah:TextBoxHelper.UseFloatingWatermark="True"
<ComboBox mah:TextBoxHelper.UseFloatingWatermark="True"
mah:TextBoxHelper.Watermark="{x:Static properties:Resources.defaultBrowserLabel}"
d:DataContext="{d:DesignInstance Type=installedBrowsers:InstalledBrowser}"
DisplayMemberPath="Name"
@@ -24,12 +22,11 @@
SelectedValuePath="Key"
SelectedValue="{Binding Source={x:Static properties:Settings.Default}, Path=Browser}"
SelectionChanged="OnSaveSettings" />
<ComboBox Name="UserAgentComboBox"
mah:TextBoxHelper.UseFloatingWatermark="True"
<ComboBox mah:TextBoxHelper.UseFloatingWatermark="True"
mah:TextBoxHelper.Watermark="{x:Static properties:Resources.defaultUserAgentLabel}"
d:DataContext="{d:DesignInstance Type=options:UserAgentItem}"
DisplayMemberPath="Caption"
ItemsSource="{Binding Source={x:Static options:UserAgentItem.UserAgents}}"
ItemsSource="{Binding Source={x:Static options:UserAgentItem.DefaultUserAgents}}"
SelectedValuePath="UserAgent"
SelectedValue="{Binding Source={x:Static properties:Settings.Default}, Path=DefaultUserAgent}"
SelectionChanged="OnSaveSettings" />

View File

@@ -6,7 +6,7 @@ namespace FeedCenter.Options;
public partial class GeneralOptionsPanel
{
public GeneralOptionsPanel(Window parentWindow) : base(parentWindow)
public GeneralOptionsPanel(Window parentWindow, FeedCenterEntities entities) : base(parentWindow, entities)
{
InitializeComponent();
}

View File

@@ -6,10 +6,12 @@ namespace FeedCenter.Options;
public class OptionsPanelBase : UserControl
{
protected readonly Window ParentWindow;
protected readonly FeedCenterEntities Entities;
protected OptionsPanelBase(Window parentWindow)
protected OptionsPanelBase(Window parentWindow, FeedCenterEntities entities)
{
ParentWindow = parentWindow;
Entities = entities;
}
public virtual string CategoryName => null;

View File

@@ -6,6 +6,7 @@ namespace FeedCenter.Options;
public partial class OptionsWindow
{
private readonly List<OptionsPanelBase> _optionPanels = new();
private readonly FeedCenterEntities _entities = new();
public OptionsWindow()
{
@@ -20,11 +21,12 @@ public partial class OptionsWindow
private void AddCategories()
{
_optionPanels.Add(new GeneralOptionsPanel(this));
_optionPanels.Add(new DisplayOptionsPanel(this));
_optionPanels.Add(new FeedsOptionsPanel(this));
_optionPanels.Add(new UpdateOptionsPanel(this));
_optionPanels.Add(new AboutOptionsPanel(this));
_optionPanels.Add(new GeneralOptionsPanel(this, _entities));
_optionPanels.Add(new DisplayOptionsPanel(this, _entities));
_optionPanels.Add(new FeedsOptionsPanel(this, _entities));
_optionPanels.Add(new AccountsOptionsPanel(this, _entities));
_optionPanels.Add(new UpdateOptionsPanel(this, _entities));
_optionPanels.Add(new AboutOptionsPanel(this, _entities));
}
private void LoadCategories()

View File

@@ -13,6 +13,10 @@
Name="CheckVersionOnStartupCheckBox"
IsChecked="{Binding Source={x:Static properties:Settings.Default}, Path=CheckVersionAtStartup}"
Click="OnSaveSettings" />
<CheckBox Content="{x:Static properties:Resources.includePrereleaseCheckBox}"
Name="IncludePrereleaseCheckBox"
IsChecked="{Binding Source={x:Static properties:Settings.Default}, Path=IncludePrerelease}"
Click="OnSaveSettings" />
<Button Content="{x:Static properties:Resources.checkVersionNowButton}"
HorizontalAlignment="Left"
Click="HandleCheckVersionNowButtonClick" />

View File

@@ -6,7 +6,7 @@ namespace FeedCenter.Options;
public partial class UpdateOptionsPanel
{
public UpdateOptionsPanel(Window parentWindow) : base(parentWindow)
public UpdateOptionsPanel(Window parentWindow, FeedCenterEntities entities) : base(parentWindow, entities)
{
InitializeComponent();
}
@@ -15,7 +15,7 @@ public partial class UpdateOptionsPanel
private void HandleCheckVersionNowButtonClick(object sender, RoutedEventArgs e)
{
UpdateCheck.DisplayUpdateInformation(true);
UpdateCheck.DisplayUpdateInformation(true, Settings.Default.IncludePrerelease);
}
private void OnSaveSettings(object sender, RoutedEventArgs e)

View File

@@ -1,17 +1,18 @@
using System.Collections.Generic;
using System.Linq;
using FeedCenter.Properties;
namespace FeedCenter.Options;
public class UserAgentItem
{
public string Caption { get; set; }
public string UserAgent { get; set; }
public static List<UserAgentItem> UserAgents => new()
public static List<UserAgentItem> DefaultUserAgents => new()
{
new UserAgentItem
{
Caption = Properties.Resources.DefaultUserAgentCaption,
Caption = Properties.Resources.ApplicationUserAgentCaption,
UserAgent = string.Empty
},
new UserAgentItem
@@ -30,4 +31,29 @@ public class UserAgentItem
UserAgent = "curl/7.47.0"
}
};
public string UserAgent { get; set; }
public static List<UserAgentItem> UserAgents
{
get
{
var defaultUserAgents = DefaultUserAgents;
var applicationDefaultUserAgent = defaultUserAgents.First(dua => dua.UserAgent == Settings.Default.DefaultUserAgent);
var userAgents = new List<UserAgentItem>
{
new()
{
Caption = string.Format(Resources.DefaultUserAgentCaption, applicationDefaultUserAgent.Caption),
UserAgent = null
}
};
userAgents.AddRange(defaultUserAgents);
return userAgents;
}
}
}

View File

@@ -8,11 +8,12 @@ using System.Windows;
[assembly: AssemblyDescription("")]
[assembly: AssemblyCompany("Chris Kaczor")]
[assembly: AssemblyProduct("Feed Center")]
[assembly: AssemblyCopyright("Copyright © Chris Kaczor 2023")]
[assembly: AssemblyCopyright("Copyright © Chris Kaczor 2025")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
[assembly: SupportedOSPlatform("windows")]
[assembly: TargetPlatform("Windows10.0")]
[assembly: SupportedOSPlatform("Windows10.0")]
#if DEBUG
[assembly: AssemblyConfiguration("Debug build")]

View File

@@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- https://go.microsoft.com/fwlink/?LinkID=208121. -->
<Project>
<PropertyGroup>
<Configuration>Release</Configuration>
<Platform>Any CPU</Platform>
<PublishDir>bin\Release\publish\</PublishDir>
<PublishProtocol>FileSystem</PublishProtocol>
<_TargetId>Folder</_TargetId>
<TargetFramework>net9.0-windows8.0</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<SelfContained>true</SelfContained>
<PublishSingleFile>true</PublishSingleFile>
<PublishReadyToRun>true</PublishReadyToRun>
<PublishTrimmed>false</PublishTrimmed>
</PropertyGroup>
</Project>

View File

@@ -60,6 +60,114 @@ namespace FeedCenter.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Name.
/// </summary>
public static string AccountNameColumnHeader {
get {
return ResourceManager.GetString("AccountNameColumnHeader", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Name.
/// </summary>
public static string accountNameLabel {
get {
return ResourceManager.GetString("accountNameLabel", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to _Refresh account every.
/// </summary>
public static string accountReadIntervalPrefix {
get {
return ResourceManager.GetString("accountReadIntervalPrefix", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to minutes.
/// </summary>
public static string accountReadIntervalSuffix {
get {
return ResourceManager.GetString("accountReadIntervalSuffix", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to _Account requires authentication.
/// </summary>
public static string accountRequiresAuthenticationCheckBox {
get {
return ResourceManager.GetString("accountRequiresAuthenticationCheckBox", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Type.
/// </summary>
public static string AccountTypeColumnHeader {
get {
return ResourceManager.GetString("AccountTypeColumnHeader", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Fever.
/// </summary>
public static string AccountTypeFever {
get {
return ResourceManager.GetString("AccountTypeFever", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Google Reader.
/// </summary>
public static string AccountTypeGoogleReader {
get {
return ResourceManager.GetString("AccountTypeGoogleReader", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Type.
/// </summary>
public static string accountTypeLabel {
get {
return ResourceManager.GetString("accountTypeLabel", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to URL.
/// </summary>
public static string accountUrlLabel {
get {
return ResourceManager.GetString("accountUrlLabel", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Add Account.
/// </summary>
public static string AccountWindowAdd {
get {
return ResourceManager.GetString("AccountWindowAdd", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Edit Account.
/// </summary>
public static string AccountWindowEdit {
get {
return ResourceManager.GetString("AccountWindowEdit", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to A_ctions.
/// </summary>
@@ -69,6 +177,15 @@ namespace FeedCenter.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Add Account.
/// </summary>
public static string AddAccountButton {
get {
return ResourceManager.GetString("AddAccountButton", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Add Category.
/// </summary>
@@ -133,6 +250,15 @@ namespace FeedCenter.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Feed Center.
/// </summary>
public static string ApplicationUserAgentCaption {
get {
return ResourceManager.GetString("ApplicationUserAgentCaption", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Password.
/// </summary>
@@ -268,6 +394,15 @@ namespace FeedCenter.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Are you sure you want to delete this account?.
/// </summary>
public static string ConfirmDeleteAccount {
get {
return ResourceManager.GetString("ConfirmDeleteAccount", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Are you sure you want to delete this category?
///
@@ -517,7 +652,7 @@ namespace FeedCenter.Properties {
}
/// <summary>
/// Looks up a localized string similar to Feed Center.
/// Looks up a localized string similar to Default ({0}).
/// </summary>
public static string DefaultUserAgentCaption {
get {
@@ -534,6 +669,15 @@ namespace FeedCenter.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Delete Account.
/// </summary>
public static string DeleteAccountButton {
get {
return ResourceManager.GetString("DeleteAccountButton", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Delete Category.
/// </summary>
@@ -588,6 +732,15 @@ namespace FeedCenter.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Edit Account.
/// </summary>
public static string EditAccountButton {
get {
return ResourceManager.GetString("EditAccountButton", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Edit Category.
/// </summary>
@@ -831,6 +984,15 @@ namespace FeedCenter.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Temporarily unavailable.
/// </summary>
public static string FeedReadResult_TemporarilyUnavailable {
get {
return ResourceManager.GetString("FeedReadResult_TemporarilyUnavailable", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Timeout.
/// </summary>
@@ -840,6 +1002,15 @@ namespace FeedCenter.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Too many requests.
/// </summary>
public static string FeedReadResult_TooManyRequests {
get {
return ResourceManager.GetString("FeedReadResult_TooManyRequests", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Not authorized.
/// </summary>
@@ -930,6 +1101,15 @@ namespace FeedCenter.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Include _prerelease.
/// </summary>
public static string includePrereleaseCheckBox {
get {
return ResourceManager.GetString("includePrereleaseCheckBox", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Last Updated.
/// </summary>
@@ -1128,6 +1308,15 @@ namespace FeedCenter.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to Accounts.
/// </summary>
public static string optionCategoryAccounts {
get {
return ResourceManager.GetString("optionCategoryAccounts", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Categories.
/// </summary>
@@ -1147,7 +1336,7 @@ namespace FeedCenter.Properties {
}
/// <summary>
/// Looks up a localized string similar to Feeds.
/// Looks up a localized string similar to Local Feeds.
/// </summary>
public static string optionCategoryFeeds {
get {
@@ -1461,6 +1650,15 @@ namespace FeedCenter.Properties {
}
}
/// <summary>
/// Looks up a localized string similar to User agent.
/// </summary>
public static string userAgentLabel {
get {
return ResourceManager.GetString("userAgentLabel", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to Version {0}.
/// </summary>

View File

@@ -230,7 +230,7 @@
<value>About</value>
</data>
<data name="optionCategoryFeeds" xml:space="preserve">
<value>Feeds</value>
<value>Local Feeds</value>
</data>
<data name="optionCategoryUpdate" xml:space="preserve">
<value>Update</value>
@@ -524,6 +524,9 @@
<value>Category: {0}</value>
</data>
<data name="DefaultUserAgentCaption" xml:space="preserve">
<value>Default ({0})</value>
</data>
<data name="ApplicationUserAgentCaption" xml:space="preserve">
<value>Feed Center</value>
</data>
<data name="defaultUserAgentLabel" xml:space="preserve">
@@ -549,4 +552,67 @@ All feeds currently in category "{0}" will be moved to the default category.</va
<data name="ConfirmDeleteFeeds" xml:space="preserve">
<value>Are you sure you want to delete the selected feeds?</value>
</data>
<data name="userAgentLabel" xml:space="preserve">
<value>User agent</value>
</data>
<data name="FeedReadResult_TemporarilyUnavailable" xml:space="preserve">
<value>Temporarily unavailable</value>
</data>
<data name="FeedReadResult_TooManyRequests" xml:space="preserve">
<value>Too many requests</value>
</data>
<data name="optionCategoryAccounts" xml:space="preserve">
<value>Accounts</value>
</data>
<data name="AccountNameColumnHeader" xml:space="preserve">
<value>Name</value>
</data>
<data name="AddAccountButton" xml:space="preserve">
<value>Add Account</value>
</data>
<data name="EditAccountButton" xml:space="preserve">
<value>Edit Account</value>
</data>
<data name="DeleteAccountButton" xml:space="preserve">
<value>Delete Account</value>
</data>
<data name="AccountWindowAdd" xml:space="preserve">
<value>Add Account</value>
</data>
<data name="AccountWindowEdit" xml:space="preserve">
<value>Edit Account</value>
</data>
<data name="ConfirmDeleteAccount" xml:space="preserve">
<value>Are you sure you want to delete this account?</value>
</data>
<data name="accountNameLabel" xml:space="preserve">
<value>Name</value>
</data>
<data name="accountUrlLabel" xml:space="preserve">
<value>URL</value>
</data>
<data name="accountReadIntervalPrefix" xml:space="preserve">
<value>_Refresh account every</value>
</data>
<data name="accountReadIntervalSuffix" xml:space="preserve">
<value>minutes</value>
</data>
<data name="accountRequiresAuthenticationCheckBox" xml:space="preserve">
<value>_Account requires authentication</value>
</data>
<data name="accountTypeLabel" xml:space="preserve">
<value>Type</value>
</data>
<data name="AccountTypeFever" xml:space="preserve">
<value>Fever</value>
</data>
<data name="AccountTypeColumnHeader" xml:space="preserve">
<value>Type</value>
</data>
<data name="AccountTypeGoogleReader" xml:space="preserve">
<value>Google Reader</value>
</data>
<data name="includePrereleaseCheckBox" xml:space="preserve">
<value>Include _prerelease</value>
</data>
</root>

View File

@@ -12,7 +12,7 @@ namespace FeedCenter.Properties {
[global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.5.0.0")]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.14.0.0")]
internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
@@ -302,5 +302,17 @@ namespace FeedCenter.Properties {
return ((string)(this["DatabaseFile"]));
}
}
[global::System.Configuration.UserScopedSettingAttribute()]
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
[global::System.Configuration.DefaultSettingValueAttribute("False")]
public bool IncludePrerelease {
get {
return ((bool)(this["IncludePrerelease"]));
}
set {
this["IncludePrerelease"] = value;
}
}
}
}

View File

@@ -74,5 +74,8 @@
<Setting Name="DatabaseFile" Type="System.String" Scope="Application">
<Value Profile="(Default)">FeedCenter.realm</Value>
</Setting>
<Setting Name="IncludePrerelease" Type="System.Boolean" Scope="User">
<Value Profile="(Default)">False</Value>
</Setting>
</Settings>
</SettingsFile>

View File

@@ -9,12 +9,7 @@ public static class SettingsStore
{
public static object OpenDataStore()
{
if (!Database.Exists)
return null;
Database.Load();
return Database.Entities;
return !Database.Exists ? null : new FeedCenterEntities();
}
public static string GetSettingValue(object dataStore, string name, Version _)
@@ -36,6 +31,9 @@ public static class SettingsStore
// Try to get the setting from the database that matches the name and version
var setting = entities.Settings.FirstOrDefault(s => s.Name == name);
if (setting?.Value == value)
return;
entities.SaveChanges(() =>
{
// If there was no setting we need to create it

View File

@@ -11,20 +11,13 @@ namespace FeedCenter;
public partial class SplashWindow : IDisposable
{
private class ProgressStep
private class ProgressStep(string key, string caption, ProgressStep.ProgressCallback callback)
{
public delegate bool ProgressCallback();
public readonly string Key;
public readonly string Caption;
public readonly ProgressCallback Callback;
public ProgressStep(string key, string caption, ProgressCallback callback)
{
Key = key;
Caption = caption;
Callback = callback;
}
public readonly string Key = key;
public readonly string Caption = caption;
public readonly ProgressCallback Callback = callback;
}
private readonly List<ProgressStep> _progressSteps = new();
@@ -180,12 +173,7 @@ public partial class SplashWindow : IDisposable
private bool LoadDatabase()
{
_dispatcher.Invoke(() =>
{
Database.Load();
Settings.Default.Reload();
});
_dispatcher.Invoke(() => Settings.Default.Reload());
return true;
}

View File

@@ -1,7 +1,6 @@
using FeedCenter.Properties;
using System;
using System.IO;
using System.Reflection;
namespace FeedCenter;
@@ -9,7 +8,7 @@ public static class SystemConfiguration
{
private static bool UseDebugPath => Environment.CommandLine.IndexOf("/debugPath", StringComparison.InvariantCultureIgnoreCase) != -1;
public static string DataDirectory => UseDebugPath ? Path.GetDirectoryName(Assembly.GetEntryAssembly().Location) : UserSettingsPath;
public static string DataDirectory => UseDebugPath ? Path.GetDirectoryName(AppContext.BaseDirectory) : UserSettingsPath;
public static string UserSettingsPath
{
@@ -17,7 +16,7 @@ public static class SystemConfiguration
{
// If we're running in debug mode then use a local path for the database and logs
if (UseDebugPath)
return Path.GetDirectoryName(Assembly.GetEntryAssembly().Location);
return Path.GetDirectoryName(AppContext.BaseDirectory);
// Get the path to the local application data directory
var path = Path.Combine(

View File

@@ -49,6 +49,9 @@
<setting name="DefaultUserAgent" serializeAs="String">
<value />
</setting>
<setting name="IncludePrerelease" serializeAs="String">
<value>False</value>
</setting>
</FeedCenter.Properties.Settings>
</userSettings>
<applicationSettings>

View File

@@ -1,60 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
xmlns:bal="http://schemas.microsoft.com/wix/BalExtension">
<?define ProductName="$(fileVersion.ProductName($(var.FeedCenter.TargetPath)))" ?>
<?define CompanyName="$(fileVersion.CompanyName($(var.FeedCenter.TargetPath)))" ?>
<?define ProductVersion="$(fileVersion.ProductVersion($(var.FeedCenter.TargetPath)))" ?>
<Bundle Name="$(var.ProductName)"
Version="$(var.ProductVersion)"
Manufacturer="$(var.CompanyName)"
UpgradeCode="5e5c13a5-635e-4310-a653-0f9760f46935"
Compressed="no">
<BootstrapperApplicationRef Id="WixExtendedBootstrapperApplication.HyperlinkLicense">
<bal:WixExtendedBootstrapperApplication LicenseUrl=""
SuppressOptionsUI="yes"
ShowVersion="yes"
LocalizationFile="Bundle.wxl"
LaunchPassive="yes"
LaunchQuiet="yes"
LaunchTarget="[LocalAppDataFolder]$(var.ProductName)\FeedCenter.exe" />
</BootstrapperApplicationRef>
<Chain>
<ExePackage Id="runtime_desktop_x64"
DisplayName=".NET 7.0 Desktop Runtime (x64)"
InstallCommand="/install /quiet"
Permanent="yes"
Compressed="no"
DownloadUrl="https://download.visualstudio.microsoft.com/download/pr/dffb1939-cef1-4db3-a579-5475a3061cdd/578b208733c914c7b7357f6baa4ecfd6/windowsdesktop-runtime-7.0.5-win-x64.exe"
Name="windowsdesktop-runtime-7.0.5-win-x64.exe"
InstallCondition='VersionNT64'>
<RemotePayload ProductName="Microsoft Windows Desktop Runtime - 7.0.5 (x64)"
Description="Microsoft Windows Desktop Runtime - 7.0.5 (x64)"
Version="7.0.5.32327"
Hash="5B4232EED009E6B66C64A6096B1277995DE63F57"
Size="57609944" />
</ExePackage>
<ExePackage Id="runtime_desktop_x32"
DisplayName=".NET 7.0 Desktop Runtime (x86)"
InstallCommand="/install /quiet"
Permanent="yes"
Compressed="no"
DownloadUrl="https://download.visualstudio.microsoft.com/download/pr/eb64dcd1-d277-4798-ada1-600805c9e2dc/fc73c843d66f3996e7ef22468f4902e6/windowsdesktop-runtime-7.0.5-win-x86.exe"
Name="windowsdesktop-runtime-7.0.5-win-x86.exe"
InstallCondition='NOT VersionNT64'>
<RemotePayload ProductName="Microsoft Windows Desktop Runtime - 7.0.5 (x86)"
Description="Microsoft Windows Desktop Runtime - 7.0.5 (x86)"
Version="7.0.5.32327"
Hash="3987657473EA907DEC2FA48F492A5FC2B83B5060"
Size="52816632" />
</ExePackage>
<MsiPackage Id="FeedCenter"
SourceFile="$(var.Setup.TargetPath)"
Compressed="yes" />
</Chain>
</Bundle>
</Wix>

View File

@@ -1,128 +0,0 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.5.33414.496
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FeedCenter", "Application\FeedCenter.csproj", "{BD3D12F2-DE23-4466-83B1-1EB617A877A4}"
EndProject
Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "Setup", "Setup\Setup.wixproj", "{DFB3FE30-18EA-4216-8D92-11DF9C8D50F1}"
EndProject
Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "Bootstrapper", "Bootstrapper\Bootstrapper.wixproj", "{5E5C13A5-635E-4310-A653-0F9760F46935}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution", "Solution", "{1462AAAD-B01B-4FF6-9B9F-595D239C9D1E}"
ProjectSection(SolutionItems) = preProject
.gitignore = .gitignore
appveyor.yml = appveyor.yml
LICENSE.md = LICENSE.md
README.md = README.md
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug - With Setup|Any CPU = Debug - With Setup|Any CPU
Debug - With Setup|Mixed Platforms = Debug - With Setup|Mixed Platforms
Debug - With Setup|x64 = Debug - With Setup|x64
Debug - With Setup|x86 = Debug - With Setup|x86
Debug|Any CPU = Debug|Any CPU
Debug|Mixed Platforms = Debug|Mixed Platforms
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release - With Setup|Any CPU = Release - With Setup|Any CPU
Release - With Setup|Mixed Platforms = Release - With Setup|Mixed Platforms
Release - With Setup|x64 = Release - With Setup|x64
Release - With Setup|x86 = Release - With Setup|x86
Release|Any CPU = Release|Any CPU
Release|Mixed Platforms = Release|Mixed Platforms
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Debug - With Setup|Any CPU.ActiveCfg = Debug|Any CPU
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Debug - With Setup|Any CPU.Build.0 = Debug|Any CPU
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Debug - With Setup|Mixed Platforms.ActiveCfg = Debug|Any CPU
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Debug - With Setup|Mixed Platforms.Build.0 = Debug|Any CPU
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Debug - With Setup|x64.ActiveCfg = Debug|x64
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Debug - With Setup|x64.Build.0 = Debug|x64
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Debug - With Setup|x86.ActiveCfg = Debug|x86
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Debug - With Setup|x86.Build.0 = Debug|x86
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Debug|Mixed Platforms.ActiveCfg = Debug|Any CPU
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Debug|Mixed Platforms.Build.0 = Debug|Any CPU
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Debug|x64.ActiveCfg = Debug|x64
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Debug|x64.Build.0 = Debug|x64
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Debug|x86.ActiveCfg = Debug|x86
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Debug|x86.Build.0 = Debug|x86
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Release - With Setup|Any CPU.ActiveCfg = Release|Any CPU
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Release - With Setup|Any CPU.Build.0 = Release|Any CPU
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Release - With Setup|Mixed Platforms.ActiveCfg = Release|Any CPU
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Release - With Setup|Mixed Platforms.Build.0 = Release|Any CPU
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Release - With Setup|x64.ActiveCfg = Release|x86
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Release - With Setup|x86.ActiveCfg = Release|Any CPU
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Release - With Setup|x86.Build.0 = Release|Any CPU
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Release|Any CPU.Build.0 = Release|Any CPU
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Release|Mixed Platforms.ActiveCfg = Release|Any CPU
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Release|Mixed Platforms.Build.0 = Release|Any CPU
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Release|x64.ActiveCfg = Release|x86
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Release|x86.ActiveCfg = Release|x86
{BD3D12F2-DE23-4466-83B1-1EB617A877A4}.Release|x86.Build.0 = Release|x86
{DFB3FE30-18EA-4216-8D92-11DF9C8D50F1}.Debug - With Setup|Any CPU.ActiveCfg = Debug|x86
{DFB3FE30-18EA-4216-8D92-11DF9C8D50F1}.Debug - With Setup|Any CPU.Build.0 = Debug|x86
{DFB3FE30-18EA-4216-8D92-11DF9C8D50F1}.Debug - With Setup|Mixed Platforms.ActiveCfg = Debug|x86
{DFB3FE30-18EA-4216-8D92-11DF9C8D50F1}.Debug - With Setup|Mixed Platforms.Build.0 = Debug|x86
{DFB3FE30-18EA-4216-8D92-11DF9C8D50F1}.Debug - With Setup|x64.ActiveCfg = Debug|x86
{DFB3FE30-18EA-4216-8D92-11DF9C8D50F1}.Debug - With Setup|x64.Build.0 = Debug|x86
{DFB3FE30-18EA-4216-8D92-11DF9C8D50F1}.Debug - With Setup|x86.ActiveCfg = Debug|x86
{DFB3FE30-18EA-4216-8D92-11DF9C8D50F1}.Debug - With Setup|x86.Build.0 = Debug|x86
{DFB3FE30-18EA-4216-8D92-11DF9C8D50F1}.Debug|Any CPU.ActiveCfg = Debug|x86
{DFB3FE30-18EA-4216-8D92-11DF9C8D50F1}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
{DFB3FE30-18EA-4216-8D92-11DF9C8D50F1}.Debug|x64.ActiveCfg = Debug|x86
{DFB3FE30-18EA-4216-8D92-11DF9C8D50F1}.Debug|x64.Build.0 = Debug|x86
{DFB3FE30-18EA-4216-8D92-11DF9C8D50F1}.Debug|x86.ActiveCfg = Debug|x86
{DFB3FE30-18EA-4216-8D92-11DF9C8D50F1}.Debug|x86.Build.0 = Debug|x86
{DFB3FE30-18EA-4216-8D92-11DF9C8D50F1}.Release - With Setup|Any CPU.ActiveCfg = Release|x86
{DFB3FE30-18EA-4216-8D92-11DF9C8D50F1}.Release - With Setup|Any CPU.Build.0 = Release|x86
{DFB3FE30-18EA-4216-8D92-11DF9C8D50F1}.Release - With Setup|Mixed Platforms.ActiveCfg = Release|x86
{DFB3FE30-18EA-4216-8D92-11DF9C8D50F1}.Release - With Setup|Mixed Platforms.Build.0 = Release|x86
{DFB3FE30-18EA-4216-8D92-11DF9C8D50F1}.Release - With Setup|x64.ActiveCfg = Release|x86
{DFB3FE30-18EA-4216-8D92-11DF9C8D50F1}.Release - With Setup|x86.ActiveCfg = Release|x86
{DFB3FE30-18EA-4216-8D92-11DF9C8D50F1}.Release - With Setup|x86.Build.0 = Release|x86
{DFB3FE30-18EA-4216-8D92-11DF9C8D50F1}.Release|Any CPU.ActiveCfg = Release|x86
{DFB3FE30-18EA-4216-8D92-11DF9C8D50F1}.Release|Mixed Platforms.ActiveCfg = Release|x86
{DFB3FE30-18EA-4216-8D92-11DF9C8D50F1}.Release|x64.ActiveCfg = Release|x86
{DFB3FE30-18EA-4216-8D92-11DF9C8D50F1}.Release|x86.ActiveCfg = Release|x86
{DFB3FE30-18EA-4216-8D92-11DF9C8D50F1}.Release|x86.Build.0 = Release|x86
{5E5C13A5-635E-4310-A653-0F9760F46935}.Debug - With Setup|Any CPU.ActiveCfg = Debug|x86
{5E5C13A5-635E-4310-A653-0F9760F46935}.Debug - With Setup|Any CPU.Build.0 = Debug|x86
{5E5C13A5-635E-4310-A653-0F9760F46935}.Debug - With Setup|Mixed Platforms.ActiveCfg = Debug|x86
{5E5C13A5-635E-4310-A653-0F9760F46935}.Debug - With Setup|Mixed Platforms.Build.0 = Debug|x86
{5E5C13A5-635E-4310-A653-0F9760F46935}.Debug - With Setup|x64.ActiveCfg = Debug|x86
{5E5C13A5-635E-4310-A653-0F9760F46935}.Debug - With Setup|x64.Build.0 = Debug|x86
{5E5C13A5-635E-4310-A653-0F9760F46935}.Debug - With Setup|x86.ActiveCfg = Debug|x86
{5E5C13A5-635E-4310-A653-0F9760F46935}.Debug - With Setup|x86.Build.0 = Debug|x86
{5E5C13A5-635E-4310-A653-0F9760F46935}.Debug|Any CPU.ActiveCfg = Debug|x86
{5E5C13A5-635E-4310-A653-0F9760F46935}.Debug|Mixed Platforms.ActiveCfg = Debug|x86
{5E5C13A5-635E-4310-A653-0F9760F46935}.Debug|x64.ActiveCfg = Debug|x86
{5E5C13A5-635E-4310-A653-0F9760F46935}.Debug|x64.Build.0 = Debug|x86
{5E5C13A5-635E-4310-A653-0F9760F46935}.Debug|x86.ActiveCfg = Debug|x86
{5E5C13A5-635E-4310-A653-0F9760F46935}.Debug|x86.Build.0 = Debug|x86
{5E5C13A5-635E-4310-A653-0F9760F46935}.Release - With Setup|Any CPU.ActiveCfg = Release|x86
{5E5C13A5-635E-4310-A653-0F9760F46935}.Release - With Setup|Any CPU.Build.0 = Release|x86
{5E5C13A5-635E-4310-A653-0F9760F46935}.Release - With Setup|Mixed Platforms.ActiveCfg = Release|x86
{5E5C13A5-635E-4310-A653-0F9760F46935}.Release - With Setup|Mixed Platforms.Build.0 = Release|x86
{5E5C13A5-635E-4310-A653-0F9760F46935}.Release - With Setup|x64.ActiveCfg = Release|x86
{5E5C13A5-635E-4310-A653-0F9760F46935}.Release - With Setup|x86.ActiveCfg = Release|x86
{5E5C13A5-635E-4310-A653-0F9760F46935}.Release - With Setup|x86.Build.0 = Release|x86
{5E5C13A5-635E-4310-A653-0F9760F46935}.Release|Any CPU.ActiveCfg = Release|x86
{5E5C13A5-635E-4310-A653-0F9760F46935}.Release|Mixed Platforms.ActiveCfg = Release|x86
{5E5C13A5-635E-4310-A653-0F9760F46935}.Release|x64.ActiveCfg = Release|x86
{5E5C13A5-635E-4310-A653-0F9760F46935}.Release|x86.ActiveCfg = Release|x86
{5E5C13A5-635E-4310-A653-0F9760F46935}.Release|x86.Build.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B1D182A0-263B-4AB8-8413-56303DBD4CCC}
EndGlobalSection
EndGlobal

View File

@@ -37,16 +37,12 @@
<HintPath>$(WixExtDir)\WixNetFxExtension.dll</HintPath>
<Name>WixNetFxExtension</Name>
</WixExtension>
<WixExtension Include="WixUtilExtension">
<HintPath>$(WixExtDir)\WixUtilExtension.dll</HintPath>
<Name>WixUtilExtension</Name>
</WixExtension>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Application\FeedCenter.csproj">
<Name>FeedCenter</Name>
<Project>{bd3d12f2-de23-4466-83b1-1eb617a877a4}</Project>
<Private>True</Private>
<DoNotHarvest>True</DoNotHarvest>
<RefProjectOutputGroups>Binaries;Content;Satellites</RefProjectOutputGroups>
<RefTargetDir>INSTALLFOLDER</RefTargetDir>
</ProjectReference>
<ProjectReference Include="..\Setup\Setup.wixproj">
<Name>Setup</Name>
<Project>{dfb3fe30-18ea-4216-8d92-11df9c8d50f1}</Project>
@@ -66,12 +62,4 @@
<PropertyGroup>
<PostBuildEvent />
</PropertyGroup>
<!--
To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Wix.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
xmlns:bal="http://schemas.microsoft.com/wix/BalExtension"
xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
<?define ExecutableName="FeedCenter.exe" ?>
<?define BuildPath="..\..\Application\bin\Release\publish" ?>
<?define BuildExecutable="$(var.BuildPath)\$(var.ExecutableName)" ?>
<?define ProductName="$(fileVersion.ProductName($(var.BuildExecutable)))" ?>
<?define CompanyName="$(fileVersion.CompanyName($(var.BuildExecutable)))" ?>
<?define ProductVersion="$(fileVersion.ProductVersion($(var.BuildExecutable)))" ?>
<Bundle Name="$(var.ProductName)"
Version="$(var.ProductVersion)"
Manufacturer="$(var.CompanyName)"
UpgradeCode="5e5c13a5-635e-4310-a653-0f9760f46935"
Compressed="no">
<BootstrapperApplicationRef Id="WixExtendedBootstrapperApplication.HyperlinkLicense">
<bal:WixExtendedBootstrapperApplication LicenseUrl=""
SuppressOptionsUI="yes"
ShowVersion="yes"
LocalizationFile="Bundle.wxl"
LaunchPassive="yes"
LaunchQuiet="yes"
LaunchTarget="[LocalAppDataFolder]$(var.ProductName)\FeedCenter.exe" />
</BootstrapperApplicationRef>
<Chain>
<MsiPackage Id="FeedCenter"
SourceFile="$(var.Setup.TargetPath)"
Compressed="yes" />
</Chain>
</Bundle>
</Wix>

51
Installer/Installer.sln Normal file
View File

@@ -0,0 +1,51 @@
Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 18
VisualStudioVersion = 18.0.11018.127
MinimumVisualStudioVersion = 10.0.40219.1
Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "Setup", "Setup\Setup.wixproj", "{DFB3FE30-18EA-4216-8D92-11DF9C8D50F1}"
EndProject
Project("{930C7802-8A8C-48F9-8165-68863BCCD9DD}") = "Bootstrapper", "Bootstrapper\Bootstrapper.wixproj", "{5E5C13A5-635E-4310-A653-0F9760F46935}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|ARM64 = Debug|ARM64
Debug|x64 = Debug|x64
Debug|x86 = Debug|x86
Release|ARM64 = Release|ARM64
Release|x64 = Release|x64
Release|x86 = Release|x86
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{DFB3FE30-18EA-4216-8D92-11DF9C8D50F1}.Debug|ARM64.ActiveCfg = Debug|x86
{DFB3FE30-18EA-4216-8D92-11DF9C8D50F1}.Debug|ARM64.Build.0 = Debug|x86
{DFB3FE30-18EA-4216-8D92-11DF9C8D50F1}.Debug|x64.ActiveCfg = Debug|x86
{DFB3FE30-18EA-4216-8D92-11DF9C8D50F1}.Debug|x64.Build.0 = Debug|x86
{DFB3FE30-18EA-4216-8D92-11DF9C8D50F1}.Debug|x86.ActiveCfg = Debug|x86
{DFB3FE30-18EA-4216-8D92-11DF9C8D50F1}.Debug|x86.Build.0 = Debug|x86
{DFB3FE30-18EA-4216-8D92-11DF9C8D50F1}.Release|ARM64.ActiveCfg = Release|x86
{DFB3FE30-18EA-4216-8D92-11DF9C8D50F1}.Release|ARM64.Build.0 = Release|x86
{DFB3FE30-18EA-4216-8D92-11DF9C8D50F1}.Release|x64.ActiveCfg = Release|x86
{DFB3FE30-18EA-4216-8D92-11DF9C8D50F1}.Release|x64.Build.0 = Release|x86
{DFB3FE30-18EA-4216-8D92-11DF9C8D50F1}.Release|x86.ActiveCfg = Release|x86
{DFB3FE30-18EA-4216-8D92-11DF9C8D50F1}.Release|x86.Build.0 = Release|x86
{5E5C13A5-635E-4310-A653-0F9760F46935}.Debug|ARM64.ActiveCfg = Debug|x86
{5E5C13A5-635E-4310-A653-0F9760F46935}.Debug|ARM64.Build.0 = Debug|x86
{5E5C13A5-635E-4310-A653-0F9760F46935}.Debug|x64.ActiveCfg = Debug|x86
{5E5C13A5-635E-4310-A653-0F9760F46935}.Debug|x64.Build.0 = Debug|x86
{5E5C13A5-635E-4310-A653-0F9760F46935}.Debug|x86.ActiveCfg = Debug|x86
{5E5C13A5-635E-4310-A653-0F9760F46935}.Debug|x86.Build.0 = Debug|x86
{5E5C13A5-635E-4310-A653-0F9760F46935}.Release|ARM64.ActiveCfg = Release|x86
{5E5C13A5-635E-4310-A653-0F9760F46935}.Release|ARM64.Build.0 = Release|x86
{5E5C13A5-635E-4310-A653-0F9760F46935}.Release|x64.ActiveCfg = Release|x86
{5E5C13A5-635E-4310-A653-0F9760F46935}.Release|x64.Build.0 = Release|x86
{5E5C13A5-635E-4310-A653-0F9760F46935}.Release|x86.ActiveCfg = Release|x86
{5E5C13A5-635E-4310-A653-0F9760F46935}.Release|x86.Build.0 = Release|x86
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {A3D28F74-5EC6-4995-A173-9D03FE88AB51}
EndGlobalSection
EndGlobal

196
Installer/Setup/Product.wxs Normal file
View File

@@ -0,0 +1,196 @@
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
xmlns:netfx="http://schemas.microsoft.com/wix/NetFxExtension"
xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
<?define ExecutableName="FeedCenter.exe" ?>
<?define BuildPath="..\..\Application\bin\Release\publish" ?>
<?define BuildExecutable="$(var.BuildPath)\$(var.ExecutableName)" ?>
<?define ProductName="$(fileVersion.ProductName($(var.BuildExecutable)))" ?>
<?define CompanyName="$(fileVersion.CompanyName($(var.BuildExecutable)))" ?>
<?define ProductVersion="$(fileVersion.ProductVersion($(var.BuildExecutable)))" ?>
<?define FileDescription="$(fileVersion.FileDescription($(var.BuildExecutable)))" ?>
<Product Id="*"
Name="$(var.ProductName)"
Language="1033"
Version="$(var.ProductVersion)"
Manufacturer="$(var.CompanyName)"
UpgradeCode="47f30e4a-a861-47ac-b82d-35e4b886992a">
<Package InstallerVersion="405"
Compressed="yes"
InstallScope="perUser" />
<util:CloseApplication Id="CloseApplication"
Target="$(var.ExecutableName)"
CloseMessage="yes"
Timeout="5" />
<MajorUpgrade Schedule="afterInstallValidate"
AllowDowngrades="no"
AllowSameVersionUpgrades="no"
DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
<MediaTemplate />
<Feature Id="ProductFeature"
Title="$(var.ProductName)"
Level="1">
<ComponentGroupRef Id="ProductComponents" />
</Feature>
</Product>
<Fragment>
<Directory Id="TARGETDIR"
Name="SourceDir">
<Directory Id="LocalAppDataFolder">
<Directory Id="INSTALLFOLDER"
Name="$(var.ProductName)">
<Directory Id="SqlServerCe_32"
Name="x86" />
<Directory Id="SqlServerCe_64"
Name="amd64" />
<Directory Id="Runtimes"
Name="runtimes">
<Directory Id="Runtimes_Win_x32"
Name="win-x32">
<Directory Id="Runtimes_Win_x32_Native"
Name="native" />
</Directory>
<Directory Id="Runtimes_Win_x64"
Name="win-x64">
<Directory Id="Runtimes_Win_x64_Native"
Name="native" />
</Directory>
</Directory>
</Directory>
</Directory>
<Directory Id="ProgramMenuFolder">
<Directory Id="ApplicationProgramsFolder"
Name="$(var.ProductName)" />
</Directory>
</Directory>
</Fragment>
<Fragment>
<ComponentGroup Id="ProductComponents">
<Component Id="SqlServerCe_64"
Guid="{67DB1CF4-23F2-4252-896C-714438959C9A}"
Directory="SqlServerCe_64">
<RegistryValue Root='HKCU'
Key='Software\Feed Center\Setup'
Type='integer'
Name='SqlServerCe_64'
Value='1'
KeyPath='yes' />
<File Id="sqlceca40_64"
Source="$(var.BuildPath)\amd64\sqlceca40.dll" />
<File Id="sqlcecompact40_64"
Source="$(var.BuildPath)\amd64\sqlcecompact40.dll" />
<File Id="sqlceer40EN_64"
Source="$(var.BuildPath)\amd64\sqlceer40EN.dll" />
<File Id="sqlceme40_64"
Source="$(var.BuildPath)\amd64\sqlceme40.dll" />
<File Id="sqlceqp40_64"
Source="$(var.BuildPath)\amd64\sqlceqp40.dll" />
<File Id="sqlcese40_64"
Source="$(var.BuildPath)\amd64\sqlcese40.dll" />
<RemoveFolder Id='SqlServerCe_64'
On='uninstall' />
</Component>
<Component Id="FeedCenter"
Guid="{9B2B9C65-8E68-4F82-9AC5-16EBCAC34B9E}"
Directory="INSTALLFOLDER">
<RegistryValue Root='HKCU'
Key='Software\Feed Center\Setup'
Type='integer'
Name='FeedCenter'
Value='1'
KeyPath='yes' />
<!-- Capabilities keys for Vista/7 "Set Program Access and Defaults" -->
<RegistryValue Root="HKCU"
Key="SOFTWARE\Feed Center\Capabilities"
Name="ApplicationDescription"
Value="$(var.FileDescription)"
Type="string" />
<RegistryValue Root="HKCU"
Key="SOFTWARE\Feed Center\Capabilities"
Name="ApplicationName"
Value="$(var.ProductName)"
Type="string" />
<RegistryValue Root="HKCU"
Key="SOFTWARE\Feed Center\Capabilities\URLAssociations"
Name="feed"
Value="FeedCenter.URL.feed"
Type="string" />
<RegistryValue Root="HKCU"
Key="SOFTWARE\Feed Center\Capabilities\URLAssociations"
Name="feeds"
Value="FeedCenter.URL.feed"
Type="string" />
<RegistryValue Root="HKCU"
Key="SOFTWARE\RegisteredApplications"
Name="Feed Center"
Value="SOFTWARE\Feed Center\Capabilities"
Type="string" />
<RegistryValue Root="HKCU"
Key="Software\Classes\FeedCenter.URL.feed"
Value="URL:Feed Center Add RSS Feed"
Type="string" />
<RegistryValue Root="HKCU"
Key="Software\Classes\FeedCenter.URL.feed\DefaultIcon"
Value="[INSTALLFOLDER]$(var.ExecutableName)"
Type="string" />
<RegistryValue Root="HKCU"
Key="Software\Classes\FeedCenter.URL.feed\shell\open\command"
Value="&quot;[INSTALLFOLDER]$(var.ExecutableName)&quot; %1"
Type="string" />
<File Id="D3DCompiler_47_cor3.dll" Source="$(var.BuildPath)\D3DCompiler_47_cor3.dll" />
<File Id="FeedCenter.dll.config" Source="$(var.BuildPath)\FeedCenter.dll.config" />
<File Id="FeedCenter.exe" Source="$(var.BuildPath)\FeedCenter.exe" />
<File Id="FeedCenter.pdb" Source="$(var.BuildPath)\FeedCenter.pdb" />
<File Id="PenImc_cor3.dll" Source="$(var.BuildPath)\PenImc_cor3.dll" />
<File Id="PresentationNative_cor3.dll" Source="$(var.BuildPath)\PresentationNative_cor3.dll" />
<File Id="realm_wrappers.dll" Source="$(var.BuildPath)\realm-wrappers.dll" />
<File Id="sni.dll" Source="$(var.BuildPath)\sni.dll" />
<File Id="vcruntime140_cor3.dll" Source="$(var.BuildPath)\vcruntime140_cor3.dll" />
<File Id="wpfgfx_cor3.dll" Source="$(var.BuildPath)\wpfgfx_cor3.dll" />
<RemoveFolder Id='INSTALLFOLDER'
On='uninstall' />
</Component>
<Component Id="ApplicationShortcut"
Guid="{63CF0995-E117-4BB9-9077-76F570FDFAA9}"
Directory="ApplicationProgramsFolder">
<Shortcut Id="StartMenuShortcut"
Directory="ApplicationProgramsFolder"
Advertise="no"
Name="$(var.ProductName)"
Target="[INSTALLFOLDER]\$(var.ExecutableName)"
WorkingDirectory="INSTALLFOLDER" />
<RemoveFolder Id="ApplicationProgramsFolder"
On="uninstall" />
<RegistryValue Root="HKCU"
Key="Software\Feed Center\Setup"
Name="Shortcut"
Type="integer"
Value="1"
KeyPath="yes" />
</Component>
</ComponentGroup>
</Fragment>
</Wix>

View File

@@ -42,16 +42,6 @@
<Name>WixNetFxExtension</Name>
</WixExtension>
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Application\FeedCenter.csproj">
<Name>FeedCenter</Name>
<Project>{bd3d12f2-de23-4466-83b1-1eb617a877a4}</Project>
<Private>True</Private>
<DoNotHarvest>True</DoNotHarvest>
<RefProjectOutputGroups>Binaries;Content;Satellites</RefProjectOutputGroups>
<RefTargetDir>INSTALLFOLDER</RefTargetDir>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<Content Include="packages.config" />
</ItemGroup>

View File

@@ -1,377 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi"
xmlns:netfx="http://schemas.microsoft.com/wix/NetFxExtension"
xmlns:util="http://schemas.microsoft.com/wix/UtilExtension">
<?define ProductName="$(fileVersion.ProductName($(var.FeedCenter.TargetPath)))" ?>
<?define CompanyName="$(fileVersion.CompanyName($(var.FeedCenter.TargetPath)))" ?>
<?define ProductVersion="$(fileVersion.ProductVersion($(var.FeedCenter.TargetPath)))" ?>
<?define FileDescription="$(fileVersion.FileDescription($(var.FeedCenter.TargetPath)))" ?>
<Product Id="*"
Name="$(var.ProductName)"
Language="1033"
Version="$(var.ProductVersion)"
Manufacturer="$(var.CompanyName)"
UpgradeCode="47f30e4a-a861-47ac-b82d-35e4b886992a">
<Package InstallerVersion="405"
Compressed="yes"
InstallScope="perUser" />
<util:CloseApplication Id="CloseApplication"
Target="FeedCenter.exe"
CloseMessage="yes"
Timeout="5" />
<MajorUpgrade Schedule="afterInstallValidate"
AllowDowngrades="no"
AllowSameVersionUpgrades="no"
DowngradeErrorMessage="A newer version of [ProductName] is already installed." />
<MediaTemplate />
<Feature Id="ProductFeature"
Title="$(var.ProductName)"
Level="1">
<ComponentGroupRef Id="ProductComponents" />
</Feature>
</Product>
<Fragment>
<Directory Id="TARGETDIR"
Name="SourceDir">
<Directory Id="LocalAppDataFolder">
<Directory Id="INSTALLFOLDER"
Name="$(var.ProductName)">
<Directory Id="SqlServerCe_32"
Name="x86" />
<Directory Id="SqlServerCe_64"
Name="amd64" />
<Directory Id="Runtimes"
Name="runtimes">
<Directory Id="Runtimes_Win_x32"
Name="win-x32">
<Directory Id="Runtimes_Win_x32_Native"
Name="native" />
</Directory>
<Directory Id="Runtimes_Win_x64"
Name="win-x64">
<Directory Id="Runtimes_Win_x64_Native"
Name="native" />
</Directory>
</Directory>
</Directory>
</Directory>
<Directory Id="ProgramMenuFolder">
<Directory Id="ApplicationProgramsFolder"
Name="$(var.ProductName)" />
</Directory>
</Directory>
</Fragment>
<Fragment>
<ComponentGroup Id="ProductComponents">
<Component Id="SqlServerCe_32"
Guid="{800607E9-65ED-489F-83A2-C73AA36A9D1D}"
Directory="SqlServerCe_32">
<RegistryValue Root='HKCU'
Key='Software\Feed Center\Setup'
Type='integer'
Name='SqlServerCe_32'
Value='1'
KeyPath='yes' />
<File Id="sqlceca40_32"
Source="$(var.FeedCenter.TargetDir)\x86\sqlceca40.dll" />
<File Id="sqlcecompact40_32"
Source="$(var.FeedCenter.TargetDir)\x86\sqlcecompact40.dll" />
<File Id="sqlceer40EN_32"
Source="$(var.FeedCenter.TargetDir)\x86\sqlceer40EN.dll" />
<File Id="sqlceme40_32"
Source="$(var.FeedCenter.TargetDir)\x86\sqlceme40.dll" />
<File Id="sqlceqp40_32"
Source="$(var.FeedCenter.TargetDir)\x86\sqlceqp40.dll" />
<File Id="sqlcese40_32"
Source="$(var.FeedCenter.TargetDir)\x86\sqlcese40.dll" />
<RemoveFolder Id='SqlServerCe_32'
On='uninstall' />
</Component>
<Component Id="SqlServerCe_64"
Guid="{67DB1CF4-23F2-4252-896C-714438959C9A}"
Directory="SqlServerCe_64">
<RegistryValue Root='HKCU'
Key='Software\Feed Center\Setup'
Type='integer'
Name='SqlServerCe_64'
Value='1'
KeyPath='yes' />
<File Id="sqlceca40_64"
Source="$(var.FeedCenter.TargetDir)\amd64\sqlceca40.dll" />
<File Id="sqlcecompact40_64"
Source="$(var.FeedCenter.TargetDir)\amd64\sqlcecompact40.dll" />
<File Id="sqlceer40EN_64"
Source="$(var.FeedCenter.TargetDir)\amd64\sqlceer40EN.dll" />
<File Id="sqlceme40_64"
Source="$(var.FeedCenter.TargetDir)\amd64\sqlceme40.dll" />
<File Id="sqlceqp40_64"
Source="$(var.FeedCenter.TargetDir)\amd64\sqlceqp40.dll" />
<File Id="sqlcese40_64"
Source="$(var.FeedCenter.TargetDir)\amd64\sqlcese40.dll" />
<RemoveFolder Id='SqlServerCe_64'
On='uninstall' />
</Component>
<Component Id="Runtimes_Win_x32_Native"
Guid="{E07B7F53-9FA0-426D-8ADF-A9FFF4E1ED02}"
Directory="Runtimes_Win_x32_Native">
<RegistryValue Root="HKCU"
Key="Software\Feed Center\Setup"
Type="integer"
Name="Runtimes_Win_x32"
Value="1"
KeyPath="yes" />
<File Id="realm_wrappers.dll_x32"
Source="$(var.FeedCenter.TargetDir)\runtimes\win-x86\native\realm-wrappers.dll" />
<File Id="sni.dll_x32"
Source="$(var.FeedCenter.TargetDir)\runtimes\win-x86\native\sni.dll" />
</Component>
<Component Id="Runtimes_Win_x64_Native"
Guid="{566D640B-F34D-4400-9B86-10EFD669A8B7}"
Directory="Runtimes_Win_x64_Native">
<RegistryValue Root="HKCU"
Key="Software\Feed Center\Setup"
Type="integer"
Name="Runtimes_Win_x64"
Value="1"
KeyPath="yes" />
<File Id="realm_wrappers.dll_x64"
Source="$(var.FeedCenter.TargetDir)\runtimes\win-x64\native\realm-wrappers.dll" />
<File Id="sni.dll_x64"
Source="$(var.FeedCenter.TargetDir)\runtimes\win-x64\native\sni.dll" />
</Component>
<Component Id="FeedCenter"
Guid="{9B2B9C65-8E68-4F82-9AC5-16EBCAC34B9E}"
Directory="INSTALLFOLDER">
<RegistryValue Root='HKCU'
Key='Software\Feed Center\Setup'
Type='integer'
Name='FeedCenter'
Value='1'
KeyPath='yes' />
<!-- Capabilities keys for Vista/7 "Set Program Access and Defaults" -->
<RegistryValue Root="HKCU"
Key="SOFTWARE\Feed Center\Capabilities"
Name="ApplicationDescription"
Value="$(var.FileDescription)"
Type="string" />
<RegistryValue Root="HKCU"
Key="SOFTWARE\Feed Center\Capabilities"
Name="ApplicationName"
Value="$(var.ProductName)"
Type="string" />
<RegistryValue Root="HKCU"
Key="SOFTWARE\Feed Center\Capabilities\URLAssociations"
Name="feed"
Value="FeedCenter.URL.feed"
Type="string" />
<RegistryValue Root="HKCU"
Key="SOFTWARE\Feed Center\Capabilities\URLAssociations"
Name="feeds"
Value="FeedCenter.URL.feed"
Type="string" />
<RegistryValue Root="HKCU"
Key="SOFTWARE\RegisteredApplications"
Name="Feed Center"
Value="SOFTWARE\Feed Center\Capabilities"
Type="string" />
<RegistryValue Root="HKCU"
Key="Software\Classes\FeedCenter.URL.feed"
Value="URL:Feed Center Add RSS Feed"
Type="string" />
<RegistryValue Root="HKCU"
Key="Software\Classes\FeedCenter.URL.feed\DefaultIcon"
Value="[INSTALLFOLDER]FeedCenter.exe"
Type="string" />
<RegistryValue Root="HKCU"
Key="Software\Classes\FeedCenter.URL.feed\shell\open\command"
Value="&quot;[INSTALLFOLDER]FeedCenter.exe&quot; %1"
Type="string" />
<File Id="ChrisKaczor.ApplicationUpdate.dll"
Source="$(var.FeedCenter.TargetDir)\ChrisKaczor.ApplicationUpdate.dll" />
<File Id="ChrisKaczor.GenericSettingsProvider.dll"
Source="$(var.FeedCenter.TargetDir)\ChrisKaczor.GenericSettingsProvider.dll" />
<File Id="ChrisKaczor.InstalledBrowsers.dll"
Source="$(var.FeedCenter.TargetDir)\ChrisKaczor.InstalledBrowsers.dll" />
<File Id="ChrisKaczor.Wpf.Application.SingleInstance.dll"
Source="$(var.FeedCenter.TargetDir)\ChrisKaczor.Wpf.Application.SingleInstance.dll" />
<File Id="ChrisKaczor.Wpf.Application.StartWithWindows.dll"
Source="$(var.FeedCenter.TargetDir)\ChrisKaczor.Wpf.Application.StartWithWindows.dll" />
<File Id="ChrisKaczor.Wpf.Controls.HtmlTextBlock.dll"
Source="$(var.FeedCenter.TargetDir)\ChrisKaczor.Wpf.Controls.HtmlTextBlock.dll" />
<File Id="ChrisKaczor.Wpf.Controls.Link.dll"
Source="$(var.FeedCenter.TargetDir)\ChrisKaczor.Wpf.Controls.Link.dll" />
<File Id="ChrisKaczor.Wpf.Controls.Toolbar.dll"
Source="$(var.FeedCenter.TargetDir)\ChrisKaczor.Wpf.Controls.Toolbar.dll" />
<File Id="ChrisKaczor.Wpf.Validation.dll"
Source="$(var.FeedCenter.TargetDir)\ChrisKaczor.Wpf.Validation.dll" />
<File Id="ChrisKaczor.Wpf.Windows.ControlBox.dll"
Source="$(var.FeedCenter.TargetDir)\ChrisKaczor.Wpf.Windows.ControlBox.dll" />
<File Id="ChrisKaczor.Wpf.Windows.SnappingWindow.dll"
Source="$(var.FeedCenter.TargetDir)\ChrisKaczor.Wpf.Windows.SnappingWindow.dll" />
<File Id="ControlzEx.dll"
Source="$(var.FeedCenter.TargetDir)\ControlzEx.dll" />
<File Id="Dapper.dll"
Source="$(var.FeedCenter.TargetDir)\Dapper.dll" />
<File Id="DebounceThrottle.dll"
Source="$(var.FeedCenter.TargetDir)\DebounceThrottle.dll" />
<File Id="FeedCenter.deps.json"
Source="$(var.FeedCenter.TargetDir)\FeedCenter.deps.json" />
<File Id="FeedCenter.dll"
Source="$(var.FeedCenter.TargetDir)\FeedCenter.dll" />
<File Id="FeedCenter.dll.config"
Source="$(var.FeedCenter.TargetDir)\FeedCenter.dll.config" />
<File Id="FeedCenter.exe"
Source="$(var.FeedCenter.TargetDir)\FeedCenter.exe" />
<File Id="FeedCenter.runtimeconfig.json"
Source="$(var.FeedCenter.TargetDir)\FeedCenter.runtimeconfig.json" />
<File Id="H.Formatters.BinaryFormatter.dll"
Source="$(var.FeedCenter.TargetDir)\H.Formatters.BinaryFormatter.dll" />
<File Id="H.Formatters.dll"
Source="$(var.FeedCenter.TargetDir)\H.Formatters.dll" />
<File Id="H.GeneratedIcons.System.Drawing.dll"
Source="$(var.FeedCenter.TargetDir)\H.GeneratedIcons.System.Drawing.dll" />
<File Id="H.NotifyIcon.dll"
Source="$(var.FeedCenter.TargetDir)\H.NotifyIcon.dll" />
<File Id="H.NotifyIcon.Wpf.dll"
Source="$(var.FeedCenter.TargetDir)\H.NotifyIcon.Wpf.dll" />
<File Id="H.Pipes.dll"
Source="$(var.FeedCenter.TargetDir)\H.Pipes.dll" />
<File Id="HtmlAgilityPack.dll"
Source="$(var.FeedCenter.TargetDir)\HtmlAgilityPack.dll" />
<File Id="HtmlTextWriter.dll"
Source="$(var.FeedCenter.TargetDir)\HtmlTextWriter.dll" />
<File Id="JetBrains.Annotations.dll"
Source="$(var.FeedCenter.TargetDir)\JetBrains.Annotations.dll" />
<File Id="MahApps.Metro.dll"
Source="$(var.FeedCenter.TargetDir)\MahApps.Metro.dll" />
<File Id="Microsoft.Bcl.AsyncInterfaces.dll"
Source="$(var.FeedCenter.TargetDir)\Microsoft.Bcl.AsyncInterfaces.dll" />
<File Id="Microsoft.Extensions.ObjectPool.dll"
Source="$(var.FeedCenter.TargetDir)\Microsoft.Extensions.ObjectPool.dll" />
<File Id="Microsoft.Xaml.Behaviors.dll"
Source="$(var.FeedCenter.TargetDir)\Microsoft.Xaml.Behaviors.dll" />
<File Id="MongoDB.Bson.dll"
Source="$(var.FeedCenter.TargetDir)\MongoDB.Bson.dll" />
<File Id="NameBasedGrid.dll"
Source="$(var.FeedCenter.TargetDir)\NameBasedGrid.dll" />
<File Id="Newtonsoft.Json.dll"
Source="$(var.FeedCenter.TargetDir)\Newtonsoft.Json.dll" />
<File Id="Realm.dll"
Source="$(var.FeedCenter.TargetDir)\Realm.dll" />
<File Id="Remotion.Linq.dll"
Source="$(var.FeedCenter.TargetDir)\Remotion.Linq.dll" />
<File Id="Serilog.dll"
Source="$(var.FeedCenter.TargetDir)\Serilog.dll" />
<File Id="Serilog.Enrichers.Thread.dll"
Source="$(var.FeedCenter.TargetDir)\Serilog.Enrichers.Thread.dll" />
<File Id="Serilog.Sinks.Console.dll"
Source="$(var.FeedCenter.TargetDir)\Serilog.Sinks.Console.dll" />
<File Id="Serilog.Sinks.File.dll"
Source="$(var.FeedCenter.TargetDir)\Serilog.Sinks.File.dll" />
<File Id="System.ComponentModel.Composition.dll"
Source="$(var.FeedCenter.TargetDir)\System.ComponentModel.Composition.dll" />
<File Id="System.ComponentModel.Composition.Registration.dll"
Source="$(var.FeedCenter.TargetDir)\System.ComponentModel.Composition.Registration.dll" />
<File Id="System.Data.Odbc.dll"
Source="$(var.FeedCenter.TargetDir)\System.Data.Odbc.dll" />
<File Id="System.Data.OleDb.dll"
Source="$(var.FeedCenter.TargetDir)\System.Data.OleDb.dll" />
<File Id="System.Data.SqlClient.dll"
Source="$(var.FeedCenter.TargetDir)\System.Data.SqlClient.dll" />
<File Id="System.Data.SqlServerCe.dll"
Source="$(var.FeedCenter.TargetDir)\System.Data.SqlServerCe.dll" />
<File Id="System.DirectoryServices.AccountManagement.dll"
Source="$(var.FeedCenter.TargetDir)\System.DirectoryServices.AccountManagement.dll" />
<File Id="System.DirectoryServices.dll"
Source="$(var.FeedCenter.TargetDir)\System.DirectoryServices.dll" />
<File Id="System.DirectoryServices.Protocols.dll"
Source="$(var.FeedCenter.TargetDir)\System.DirectoryServices.Protocols.dll" />
<File Id="System.IO.Ports.dll"
Source="$(var.FeedCenter.TargetDir)\System.IO.Ports.dll" />
<File Id="System.Management.dll"
Source="$(var.FeedCenter.TargetDir)\System.Management.dll" />
<File Id="System.Private.ServiceModel.dll"
Source="$(var.FeedCenter.TargetDir)\System.Private.ServiceModel.dll" />
<File Id="System.Reflection.Context.dll"
Source="$(var.FeedCenter.TargetDir)\System.Reflection.Context.dll" />
<File Id="System.Runtime.Caching.dll"
Source="$(var.FeedCenter.TargetDir)\System.Runtime.Caching.dll" />
<File Id="System.Security.Cryptography.Pkcs.dll"
Source="$(var.FeedCenter.TargetDir)\System.Security.Cryptography.Pkcs.dll" />
<File Id="System.Security.Cryptography.ProtectedData.dll"
Source="$(var.FeedCenter.TargetDir)\System.Security.Cryptography.ProtectedData.dll" />
<File Id="System.Security.Cryptography.Xml.dll"
Source="$(var.FeedCenter.TargetDir)\System.Security.Cryptography.Xml.dll" />
<File Id="System.ServiceModel.dll"
Source="$(var.FeedCenter.TargetDir)\System.ServiceModel.dll" />
<File Id="System.ServiceModel.Duplex.dll"
Source="$(var.FeedCenter.TargetDir)\System.ServiceModel.Duplex.dll" />
<File Id="System.ServiceModel.Http.dll"
Source="$(var.FeedCenter.TargetDir)\System.ServiceModel.Http.dll" />
<File Id="System.ServiceModel.NetTcp.dll"
Source="$(var.FeedCenter.TargetDir)\System.ServiceModel.NetTcp.dll" />
<File Id="System.ServiceModel.Primitives.dll"
Source="$(var.FeedCenter.TargetDir)\System.ServiceModel.Primitives.dll" />
<File Id="System.ServiceModel.Security.dll"
Source="$(var.FeedCenter.TargetDir)\System.ServiceModel.Security.dll" />
<File Id="System.ServiceModel.Syndication.dll"
Source="$(var.FeedCenter.TargetDir)\System.ServiceModel.Syndication.dll" />
<File Id="System.ServiceProcess.ServiceController.dll"
Source="$(var.FeedCenter.TargetDir)\System.ServiceProcess.ServiceController.dll" />
<File Id="System.Speech.dll"
Source="$(var.FeedCenter.TargetDir)\System.Speech.dll" />
<File Id="System.Threading.AccessControl.dll"
Source="$(var.FeedCenter.TargetDir)\System.Threading.AccessControl.dll" />
<File Id="System.Web.Services.Description.dll"
Source="$(var.FeedCenter.TargetDir)\System.Web.Services.Description.dll" />
<File Id="WpfScreenHelper.dll"
Source="$(var.FeedCenter.TargetDir)\WpfScreenHelper.dll" />
<RemoveFolder Id='INSTALLFOLDER'
On='uninstall' />
</Component>
<Component Id="ApplicationShortcut"
Guid="{63CF0995-E117-4BB9-9077-76F570FDFAA9}"
Directory="ApplicationProgramsFolder">
<Shortcut Id="StartMenuShortcut"
Directory="ApplicationProgramsFolder"
Advertise="no"
Name="$(var.ProductName)"
Target="[INSTALLFOLDER]\$(var.FeedCenter.TargetName)"
WorkingDirectory="INSTALLFOLDER" />
<RemoveFolder Id="ApplicationProgramsFolder"
On="uninstall" />
<RegistryValue Root="HKCU"
Key="Software\Feed Center\Setup"
Name="Shortcut"
Type="integer"
Value="1"
KeyPath="yes" />
</Component>
</ComponentGroup>
</Fragment>
</Wix>

View File

@@ -3,22 +3,37 @@ pull_requests:
do_not_increment_build_number: true
skip_tags: true
image: Visual Studio 2022
configuration: Release - With Setup
platform: x86
configuration: Release
platform: x86
assembly_info:
patch: true
file: 'Properties\AssemblyInfo.cs'
assembly_version: '{version}'
assembly_file_version: '{version}'
assembly_version: "{version}"
assembly_file_version: "{version}"
before_build:
- ps: nuget restore .\Installer\
- ps: dotnet publish .\Application\ /p:PublishProfile=Properties\PublishProfiles\x64.pubxml
build:
project: FeedCenter.sln
project: Installer\Installer.sln
verbosity: minimal
artifacts:
- path: Bootstrapper\bin\Release\FeedCenterSetup.exe
name: Release
deploy:
- provider: Environment
name: GitHub
before_build:
- cmd: nuget restore
- cmd: nuget restore Application\FeedCenter.csproj
- path: Installer\Bootstrapper\bin\Release\FeedCenterSetup.exe
name: Release
for:
- branches:
only:
- main
deploy:
- provider: Environment
name: GitHub
prerelease: false
- branches:
only:
- prerelease
version: 1.2.0.{build}
deploy:
- provider: Environment
name: GitHub
prerelease: true
tag: $(version)-prerelease