forked from reactiveui/ReactiveUI
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathAutoPersistHelper.cs
More file actions
136 lines (117 loc) · 5.53 KB
/
AutoPersistHelper.cs
File metadata and controls
136 lines (117 loc) · 5.53 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
using ReactiveUI;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Reactive;
using System.Reactive.Concurrency;
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Runtime.Serialization;
using System.Text;
using System.Reflection;
using Splat;
namespace ReactiveUI
{
public static class AutoPersistHelper
{
static MemoizingMRUCache<Type, Dictionary<string, bool>> persistablePropertiesCache = new MemoizingMRUCache<Type, Dictionary<string, bool>>((type, _) => {
return type.GetTypeInfo().DeclaredProperties
.Where(x => x.CustomAttributes.Any(y => typeof(DataMemberAttribute).GetTypeInfo().IsAssignableFrom(y.AttributeType.GetTypeInfo())))
.ToDictionary(k => k.Name, v => true);
}, RxApp.SmallCacheLimit);
static MemoizingMRUCache<Type, bool> dataContractCheckCache = new MemoizingMRUCache<Type, bool>((t, _) => {
return t.GetTypeInfo().GetCustomAttributes(typeof(DataContractAttribute), true).Any();
}, RxApp.SmallCacheLimit);
public static IDisposable AutoPersist<T>(this T This, Func<T, IObservable<Unit>> doPersist, TimeSpan? interval = null)
where T : IReactiveNotifyPropertyChanged
{
return This.AutoPersist(doPersist, Observable.Never<Unit>(), interval);
}
public static IDisposable AutoPersist<T, TDontCare>(this T This, Func<T, IObservable<Unit>> doPersist, IObservable<TDontCare> manualSaveSignal, TimeSpan? interval = null)
where T : IReactiveNotifyPropertyChanged
{
interval = interval ?? TimeSpan.FromSeconds(3.0);
lock (dataContractCheckCache) {
if (!dataContractCheckCache.Get(This.GetType())) {
throw new ArgumentException("AutoPersist can only be applied to objects with [DataContract]");
}
}
var persistableProperties = default(Dictionary<string, bool>);
lock (persistablePropertiesCache) {
persistableProperties = persistablePropertiesCache.Get(This.GetType());
}
var saveHint = Observable.Merge(
This.Changed.Where(x => persistableProperties.ContainsKey(x.PropertyName)).Select(_ => Unit.Default),
manualSaveSignal.Select(_ => Unit.Default));
var autoSaver = saveHint
.Throttle(interval.Value, RxApp.TaskpoolScheduler)
.SelectMany(_ => doPersist(This))
.Publish();
// NB: This rigamarole is to prevent the initialization of a class
// from triggering a save
var ret = new SingleAssignmentDisposable();
RxApp.MainThreadScheduler.Schedule(() => {
if (ret.IsDisposed) return;
ret.Disposable = autoSaver.Connect();
});
return ret;
}
public static IDisposable AutoPersistCollection<T>(this ReactiveList<T> This, Func<T, IObservable<Unit>> doPersist, TimeSpan? interval = null)
where T : IReactiveNotifyPropertyChanged
{
return AutoPersistCollection(This, doPersist, Observable.Never<Unit>(), interval);
}
public static IDisposable AutoPersistCollection<T, TDontCare>(this ReactiveList<T> This, Func<T, IObservable<Unit>> doPersist, IObservable<TDontCare> manualSaveSignal, TimeSpan? interval = null)
where T : IReactiveNotifyPropertyChanged
{
var disposerList = new Dictionary<T, IDisposable>();
var disp = This.ActOnEveryObject(
x => {
if (disposerList.ContainsKey(x)) return;
disposerList[x] = x.AutoPersist(doPersist, manualSaveSignal, interval);
},
x => {
disposerList[x].Dispose();
disposerList.Remove(x);
});
return Disposable.Create(() => {
disp.Dispose();
disposerList.Values.ForEach(x => x.Dispose());
});
}
public static IDisposable ActOnEveryObject<T>(this ReactiveList<T> This, Action<T> onAdd, Action<T> onRemove)
where T : IReactiveNotifyPropertyChanged
{
foreach (var v in This) { onAdd(v); }
var changingDisp = This.Changing
.Where(x => x.Action == NotifyCollectionChangedAction.Reset)
.Subscribe(
_ => This.ForEach(x => onRemove(x)));
var changedDisp = This.Changed.Subscribe(x => {
switch (x.Action) {
case NotifyCollectionChangedAction.Add:
foreach (T v in x.NewItems) { onAdd(v); }
break;
case NotifyCollectionChangedAction.Replace:
foreach (T v in x.OldItems) { onRemove(v); }
foreach (T v in x.NewItems) { onAdd(v); }
break;
case NotifyCollectionChangedAction.Remove:
foreach (T v in x.OldItems) { onRemove(v); }
break;
case NotifyCollectionChangedAction.Reset:
foreach (T v in This) { onAdd(v); }
break;
default:
break;
}
});
return Disposable.Create(() => {
changingDisp.Dispose();
changedDisp.Dispose();
This.ForEach(x => onRemove(x));
});
}
}
}