Commit 4c1711b7 authored by HankG's avatar HankG

Merge branch 'person-db-importer' into 'develop'

Release PR2

See merge request !5
parents f20e5558 56f5926e
Pipeline #99113884 failed with stage
in 60 minutes
......@@ -22,7 +22,7 @@
<AvaloniaResource Include="Assets\**" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia" Version="0.9.0-preview7" />
<PackageReference Include="Avalonia" Version="0.9.0-preview8" />
<PackageReference Include="Avalonia.Desktop" Version="0.9.0-preview7" />
<PackageReference Include="Avalonia.ReactiveUI" Version="0.9.0-preview7" />
<PackageReference Include="MySocialPortalLib" Version="1.0.0-alpha2" />
......
......@@ -12,10 +12,12 @@ namespace MySocialPortalDesktop.Services
public static class DefaultValueService
{
private const string DefaultProfileImageName = "default_profile_icon.png";
private static Lazy<Bitmap> DefaultProfileImageLazy = new Lazy<Bitmap>(BuildDefaultProfileImage);
public static Bitmap DefaultProfileImage = DefaultProfileImageLazy.Value;
private static readonly Lazy<Bitmap> DefaultProfileImageLazy = new Lazy<Bitmap>(BuildDefaultProfileImage);
public static Bitmap DefaultProfileImage => DefaultProfileImageLazy.Value;
public static string FavoriteListName = "Favorites";
private static Bitmap BuildDefaultProfileImage()
{
......@@ -35,6 +37,5 @@ namespace MySocialPortalDesktop.Services
var posts = RepositoryFactory.Instance.AllPostsRepository.GetPosts(maxPosts);
return new PostTimelineViewModel(posts);
}
}
}
\ No newline at end of file
using System;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
......@@ -7,6 +8,7 @@ using JetBrains.Annotations;
using MySocialPortalDesktop.Factory;
using MySocialPortalDesktop.Services;
using MySocialPortalLib.Model;
using MySocialPortalLib.Service;
namespace MySocialPortalDesktop.Util
{
......@@ -22,8 +24,12 @@ namespace MySocialPortalDesktop.Util
}
var screenName = "";
person.SocialMediaAccounts[StandardSocialNetworkNames.Twitter]
?.AdditionalProperties.TryGetValue(screenNameKey, out screenName);
var sma = person.SocialMediaAccounts;
if (sma.ContainsKey(StandardSocialNetworkNames.Twitter))
{
person.SocialMediaAccounts[StandardSocialNetworkNames.Twitter]
?.AdditionalProperties.TryGetValue(screenNameKey, out screenName);
}
return string.IsNullOrWhiteSpace(screenName) ? unknownValue : screenName;
}
......@@ -38,7 +44,12 @@ namespace MySocialPortalDesktop.Util
try
{
var profileImageUrl = person.SocialMediaAccounts.First().Value.ProfilePhotoPath;
if (!string.IsNullOrWhiteSpace(profileImageUrl))
if (string.IsNullOrWhiteSpace(profileImageUrl))
{
return DefaultValueService.DefaultProfileImage;
}
if (Uri.IsWellFormedUriString(profileImageUrl, UriKind.Absolute))
{
var cancelToken = new CancellationTokenSource(2000);
var task = ServiceFactory.Instance.ProfileImageService
......@@ -53,6 +64,12 @@ namespace MySocialPortalDesktop.Util
}
}
}
var localImagePath = Path.Combine(DirectoryServices.Instance.ProfileImagesDirectory(), profileImageUrl);
if (File.Exists(localImagePath))
{
return new Bitmap(localImagePath);
}
}
catch (Exception e)
{
......
......@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
......@@ -63,9 +64,80 @@ namespace MySocialPortalDesktop.ViewModels
Console.WriteLine("Will import posts when get better post processor");
}
public async void ImportPeople()
{
var app = Application.Current.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime;
var window = app.MainWindow;
var fileDialog = new OpenFileDialog
{
Directory = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments),
Title = "Import People Database",
AllowMultiple = false,
Filters = new List<FileDialogFilter>
{
new FileDialogFilter
{
Name = "People JSON",
Extensions = new List<string>{"json"}
}
}
};
var result = await fileDialog.ShowAsync(window).ConfigureAwait(false);
await ImportPeopleJsonToDatabase(result?.First()).ConfigureAwait(false);
PeopleListViewModel.ShowAll(true);
Console.WriteLine("Done importing people");
}
private List<Post> ImportedPosts { get; }
private async Task<bool> ImportPeopleJsonToDatabase(string path)
{
if (path == null)
{
Console.WriteLine("Path null not doing an import");
return false;
}
if (!File.Exists(path))
{
Console.WriteLine($"Requested JSON file doesn't exist or don't have read permissions: {path}");
}
try
{
Console.WriteLine("Read in JSON");
var peopleFromJson = await Task.Run(() => System.Text.Json.JsonSerializer
.Deserialize<List<Person>>(File.ReadAllText(path)))
.ConfigureAwait(false);
Console.WriteLine("Load all people from DB");
var peopleFromDb = await Task.Run(() => RepositoryFactory.Instance.MainPeopleRepository
.ListAll())
.ConfigureAwait(false);
Console.WriteLine("Merge two sets of people");
var (newPersons, mergedPersons, _) = PersonDatabaseMergingUtils.Merge(peopleFromDb, peopleFromJson);
Console.WriteLine($"New People: {newPersons?.Count}");
Console.WriteLine($"Merged People: {mergedPersons?.Count}");
Console.WriteLine("Write new people to the database");
await Task.Run(() => newPersons.ToList()
.ForEach(p => RepositoryFactory.Instance.MainPeopleRepository.AddPerson(p)))
.ConfigureAwait(false);
Console.WriteLine("Update existing people in database");
await Task.Run(() => mergedPersons.ToList()
.ForEach(p => RepositoryFactory.Instance.MainPeopleRepository.UpdatePerson(p)))
.ConfigureAwait(false);
Console.WriteLine("Update People list view");
}
catch (Exception e)
{
Console.WriteLine(e);
return false;
}
return true;
}
}
}
......@@ -5,6 +5,7 @@ using System.Globalization;
using System.Linq;
using MySocialPortalDesktop.Comparer;
using MySocialPortalDesktop.Factory;
using MySocialPortalLib.Model;
using ReactiveUI;
namespace MySocialPortalDesktop.ViewModels
......@@ -26,15 +27,11 @@ namespace MySocialPortalDesktop.ViewModels
public PeopleListViewModel(PostTimelineViewModel relatedTimelineViewModel)
{
var allPeople = RepositoryFactory.Instance.MainPeopleRepository.ListAll();
var peopleVms = allPeople?.Select(p => new PersonViewModel(p)).ToList() ?? new List<PersonViewModel>();
if (relatedTimelineViewModel != null)
{
peopleVms.ForEach(p => p.TimelineToDrive = relatedTimelineViewModel);
}
RelatedTimelineViewModel = relatedTimelineViewModel;
var peopleVms = GetAllPersonsFromDatabase();
People = new ObservableCollection<PersonViewModel>(peopleVms);
FullPeopleList = new List<PersonViewModel>(peopleVms);
ListFilteredPeople = new List<PersonViewModel>(peopleVms);
CurrentViewFilter = FilterText = "";
}
......@@ -61,13 +58,38 @@ namespace MySocialPortalDesktop.ViewModels
UpdateSortedView();
}
public void ShowAll(bool resetFromDb = false)
{
if (resetFromDb)
{
FullPeopleList.Clear();
FullPeopleList.AddRange(GetAllPersonsFromDatabase());
}
ListFilteredPeople.Clear();
ListFilteredPeople.AddRange(FullPeopleList);
UpdateViewFilter(true);
}
public void ToggleVisibleList(string list)
{
var idsInList = RepositoryFactory.Instance.ListsRepository.GetAllIdsForList(list).ToHashSet();
var peopleVmsInList = FullPeopleList.FindAll(p => idsInList.Contains(p.Person.Id));
ListFilteredPeople.Clear();
ListFilteredPeople.AddRange(peopleVmsInList);
UpdateViewFilter(true);
}
private IComparer<PersonViewModel> CurrentComparer { get; set; }
private string CurrentViewFilter { get; set; }
private List<PersonViewModel> ListFilteredPeople { get; }
private List<PersonViewModel> FullPeopleList { get; }
private PostTimelineViewModel RelatedTimelineViewModel { get; }
private void UpdateSortedView()
{
if(CurrentComparer == null)
......@@ -90,7 +112,7 @@ namespace MySocialPortalDesktop.ViewModels
CurrentViewFilter = FilterText;
var filter = FilterText.ToUpperInvariant();
var filteredPeople = FilterText.Length == 0 ? new List<PersonViewModel>(FullPeopleList)
var filteredPeople = FilterText.Length == 0 ? new List<PersonViewModel>(ListFilteredPeople)
: FullPeopleList.FindAll(
f => f.DisplayName.ToUpperInvariant().Contains(filter, StringComparison.InvariantCulture)
|| f.DisplayName.Contains(filter, StringComparison.InvariantCulture));
......@@ -103,5 +125,16 @@ namespace MySocialPortalDesktop.ViewModels
filteredPeople.ForEach(f => People.Add(f));
}
private List<PersonViewModel> GetAllPersonsFromDatabase()
{
var allPeople = RepositoryFactory.Instance.MainPeopleRepository.ListAll();
var peopleVms = allPeople?.Select(p => new PersonViewModel(p)).ToList() ?? new List<PersonViewModel>();
if (RelatedTimelineViewModel != null)
{
peopleVms.ForEach(p => p.TimelineToDrive = RelatedTimelineViewModel);
}
return peopleVms;
}
}
}
\ No newline at end of file
using System;
using System.Linq;
using Avalonia.Media.Imaging;
using MySocialPortalDesktop.Factory;
using MySocialPortalDesktop.Services;
using MySocialPortalDesktop.Util;
using MySocialPortalLib.Model;
......@@ -15,6 +16,7 @@ namespace MySocialPortalDesktop.ViewModels
private Bitmap _displayIcon;
private Person _person;
private PostTimelineViewModel _timelineToDrive;
private bool _isFavorite;
public PostTimelineViewModel TimelineToDrive
{
......@@ -40,6 +42,12 @@ namespace MySocialPortalDesktop.ViewModels
set => this.RaiseAndSetIfChanged(ref _displayIcon, value);
}
public bool IsFavorite
{
get => _isFavorite;
set => this.RaiseAndSetIfChanged(ref _isFavorite, value);
}
public Person Person
{
get => _person;
......@@ -60,12 +68,27 @@ namespace MySocialPortalDesktop.ViewModels
Console.WriteLine($"Change timeline to be for user {Person.Name}");
TimelineToDrive?.ConfigureSources(Person);
}
public void AddToFavorites()
{
RepositoryFactory.Instance.ListsRepository.Add(Person.Id, DefaultValueService.FavoriteListName);
IsFavorite = true;
}
public void RemoveFromFavorites()
{
RepositoryFactory.Instance.ListsRepository.RemoveIdFromList(Person.Id, DefaultValueService.FavoriteListName);
IsFavorite = false;
}
private void UpdatePersonData()
{
DisplayName = string.IsNullOrWhiteSpace(Person.Name) ? "" : Person.Name;
DisplayUsername = PersonDataGenerator.GetDefaultUserName(Person).Result;
DisplayIcon = PersonDataGenerator.LoadProfileImage(Person).Result;
IsFavorite = RepositoryFactory.Instance.ListsRepository
.GetAllListsForId(Person.Id)
.Contains(DefaultValueService.FavoriteListName);
}
}
......
......@@ -12,7 +12,7 @@ namespace MySocialPortalDesktop.ViewModels
{
public class PostTimelineViewModel : ViewModelBase
{
private const int MaxPostsQuery = 5;
private const int MaxPostsQuery = 20;
private const int MaxPostHistory = 50;
public ObservableCollection<PostViewModel> PostViewModels { get; }
......@@ -57,7 +57,7 @@ namespace MySocialPortalDesktop.ViewModels
try
{
var posts = CurrentPerson == null ?
TwitterConnector.GetNewerHomeTimeline(MaxPostsQuery).ToList() :
TwitterConnector.GetNewerHomeTimeline(10).ToList() :
TwitterConnector.GetNewerUserTimeline(CurrentPerson, MaxPostsQuery).ToList();
ProcessNewPosts(posts, true);
......@@ -73,7 +73,7 @@ namespace MySocialPortalDesktop.ViewModels
try
{
var posts = CurrentPerson == null ?
TwitterConnector.GetOlderHomeTimeline(MaxPostsQuery).ToList() :
TwitterConnector.GetOlderHomeTimeline(10).ToList() :
TwitterConnector.GetOlderUserTimeline(CurrentPerson, MaxPostsQuery).ToList();
ProcessNewPosts(posts, false);
}
......@@ -110,33 +110,48 @@ namespace MySocialPortalDesktop.ViewModels
RepositoryFactory.Instance.AllPostsRepository.AddPosts(filteredPosts);
if (insertTop)
{
posts.Sort(new PostComparisonAscending());
posts.Sort(new PostComparison(true));
posts.ForEach(p => PostViewModels.Insert(0, new PostViewModel(p)));
}
else
{
posts.Sort(new PostComparisonDescending());
var currentGuessIndex = 0;
posts.Sort(new PostComparison(false));
foreach (var post in posts)
{
var postDate = post.PostDateTime.LocalDateTime;
var guessDate = PostViewModels[currentGuessIndex].Date;
if (postDate >= guessDate)
var found = false;
var currentGuessIndex = 0;
while (!found)
{
PostViewModels.Insert(currentGuessIndex, new PostViewModel(post));
continue;
}
if (PostViewModels.Count == 0)
{
PostViewModels.Add(new PostViewModel(post));
found = true;
continue;
}
var postDate = post.PostDateTime.LocalDateTime;
var guessDate = PostViewModels[currentGuessIndex].Date;
if (currentGuessIndex == PostViewModels.Count - 1)
{
PostViewModels.Insert(currentGuessIndex, new PostViewModel(post));
continue;
}
if (postDate >= guessDate)
{
PostViewModels.Insert(currentGuessIndex, new PostViewModel(post));
found = true;
continue;
}
if (currentGuessIndex == PostViewModels.Count - 1)
{
PostViewModels.Insert(currentGuessIndex, new PostViewModel(post));
found = true;
continue;
}
currentGuessIndex++;
currentGuessIndex++;
}
}
PostViewModels.AddRange(posts.Select(p => new PostViewModel(p)));
//PostViewModels.AddRange(posts.Select(p => new PostViewModel(p)));
}
}
}
......
......@@ -17,7 +17,8 @@
<DockPanel>
<Menu DockPanel.Dock="Top">
<MenuItem Header="_File">
<MenuItem Header="_Posts Import" Command="{Binding ImportPosts}" HotKey="CTRL+H"/>
<MenuItem Header="_Posts Import" Command="{Binding ImportPosts}" HotKey="CTRL+P"/>
<MenuItem Header="P_eople Import" Command="{Binding ImportPeople}" HotKey="CTRL+E"/>
<Separator/>
<MenuItem Header="Exit" Command="{Binding DoExit}"/>
</MenuItem>
......
......@@ -16,8 +16,17 @@
</Style>
</UserControl.Styles>
<Grid RowDefinitions="Auto,*" ColumnDefinitions="*">
<Grid Grid.Row="0" Grid.Column="0" RowDefinitions="Auto" ColumnDefinitions="Auto,*" Margin="2">
<Grid RowDefinitions="Auto, Auto,*" ColumnDefinitions="*">
<Grid Grid.Row="0" Grid.Column="0">
<Grid RowDefinitions="*" ColumnDefinitions="Auto,*">
<TextBlock Grid.Row="0" Grid.Column="0" Text="Visible Users: "/>
<ComboBox Name="VisibleUsersSelection" Grid.Row="0" Grid.Column="1" SelectedIndex="0">
<ComboBoxItem Content="All"/>
<ComboBoxItem Content="Favorites"/>
</ComboBox>
</Grid>
</Grid>
<Grid Grid.Row="1" Grid.Column="0" RowDefinitions="Auto" ColumnDefinitions="Auto,*" Margin="2">
<ComboBox Name="SortOrderComboBox" Grid.Row="0" Grid.Column="0" Width="100" SelectedIndex="0" >
<ComboBoxItem Content="None" />
<ComboBoxItem Content="Name (A-Z)" />
......@@ -27,13 +36,16 @@
</ComboBox>
<TextBox Grid.Row="0" Grid.Column="1" Text="{Binding FilterText}" />
</Grid>
<ListBox Grid.Row="1" Grid.Column="0" Name="FriendsList" Items="{Binding People}">
<ListBox Grid.Row="2" Grid.Column="0" Name="FriendsList" Items="{Binding People}">
<ListBox.DataTemplates>
<DataTemplate DataType="vm:PersonViewModel">
<Grid RowDefinitions="Auto, Auto" ColumnDefinitions="Auto, Auto">
<Grid.ContextMenu>
<ContextMenu>
<MenuItem Header="View Timeline" Command="{Binding ViewTimeline}"/>
<MenuItem Header="-"></MenuItem>
<MenuItem Header="Add To Favorites" Command="{Binding AddToFavorites}" IsEnabled="{Binding !IsFavorite}"/>
<MenuItem Header="Remove From Favorites" Command="{Binding RemoveFromFavorites}" IsEnabled="{Binding IsFavorite}"/>
</ContextMenu>
</Grid.ContextMenu>
<Image Grid.Row="0" Grid.Column="0" Grid.RowSpan="2" Margin="5" Source="{Binding DisplayIcon}" Width="50" Height="50"/>
......
......@@ -32,6 +32,28 @@ namespace MySocialPortalDesktop.Views
var selection = (e?.AddedItems[0] as ComboBoxItem)?.Content?.ToString();
vm.ChangeSortOrderCommand(selection);
};
var visibleUsers = this.Find<ComboBox>("VisibleUsersSelection");
visibleUsers.SelectionChanged += (sender, e) =>
{
var vm = this.DataContext as PeopleListViewModel;
if (vm == null)
{
return;
}
var selection = (e?.AddedItems[0] as ComboBoxItem)?.Content?.ToString();
if (selection == "All")
{
vm.ShowAll();
}
else
{
vm.ToggleVisibleList(selection);
}
};
}
}
}
\ No newline at end of file
using System;
using System.IO;
using LiteDB;
using MySocialPortalLib.Repository;
using MySocialPortalLib.Service;
......@@ -10,6 +11,7 @@ namespace MySocialPortalDesktop.Factory
private const string AllPostsRepositoryDbName = "all_posts.db";
private const string LinkPreviewRepositoryDbName = "link_preview.db";
private const string LinkPreviewCacheRepositoryDbName = "link_preview_cache.db";
private const string ListsRepositoryDbName = "lists.db";
private const string MainPeopleRepositoryDbName = "all_people.db";
private const string ProfileImageRepositoryDbName = "profile_images.db";
private const string TimelineRepositoryDbName = "timeline_cache.db";
......@@ -27,6 +29,8 @@ namespace MySocialPortalDesktop.Factory
public IFileCacheRepository LinkPreviewImageCacheRepository => LinkPreviewImageCacheRepositoryLazy.Value;
public INamedListRepository ListsRepository => ListsRepositoryLazy.Value;
public IFileCacheRepository ProfileImageCacheRepository => ProfileImageCacheRepositoryLazy.Value;
public IPersonsRepository MainPeopleRepository => MainPeopleRepositoryLazy.Value;
......@@ -38,6 +42,8 @@ namespace MySocialPortalDesktop.Factory
AllPostsRepositoryLazy = new Lazy<IPostsRepository>(BuildAllPostsRepository);
LinkPreviewRepositoryLazy = new Lazy<ILinkPreviewRepository>(BuildLinkPreviewRepository);
LinkPreviewImageCacheRepositoryLazy = new Lazy<IFileCacheRepository>(BuildLinkPreviewImageCacheRepository);
ListsRepositoryLazy = new Lazy<INamedListRepository>(BuildListRepository);
ListsRepositoryBackingLazy = new Lazy<LiteRepository>(BuildListRepositoryBacking);
MainPeopleRepositoryLazy = new Lazy<IPersonsRepository>(BuildPeopleRepository);
ProfileImageCacheRepositoryLazy = new Lazy<IFileCacheRepository>(BuildProfileImageCacheRepository);
TimelineRepositoryLazy = new Lazy<ITimelineRepository>(BuildTimelineRepository);
......@@ -49,6 +55,10 @@ namespace MySocialPortalDesktop.Factory
private Lazy<IFileCacheRepository> LinkPreviewImageCacheRepositoryLazy { get; }
private Lazy<INamedListRepository> ListsRepositoryLazy { get; }
private Lazy<LiteRepository> ListsRepositoryBackingLazy { get; }
private Lazy<IPersonsRepository> MainPeopleRepositoryLazy { get; }
private Lazy<IFileCacheRepository> ProfileImageCacheRepositoryLazy { get; }
......@@ -69,6 +79,16 @@ namespace MySocialPortalDesktop.Factory
{
return new FileCacheLiteDbRepository(BuildDbPath(LinkPreviewCacheRepositoryDbName));
}
private INamedListRepository BuildListRepository()
{
return new NamedListLiteDbRepository(ListsRepositoryBackingLazy.Value);
}
private LiteRepository BuildListRepositoryBacking()
{
return new LiteRepository(BuildDbPath(ListsRepositoryDbName));
}
private IFileCacheRepository BuildProfileImageCacheRepository()
{
......@@ -106,6 +126,7 @@ namespace MySocialPortalDesktop.Factory
(ProfileImageCacheRepository as IDisposable)?.Dispose();
(TimelineRepository as IDisposable)?.Dispose();
(LinkPreviewImageCacheRepository as IDisposable)?.Dispose();
(ListsRepositoryBackingLazy.Value as IDisposable)?.Dispose();
}
_disposed = true;
......
......@@ -54,6 +54,26 @@ namespace MySocialPortalLib.Model
&& SocialMediaSystemName == other.SocialMediaSystemName;
}
public SocialMediaAccountData Copy()
{
var newValue = new SocialMediaAccountData
{
Id = this.Id,
Active = this.Active,
ProfileId = this.ProfileId,
ProfilePhotoPath = this.ProfilePhotoPath,
ProfileUrl = this.ProfileUrl,
RealName = this.RealName,
SocialMediaSystemName = this.SocialMediaSystemName
};
foreach (var (k,v) in AdditionalProperties)
{
newValue.AdditionalProperties[k] = v;
}
return newValue;
}
protected bool Equals(SocialMediaAccountData other)
{
if (other == null)
......
using System.Collections.Generic;
namespace MySocialPortalLib.Repository
{
public interface INamedListRepository
{
void Add(string id, string listName);
void RemoveIdFromList(string id, string listName);
void RemoveList(string listName);
void RemoveId(string id);
IList<string> GetAllLists();
IList<string> GetAllListsForId(string id);
IList<string> GetAllIdsForList(string list);
}
}
\ No newline at end of file
using System;
using System.Collections.Generic;
using System.Linq;
using LiteDB;
namespace MySocialPortalLib.Repository
{
public class NamedListLiteDbRepository : INamedListRepository
{
private const string DefaultCollectionName = "NamedLists";
private NamedListLiteDbRepository()
{
throw new MethodAccessException();
}
public NamedListLiteDbRepository(LiteRepository repository, string collectionName = DefaultCollectionName)
{
Repository = repository;
CollectionName = collectionName;
}
public void Add(string id, string listName)
{
var entry = new Entry
{
ListName = listName,
MemberId = id
};
if (Repository.Query<Entry>(CollectionName)
.Where(e => e.ListName == listName && e.MemberId == id)
.Count() == 0)
{
Repository.Insert(entry, CollectionName);
}
}
public IList<string> GetAllLists()
{
return Repository.Database.Execute($"SELECT DISTINCT(*.ListName) From {CollectionName}")
.Current["expr"]
.AsArray
.Select(e => e.AsString)
.ToList();
}
public IList<string> GetAllListsForId(string id)
{
return Repository.Query<Entry>(CollectionName)
.Where(e => e.MemberId == id)
.ToList()
.Select(e => e.ListName)
.ToList();
}
public IList<string> GetAllIdsForList(string list)
{
return Repository.Query<Entry>(CollectionName)
.Where(e => e.ListName == list)
.ToList()
.Select(e => e.MemberId)
.ToList();