14
14
using xivModdingFramework . Items . Interfaces ;
15
15
using xivModdingFramework . Mods ;
16
16
using xivModdingFramework . Resources ;
17
+ using xivModdingFramework . SqPack . FileTypes ;
17
18
18
19
namespace xivModdingFramework . Cache
19
20
{
@@ -257,7 +258,13 @@ public static void RebuildCache()
257
258
258
259
} catch ( Exception Ex )
259
260
{
260
- SetMetaValue ( "needs_rebuild" , "1" ) ;
261
+ try
262
+ {
263
+ SetMetaValue ( "needs_rebuild" , "1" ) ;
264
+ }
265
+ catch {
266
+ // No-op. We're pretty fucked at this point.
267
+ }
261
268
_REBUILDING = false ;
262
269
throw ;
263
270
}
@@ -1153,6 +1160,42 @@ public static async Task<List<string>> GetChildFiles(string internalFilePath, bo
1153
1160
return list ;
1154
1161
}
1155
1162
1163
+ /// <summary>
1164
+ /// Determines if a file is orphaned or not in the file system.
1165
+ /// An Orphaned file is a custom file which no longer has any files in the filesystem actively referencing it.
1166
+ /// These files may be safely deleted IFF all mods are currently active in ModList.
1167
+ /// ( Determining if any deactivated mods use the file is much more annoying )
1168
+ /// </summary>
1169
+ /// <param name="internalFilePath"></param>
1170
+ /// <returns></returns>
1171
+ public static async Task < bool > IsOrphanedFile ( string internalFilePath )
1172
+ {
1173
+ var index = new Index ( GameInfo . GameDirectory ) ;
1174
+ // So to be orphaned, a file has to pass(fail) a few criteria.
1175
+
1176
+ // 1. It cannot be a default FFXIV file. Default files are never treated as orphans for safety.
1177
+ var def = await index . IsDefaultFilePath ( internalFilePath ) ;
1178
+ if ( def ) return false ;
1179
+
1180
+ // 2. It has to have updated cache data.
1181
+ await GetParentFiles ( internalFilePath ) ;
1182
+
1183
+ // 3. That cache data must specifically have exactly one parent listed, and that parent must be NULL.
1184
+ var wc = new WhereClause ( ) { Column = "child" , Comparer = WhereClause . ComparisonType . Equal , Value = internalFilePath } ;
1185
+ var list = await BuildListFromTable ( "dependencies_parents" , wc , async ( reader ) =>
1186
+ {
1187
+ return reader . GetString ( "parent" ) ;
1188
+ } ) ;
1189
+
1190
+ if ( list . Count == 1 && list [ 0 ] == null )
1191
+ {
1192
+ return true ;
1193
+ }
1194
+ return false ;
1195
+
1196
+
1197
+ }
1198
+
1156
1199
/// <summary>
1157
1200
/// Retreives the parent files in the dependency graph for this file.
1158
1201
/// </summary>
@@ -1179,16 +1222,9 @@ public static async Task<List<string>> GetParentFiles(string internalFilePath, b
1179
1222
1180
1223
if ( list . Count == 0 )
1181
1224
{
1182
- // Need to pull the raw data to verify a 0 count entry .
1225
+ // 0 Length list means there was no cached data .
1183
1226
list = await XivDependencyGraph . GetParentFiles ( internalFilePath ) ;
1184
1227
await UpdateParentFiles ( internalFilePath , list ) ;
1185
-
1186
- // So if we updated our own parents, we have to update our children's parents too.
1187
- // Because we may be a previously orphaned node, that now is re-attached to the main tree.
1188
- var children = await GetChildFiles ( internalFilePath ) ;
1189
-
1190
- // In short, any parent calculation is always cascading downwards.
1191
- QueueParentFilesUpdate ( children ) ;
1192
1228
}
1193
1229
return list ;
1194
1230
}
@@ -1538,18 +1574,45 @@ public static async Task<List<string>> UpdateChildFiles(string internalFilePath,
1538
1574
children = await XivDependencyGraph . GetChildFiles ( internalFilePath ) ;
1539
1575
}
1540
1576
1577
+ var oldCacheChildren = new List < string > ( ) ;
1578
+
1541
1579
using ( var db = new SQLiteConnection ( CacheConnectionString ) )
1542
1580
{
1543
1581
db . Open ( ) ;
1544
1582
using ( var transaction = db . BeginTransaction ( ) )
1545
1583
{
1584
+ // Clear out our old children.
1546
1585
var query = "delete from dependencies_children where parent = $parent" ;
1547
1586
using ( var cmd = new SQLiteCommand ( query , db ) )
1548
1587
{
1549
1588
cmd . Parameters . AddWithValue ( "parent" , internalFilePath ) ;
1550
1589
cmd . ExecuteScalar ( ) ;
1551
1590
}
1552
1591
1592
+ // Find all the files that currently point to us as a parent in the cache.
1593
+ query = "select child from dependencies_parents where parent = $parent" ;
1594
+ using ( var cmd = new SQLiteCommand ( query , db ) )
1595
+ {
1596
+ cmd . Parameters . AddWithValue ( "parent" , internalFilePath ) ;
1597
+ using ( var reader = new CacheReader ( cmd . ExecuteReader ( ) ) )
1598
+ {
1599
+ while ( reader . NextRow ( ) )
1600
+ {
1601
+ oldCacheChildren . Add ( reader . GetString ( "child" ) ) ;
1602
+ }
1603
+ }
1604
+ }
1605
+
1606
+ // And purge all of those cached file's parents (we'll queue them up for recalculation after the transaction is done)
1607
+ // Could let the queue-up do the purging for us here, but then there's potential that some other thread
1608
+ // might come in and pluck up bad data between our transaction and queuing call.
1609
+ query = "delete from dependencies_parents where child in (select child as c_file from dependencies_parents where parent = $parent)" ;
1610
+ using ( var cmd = new SQLiteCommand ( query , db ) )
1611
+ {
1612
+ cmd . Parameters . AddWithValue ( "parent" , internalFilePath ) ;
1613
+ cmd . ExecuteScalar ( ) ;
1614
+ }
1615
+
1553
1616
if ( children == null || children . Count == 0 )
1554
1617
{
1555
1618
// Null indicator says we updated the data, but there were no parents.
@@ -1578,6 +1641,19 @@ public static async Task<List<string>> UpdateChildFiles(string internalFilePath,
1578
1641
}
1579
1642
}
1580
1643
1644
+ var toUpdate = new HashSet < string > ( ) ;
1645
+ foreach ( var file in oldCacheChildren )
1646
+ {
1647
+ toUpdate . Add ( file ) ;
1648
+ }
1649
+ foreach ( var file in children )
1650
+ {
1651
+ toUpdate . Add ( file ) ;
1652
+ }
1653
+
1654
+ // Queue everything up that we affected.
1655
+ QueueParentFilesUpdate ( toUpdate . ToList ( ) ) ;
1656
+
1581
1657
return children ;
1582
1658
}
1583
1659
@@ -1806,12 +1882,28 @@ private static void ProcessDependencyQueue(object sender, DoWorkEventArgs e)
1806
1882
/// <returns></returns>
1807
1883
internal static async Task < List < string > > GetCacheParents ( string internalFilePath )
1808
1884
{
1885
+
1809
1886
var wc = new WhereClause ( ) { Column = "child" , Comparer = WhereClause . ComparisonType . Equal , Value = internalFilePath } ;
1810
- return await BuildListFromTable ( "dependencies_children" , wc , async ( reader ) =>
1887
+ List < string > parents = await BuildListFromTable ( "dependencies_children" , wc , async ( reader ) =>
1811
1888
{
1812
1889
return reader . GetString ( "parent" ) ;
1813
1890
} ) ;
1814
1891
1892
+ // It's possible this file is a DX9 => 11 conversion texture, in which case we also have to look for references to our
1893
+ // DX9 version.
1894
+ var dx11Name = internalFilePath . Replace ( "--" , "" ) ;
1895
+ if ( dx11Name != internalFilePath )
1896
+ {
1897
+ wc = new WhereClause ( ) { Column = "child" , Comparer = WhereClause . ComparisonType . Equal , Value = dx11Name } ;
1898
+ List < string > dxParents = await BuildListFromTable ( "dependencies_children" , wc , async ( reader ) =>
1899
+ {
1900
+ return reader . GetString ( "parent" ) ;
1901
+ } ) ;
1902
+
1903
+ parents . AddRange ( dxParents ) ;
1904
+ } ;
1905
+ return parents ;
1906
+
1815
1907
}
1816
1908
1817
1909
/// <summary>
0 commit comments