1
+ using System ;
2
+ using System . IO ;
3
+ using System . Linq ;
4
+ using System . Linq . Expressions ;
5
+ using System . Threading ;
6
+ using System . Collections . Generic ;
7
+ using PatchKit . Unity . Patcher . Cancellation ;
8
+ using PatchKit . Unity . Patcher . Debug ;
9
+ using PatchKit . Unity . Patcher . AppUpdater . Commands ;
10
+ using PatchKit . Unity . Patcher . AppUpdater . Status ;
11
+ using PatchKit . Api . Models . Main ;
12
+
13
+ namespace PatchKit . Unity . Patcher . AppUpdater
14
+ {
15
+ public class AppRepairer
16
+ {
17
+ private static readonly DebugLogger DebugLogger = new DebugLogger ( typeof ( AppRepairer ) ) ;
18
+
19
+ public readonly AppUpdaterContext Context ;
20
+
21
+ // set to true if you wish to check file hashes
22
+ public bool CheckHashes = false ;
23
+
24
+ // how many times process will repeat until it ultimately fails
25
+ public int RepeatCount = 3 ;
26
+
27
+ private UpdaterStatus _status ;
28
+
29
+ private AppUpdaterStrategyResolver _strategyResolver ;
30
+
31
+ private AppUpdaterCommandFactory _commandFactory ;
32
+
33
+ private int _lowestVersionWithContentId ;
34
+
35
+ private const double IncreaseRepairCost = 1.5d ;
36
+
37
+
38
+ public AppRepairer ( AppUpdaterContext context , UpdaterStatus status )
39
+ {
40
+ DebugLogger . LogConstructor ( ) ;
41
+
42
+ Checks . ArgumentNotNull ( context , "context" ) ;
43
+
44
+ Context = context ;
45
+ _status = status ;
46
+
47
+ _strategyResolver = new AppUpdaterStrategyResolver ( _status ) ;
48
+ _commandFactory = new AppUpdaterCommandFactory ( ) ;
49
+ }
50
+
51
+ // returns true if data is valid (was valid from the start or successfull repair was performed)
52
+ public bool Perform ( PatchKit . Unity . Patcher . Cancellation . CancellationToken cancellationToken )
53
+ {
54
+ _lowestVersionWithContentId = Context . App . GetLowestVersionWithContentId ( cancellationToken ) ;
55
+
56
+ for ( int attempt = 1 ; attempt <= RepeatCount ; ++ attempt )
57
+ {
58
+ DebugLogger . Log ( "Running integrity check, attempt " + attempt + " of " + RepeatCount ) ;
59
+
60
+ if ( PerformInternal ( cancellationToken ) )
61
+ {
62
+ return true ;
63
+ }
64
+ }
65
+
66
+ // retry count reached, let's check for the last time if data is ok, but without repairing
67
+ int installedVersionId = Context . App . GetInstalledVersionId ( ) ;
68
+ VersionIntegrity results = CheckIntegrity ( cancellationToken , installedVersionId ) ;
69
+ var filesNeedFixing = FilesNeedFixing ( results ) ;
70
+
71
+ if ( filesNeedFixing . Count ( ) == 0 )
72
+ {
73
+ DebugLogger . Log ( "No missing or invalid size files." ) ;
74
+ return true ;
75
+ }
76
+
77
+
78
+ DebugLogger . LogError ( "Still have corrupted files after all fixing attempts" ) ;
79
+ return false ;
80
+ }
81
+
82
+ // returns true if there was no integrity errors, false if there was and repair was performed
83
+ private bool PerformInternal ( PatchKit . Unity . Patcher . Cancellation . CancellationToken cancellationToken )
84
+ {
85
+ int installedVersionId = Context . App . GetInstalledVersionId ( ) ;
86
+
87
+ VersionIntegrity results = CheckIntegrity ( cancellationToken , installedVersionId ) ;
88
+ var filesNeedFixing = FilesNeedFixing ( results ) ;
89
+
90
+ if ( filesNeedFixing . Count ( ) == 0 )
91
+ {
92
+ DebugLogger . Log ( "No missing or invalid size files." ) ;
93
+ return true ;
94
+ }
95
+
96
+ // need to collect some data about the application to calculate the repair cost and make decisions
97
+
98
+ int latestVersionId = Context . App . GetLatestVersionId ( true , cancellationToken ) ;
99
+
100
+ AppContentSummary installedVersionContentSummary
101
+ = Context . App . RemoteMetaData . GetContentSummary ( installedVersionId , cancellationToken ) ;
102
+
103
+ AppContentSummary latestVersionContentSummary
104
+ = Context . App . RemoteMetaData . GetContentSummary ( latestVersionId , cancellationToken ) ;
105
+
106
+ bool isNewVersionAvailable = installedVersionId < latestVersionId ;
107
+
108
+ long contentSize = isNewVersionAvailable
109
+ ? latestVersionContentSummary . Files . Sum ( f => f . Size )
110
+ : installedVersionContentSummary . Files . Sum ( f => f . Size ) ;
111
+
112
+ double repairCost = CalculateRepairCost ( installedVersionContentSummary , filesNeedFixing ) ;
113
+
114
+ // increasing repair costs that reinstallation will be done for 1/3 of the content size
115
+ repairCost *= IncreaseRepairCost ;
116
+
117
+
118
+ if ( _lowestVersionWithContentId > installedVersionId )
119
+ {
120
+ DebugLogger . Log (
121
+ "Repair is impossible because lowest version with content id is "
122
+ + _lowestVersionWithContentId +
123
+ " and currently installed version id is "
124
+ + installedVersionId +
125
+ ". Reinstalling." ) ;
126
+
127
+ ReinstallContent ( cancellationToken ) ;
128
+ }
129
+ else if ( repairCost < contentSize )
130
+ {
131
+ DebugLogger . Log ( string . Format ( "Repair cost {0} is smaller than content cost {1}, repairing..." , repairCost , contentSize ) ) ;
132
+ IAppUpdaterStrategy repairStrategy = _strategyResolver . Create ( StrategyType . Repair , Context ) ;
133
+ repairStrategy . Update ( cancellationToken ) ;
134
+ }
135
+ else
136
+ {
137
+ DebugLogger . Log ( string . Format ( "Content cost {0} is smaller than repair {1}. Reinstalling." , contentSize , repairCost ) ) ;
138
+ ReinstallContent ( cancellationToken ) ;
139
+ }
140
+
141
+ return false ;
142
+ }
143
+
144
+ private VersionIntegrity CheckIntegrity (
145
+ PatchKit . Unity . Patcher . Cancellation . CancellationToken cancellationToken ,
146
+ int installedVersionId
147
+ )
148
+ {
149
+ ICheckVersionIntegrityCommand checkIntegrity = _commandFactory
150
+ . CreateCheckVersionIntegrityCommand (
151
+ versionId : installedVersionId ,
152
+ context : Context ,
153
+ isCheckingHash : CheckHashes ,
154
+ isCheckingSize : true ,
155
+ cancellationToken : cancellationToken ) ;
156
+
157
+ checkIntegrity . Prepare ( _status , cancellationToken ) ;
158
+ checkIntegrity . Execute ( cancellationToken ) ;
159
+
160
+ return checkIntegrity . Results ;
161
+ }
162
+
163
+ private IEnumerable < FileIntegrity > FilesNeedFixing ( VersionIntegrity results )
164
+ {
165
+ var missingFiles = results . Files . Where ( f => f . Status == FileIntegrityStatus . MissingData ) ;
166
+ var invalidSizeFiles = results . Files . Where ( f => f . Status == FileIntegrityStatus . InvalidSize ) ;
167
+
168
+ return missingFiles . Concat ( invalidSizeFiles ) ;
169
+ }
170
+
171
+ private long CalculateRepairCost ( AppContentSummary contentSummary , IEnumerable < FileIntegrity > filesToRepair )
172
+ {
173
+ return filesToRepair
174
+ . Select ( f => contentSummary . Files . FirstOrDefault ( e => e . Path == f . FileName ) )
175
+ . Sum ( f => f . Size ) ;
176
+ }
177
+
178
+ private void ReinstallContent ( PatchKit . Unity . Patcher . Cancellation . CancellationToken cancellationToken )
179
+ {
180
+ IUninstallCommand uninstall = _commandFactory . CreateUninstallCommand ( Context ) ;
181
+ uninstall . Prepare ( _status , cancellationToken ) ;
182
+ uninstall . Execute ( cancellationToken ) ;
183
+
184
+ // not catching any exceptions here, because exception during content installation in this place should be fatal
185
+ var contentStrategy = new AppUpdaterContentStrategy ( Context , _status ) ;
186
+ contentStrategy . RepairOnError = false ; // do not attempt to repair content to not cause a loop
187
+ contentStrategy . Update ( cancellationToken ) ;
188
+ }
189
+ }
190
+ }
0 commit comments