diff --git a/RetainerTrack/.editorconfig b/RetainerTrack/.editorconfig new file mode 100644 index 0000000..89b3328 --- /dev/null +++ b/RetainerTrack/.editorconfig @@ -0,0 +1,1017 @@ +# NOTE: Requires **VS2019 16.3** or later + +# All Rules Enabled as build warnings +# Description: All Rules are enabled as build warnings. Rules with IsEnabledByDefault = false are force enabled as build warnings. + +# Code files +[*.{cs,vb}] + + +# CA1000: Do not declare static members on generic types +dotnet_diagnostic.CA1000.severity = warning + +# CA1001: Types that own disposable fields should be disposable +dotnet_diagnostic.CA1001.severity = warning + +# CA1002: Do not expose generic lists +dotnet_diagnostic.CA1002.severity = warning + +# CA1003: Use generic event handler instances +dotnet_diagnostic.CA1003.severity = warning + +# CA1005: Avoid excessive parameters on generic types +dotnet_diagnostic.CA1005.severity = warning + +# CA1008: Enums should have zero value +dotnet_diagnostic.CA1008.severity = warning + +# CA1010: Generic interface should also be implemented +dotnet_diagnostic.CA1010.severity = warning + +# CA1012: Abstract types should not have public constructors +dotnet_diagnostic.CA1012.severity = warning + +# CA1014: Mark assemblies with CLSCompliant +dotnet_diagnostic.CA1014.severity = warning + +# CA1016: Mark assemblies with assembly version +dotnet_diagnostic.CA1016.severity = warning + +# CA1017: Mark assemblies with ComVisible +dotnet_diagnostic.CA1017.severity = warning + +# CA1018: Mark attributes with AttributeUsageAttribute +dotnet_diagnostic.CA1018.severity = warning + +# CA1019: Define accessors for attribute arguments +dotnet_diagnostic.CA1019.severity = warning + +# CA1021: Avoid out parameters +dotnet_diagnostic.CA1021.severity = none + +# CA1024: Use properties where appropriate +dotnet_diagnostic.CA1024.severity = warning + +# CA1027: Mark enums with FlagsAttribute +dotnet_diagnostic.CA1027.severity = warning + +# CA1028: Enum Storage should be Int32 +dotnet_diagnostic.CA1028.severity = warning + +# CA1030: Use events where appropriate +dotnet_diagnostic.CA1030.severity = warning + +# CA1031: Do not catch general exception types +dotnet_diagnostic.CA1031.severity = suggestion + +# CA1032: Implement standard exception constructors +dotnet_diagnostic.CA1032.severity = warning + +# CA1033: Interface methods should be callable by child types +dotnet_diagnostic.CA1033.severity = warning + +# CA1034: Nested types should not be visible +dotnet_diagnostic.CA1034.severity = warning + +# CA1036: Override methods on comparable types +dotnet_diagnostic.CA1036.severity = warning + +# CA1040: Avoid empty interfaces +dotnet_diagnostic.CA1040.severity = warning + +# CA1041: Provide ObsoleteAttribute message +dotnet_diagnostic.CA1041.severity = warning + +# CA1043: Use Integral Or String Argument For Indexers +dotnet_diagnostic.CA1043.severity = warning + +# CA1044: Properties should not be write only +dotnet_diagnostic.CA1044.severity = warning + +# CA1045: Do not pass types by reference +dotnet_diagnostic.CA1045.severity = warning + +# CA1046: Do not overload equality operator on reference types +dotnet_diagnostic.CA1046.severity = warning + +# CA1047: Do not declare protected member in sealed type +dotnet_diagnostic.CA1047.severity = warning + +# CA1050: Declare types in namespaces +dotnet_diagnostic.CA1050.severity = warning + +# CA1051: Do not declare visible instance fields +dotnet_diagnostic.CA1051.severity = warning + +# CA1052: Static holder types should be Static or NotInheritable +dotnet_diagnostic.CA1052.severity = warning + +# CA1054: URI-like parameters should not be strings +dotnet_diagnostic.CA1054.severity = warning + +# CA1055: URI-like return values should not be strings +dotnet_diagnostic.CA1055.severity = warning + +# CA1056: URI-like properties should not be strings +dotnet_diagnostic.CA1056.severity = warning + +# CA1058: Types should not extend certain base types +dotnet_diagnostic.CA1058.severity = warning + +# CA1060: Move pinvokes to native methods class +dotnet_diagnostic.CA1060.severity = warning + +# CA1061: Do not hide base class methods +dotnet_diagnostic.CA1061.severity = warning + +# CA1062: Validate arguments of public methods +dotnet_diagnostic.CA1062.severity = warning + +# CA1063: Implement IDisposable Correctly +dotnet_diagnostic.CA1063.severity = warning + +# CA1064: Exceptions should be public +dotnet_diagnostic.CA1064.severity = warning + +# CA1065: Do not raise exceptions in unexpected locations +dotnet_diagnostic.CA1065.severity = warning + +# CA1066: Implement IEquatable when overriding Object.Equals +dotnet_diagnostic.CA1066.severity = warning + +# CA1067: Override Object.Equals(object) when implementing IEquatable +dotnet_diagnostic.CA1067.severity = warning + +# CA1068: CancellationToken parameters must come last +dotnet_diagnostic.CA1068.severity = warning + +# CA1069: Enums values should not be duplicated +dotnet_diagnostic.CA1069.severity = warning + +# CA1070: Do not declare event fields as virtual +dotnet_diagnostic.CA1070.severity = warning + +# CA1200: Avoid using cref tags with a prefix +dotnet_diagnostic.CA1200.severity = warning + +# CA1303: Do not pass literals as localized parameters +dotnet_diagnostic.CA1303.severity = warning + +# CA1304: Specify CultureInfo +dotnet_diagnostic.CA1304.severity = warning + +# CA1305: Specify IFormatProvider +dotnet_diagnostic.CA1305.severity = warning + +# CA1307: Specify StringComparison for clarity +dotnet_diagnostic.CA1307.severity = warning + +# CA1308: Normalize strings to uppercase +dotnet_diagnostic.CA1308.severity = warning + +# CA1309: Use ordinal string comparison +dotnet_diagnostic.CA1309.severity = warning + +# CA1310: Specify StringComparison for correctness +dotnet_diagnostic.CA1310.severity = warning + +# CA1311: Specify a culture or use an invariant version +dotnet_diagnostic.CA1311.severity = warning + +# CA1401: P/Invokes should not be visible +dotnet_diagnostic.CA1401.severity = warning + +# CA1416: Validate platform compatibility +dotnet_diagnostic.CA1416.severity = warning + +# CA1417: Do not use 'OutAttribute' on string parameters for P/Invokes +dotnet_diagnostic.CA1417.severity = warning + +# CA1418: Use valid platform string +dotnet_diagnostic.CA1418.severity = warning + +# CA1419: Provide a parameterless constructor that is as visible as the containing type for concrete types derived from 'System.Runtime.InteropServices.SafeHandle' +dotnet_diagnostic.CA1419.severity = warning + +# CA1420: Property, type, or attribute requires runtime marshalling +dotnet_diagnostic.CA1420.severity = warning + +# CA1421: This method uses runtime marshalling even when the 'DisableRuntimeMarshallingAttribute' is applied +dotnet_diagnostic.CA1421.severity = warning + +# CA1422: Validate platform compatibility +dotnet_diagnostic.CA1422.severity = warning + +# CA1501: Avoid excessive inheritance +dotnet_diagnostic.CA1501.severity = warning + +# CA1502: Avoid excessive complexity +dotnet_diagnostic.CA1502.severity = suggestion + +# CA1505: Avoid unmaintainable code +dotnet_diagnostic.CA1505.severity = warning + +# CA1506: Avoid excessive class coupling +dotnet_diagnostic.CA1506.severity = warning + +# CA1507: Use nameof to express symbol names +dotnet_diagnostic.CA1507.severity = warning + +# CA1508: Avoid dead conditional code +dotnet_diagnostic.CA1508.severity = warning + +# CA1509: Invalid entry in code metrics rule specification file +dotnet_diagnostic.CA1509.severity = warning + +# CA1510: Use ArgumentNullException throw helper +dotnet_diagnostic.CA1510.severity = warning + +# CA1511: Use ArgumentException throw helper +dotnet_diagnostic.CA1511.severity = warning + +# CA1512: Use ArgumentOutOfRangeException throw helper +dotnet_diagnostic.CA1512.severity = warning + +# CA1513: Use ObjectDisposedException throw helper +dotnet_diagnostic.CA1513.severity = warning + +# CA1700: Do not name enum values 'Reserved' +dotnet_diagnostic.CA1700.severity = warning + +# CA1707: Identifiers should not contain underscores +dotnet_diagnostic.CA1707.severity = warning + +# CA1708: Identifiers should differ by more than case +dotnet_diagnostic.CA1708.severity = warning + +# CA1710: Identifiers should have correct suffix +dotnet_diagnostic.CA1710.severity = warning + +# CA1711: Identifiers should not have incorrect suffix +dotnet_diagnostic.CA1711.severity = warning + +# CA1712: Do not prefix enum values with type name +dotnet_diagnostic.CA1712.severity = warning + +# CA1713: Events should not have 'Before' or 'After' prefix +dotnet_diagnostic.CA1713.severity = warning + +# CA1715: Identifiers should have correct prefix +dotnet_diagnostic.CA1715.severity = warning + +# CA1716: Identifiers should not match keywords +dotnet_diagnostic.CA1716.severity = warning + +# CA1720: Identifier contains type name +dotnet_diagnostic.CA1720.severity = warning + +# CA1721: Property names should not match get methods +dotnet_diagnostic.CA1721.severity = warning + +# CA1724: Type names should not match namespaces +dotnet_diagnostic.CA1724.severity = warning + +# CA1725: Parameter names should match base declaration +dotnet_diagnostic.CA1725.severity = warning + +# CA1727: Use PascalCase for named placeholders +dotnet_diagnostic.CA1727.severity = warning + +# CA1802: Use literals where appropriate +dotnet_diagnostic.CA1802.severity = warning + +# CA1805: Do not initialize unnecessarily +dotnet_diagnostic.CA1805.severity = warning + +# CA1806: Do not ignore method results +dotnet_diagnostic.CA1806.severity = warning + +# CA1810: Initialize reference type static fields inline +dotnet_diagnostic.CA1810.severity = warning + +# CA1812: Avoid uninstantiated internal classes +dotnet_diagnostic.CA1812.severity = none + +# CA1813: Avoid unsealed attributes +dotnet_diagnostic.CA1813.severity = warning + +# CA1814: Prefer jagged arrays over multidimensional +dotnet_diagnostic.CA1814.severity = warning + +# CA1815: Override equals and operator equals on value types +dotnet_diagnostic.CA1815.severity = warning + +# CA1816: Dispose methods should call SuppressFinalize +dotnet_diagnostic.CA1816.severity = warning + +# CA1819: Properties should not return arrays +dotnet_diagnostic.CA1819.severity = warning + +# CA1820: Test for empty strings using string length +dotnet_diagnostic.CA1820.severity = warning + +# CA1821: Remove empty Finalizers +dotnet_diagnostic.CA1821.severity = warning + +# CA1822: Mark members as static +dotnet_diagnostic.CA1822.severity = warning + +# CA1823: Avoid unused private fields +dotnet_diagnostic.CA1823.severity = warning + +# CA1824: Mark assemblies with NeutralResourcesLanguageAttribute +dotnet_diagnostic.CA1824.severity = warning + +# CA1825: Avoid zero-length array allocations +dotnet_diagnostic.CA1825.severity = warning + +# CA1826: Do not use Enumerable methods on indexable collections +dotnet_diagnostic.CA1826.severity = warning + +# CA1827: Do not use Count() or LongCount() when Any() can be used +dotnet_diagnostic.CA1827.severity = warning + +# CA1828: Do not use CountAsync() or LongCountAsync() when AnyAsync() can be used +dotnet_diagnostic.CA1828.severity = warning + +# CA1829: Use Length/Count property instead of Count() when available +dotnet_diagnostic.CA1829.severity = warning + +# CA1830: Prefer strongly-typed Append and Insert method overloads on StringBuilder +dotnet_diagnostic.CA1830.severity = warning + +# CA1831: Use AsSpan or AsMemory instead of Range-based indexers when appropriate +dotnet_diagnostic.CA1831.severity = warning + +# CA1832: Use AsSpan or AsMemory instead of Range-based indexers when appropriate +dotnet_diagnostic.CA1832.severity = warning + +# CA1833: Use AsSpan or AsMemory instead of Range-based indexers when appropriate +dotnet_diagnostic.CA1833.severity = warning + +# CA1834: Consider using 'StringBuilder.Append(char)' when applicable +dotnet_diagnostic.CA1834.severity = warning + +# CA1835: Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync' +dotnet_diagnostic.CA1835.severity = warning + +# CA1836: Prefer IsEmpty over Count +dotnet_diagnostic.CA1836.severity = warning + +# CA1837: Use 'Environment.ProcessId' +dotnet_diagnostic.CA1837.severity = warning + +# CA1838: Avoid 'StringBuilder' parameters for P/Invokes +dotnet_diagnostic.CA1838.severity = warning + +# CA1839: Use 'Environment.ProcessPath' +dotnet_diagnostic.CA1839.severity = warning + +# CA1840: Use 'Environment.CurrentManagedThreadId' +dotnet_diagnostic.CA1840.severity = warning + +# CA1841: Prefer Dictionary.Contains methods +dotnet_diagnostic.CA1841.severity = warning + +# CA1842: Do not use 'WhenAll' with a single task +dotnet_diagnostic.CA1842.severity = warning + +# CA1843: Do not use 'WaitAll' with a single task +dotnet_diagnostic.CA1843.severity = warning + +# CA1844: Provide memory-based overrides of async methods when subclassing 'Stream' +dotnet_diagnostic.CA1844.severity = warning + +# CA1845: Use span-based 'string.Concat' +dotnet_diagnostic.CA1845.severity = warning + +# CA1846: Prefer 'AsSpan' over 'Substring' +dotnet_diagnostic.CA1846.severity = warning + +# CA1847: Use char literal for a single character lookup +dotnet_diagnostic.CA1847.severity = warning + +# CA1848: Use the LoggerMessage delegates +dotnet_diagnostic.CA1848.severity = suggestion + +# CA1849: Call async methods when in an async method +dotnet_diagnostic.CA1849.severity = warning + +# CA1850: Prefer static 'HashData' method over 'ComputeHash' +dotnet_diagnostic.CA1850.severity = warning + +# CA1851: Possible multiple enumerations of 'IEnumerable' collection +dotnet_diagnostic.CA1851.severity = warning + +# CA1852: Seal internal types +dotnet_diagnostic.CA1852.severity = warning + +# CA1853: Unnecessary call to 'Dictionary.ContainsKey(key)' +dotnet_diagnostic.CA1853.severity = warning + +# CA1854: Prefer the 'IDictionary.TryGetValue(TKey, out TValue)' method +dotnet_diagnostic.CA1854.severity = warning + +# CA1855: Prefer 'Clear' over 'Fill' +dotnet_diagnostic.CA1855.severity = warning + +# CA1856: Incorrect usage of ConstantExpected attribute +dotnet_diagnostic.CA1856.severity = warning + +# CA1857: A constant is expected for the parameter +dotnet_diagnostic.CA1857.severity = warning + +# CA1858: Use 'StartsWith' instead of 'IndexOf' +dotnet_diagnostic.CA1858.severity = warning + +# CA1859: Use concrete types when possible for improved performance +dotnet_diagnostic.CA1859.severity = warning + +# CA1860: Avoid using 'Enumerable.Any()' extension method +dotnet_diagnostic.CA1860.severity = warning + +# CA1861: Avoid constant arrays as arguments +dotnet_diagnostic.CA1861.severity = warning + +# CA1862: Use the 'StringComparison' method overloads to perform case-insensitive string comparisons +dotnet_diagnostic.CA1862.severity = warning + +# CA1863: Use 'CompositeFormat' +dotnet_diagnostic.CA1863.severity = warning + +# CA1864: Prefer the 'IDictionary.TryAdd(TKey, TValue)' method +dotnet_diagnostic.CA1864.severity = warning + +# CA1865: Use char overload +dotnet_diagnostic.CA1865.severity = warning + +# CA1866: Use char overload +dotnet_diagnostic.CA1866.severity = warning + +# CA1867: Use char overload +dotnet_diagnostic.CA1867.severity = warning + +# CA1868: Unnecessary call to 'Contains(item)' +dotnet_diagnostic.CA1868.severity = warning + +# CA1869: Cache and reuse 'JsonSerializerOptions' instances +dotnet_diagnostic.CA1869.severity = warning + +# CA1870: Use a cached 'SearchValues' instance +dotnet_diagnostic.CA1870.severity = warning + +# CA2000: Dispose objects before losing scope +dotnet_diagnostic.CA2000.severity = warning + +# CA2002: Do not lock on objects with weak identity +dotnet_diagnostic.CA2002.severity = warning + +# CA2007: Consider calling ConfigureAwait on the awaited task +dotnet_diagnostic.CA2007.severity = warning + +# CA2008: Do not create tasks without passing a TaskScheduler +dotnet_diagnostic.CA2008.severity = warning + +# CA2009: Do not call ToImmutableCollection on an ImmutableCollection value +dotnet_diagnostic.CA2009.severity = warning + +# CA2011: Avoid infinite recursion +dotnet_diagnostic.CA2011.severity = warning + +# CA2012: Use ValueTasks correctly +dotnet_diagnostic.CA2012.severity = warning + +# CA2013: Do not use ReferenceEquals with value types +dotnet_diagnostic.CA2013.severity = warning + +# CA2014: Do not use stackalloc in loops +dotnet_diagnostic.CA2014.severity = warning + +# CA2015: Do not define finalizers for types derived from MemoryManager +dotnet_diagnostic.CA2015.severity = warning + +# CA2016: Forward the 'CancellationToken' parameter to methods +dotnet_diagnostic.CA2016.severity = warning + +# CA2017: Parameter count mismatch +dotnet_diagnostic.CA2017.severity = warning + +# CA2018: 'Buffer.BlockCopy' expects the number of bytes to be copied for the 'count' argument +dotnet_diagnostic.CA2018.severity = warning + +# CA2019: Improper 'ThreadStatic' field initialization +dotnet_diagnostic.CA2019.severity = warning + +# CA2020: Prevent behavioral change +dotnet_diagnostic.CA2020.severity = warning + +# CA2021: Do not call Enumerable.Cast or Enumerable.OfType with incompatible types +dotnet_diagnostic.CA2021.severity = warning + +# CA2100: Review SQL queries for security vulnerabilities +dotnet_diagnostic.CA2100.severity = warning + +# CA2101: Specify marshaling for P/Invoke string arguments +dotnet_diagnostic.CA2101.severity = warning + +# CA2119: Seal methods that satisfy private interfaces +dotnet_diagnostic.CA2119.severity = warning + +# CA2153: Do Not Catch Corrupted State Exceptions +dotnet_diagnostic.CA2153.severity = warning + +# CA2200: Rethrow to preserve stack details +dotnet_diagnostic.CA2200.severity = warning + +# CA2201: Do not raise reserved exception types +dotnet_diagnostic.CA2201.severity = warning + +# CA2207: Initialize value type static fields inline +dotnet_diagnostic.CA2207.severity = warning + +# CA2208: Instantiate argument exceptions correctly +dotnet_diagnostic.CA2208.severity = warning + +# CA2211: Non-constant fields should not be visible +dotnet_diagnostic.CA2211.severity = warning + +# CA2213: Disposable fields should be disposed +dotnet_diagnostic.CA2213.severity = warning + +# CA2214: Do not call overridable methods in constructors +dotnet_diagnostic.CA2214.severity = warning + +# CA2215: Dispose methods should call base class dispose +dotnet_diagnostic.CA2215.severity = warning + +# CA2216: Disposable types should declare finalizer +dotnet_diagnostic.CA2216.severity = warning + +# CA2217: Do not mark enums with FlagsAttribute +dotnet_diagnostic.CA2217.severity = warning + +# CA2218: Override GetHashCode on overriding Equals +dotnet_diagnostic.CA2218.severity = warning + +# CA2219: Do not raise exceptions in finally clauses +dotnet_diagnostic.CA2219.severity = warning + +# CA2224: Override Equals on overloading operator equals +dotnet_diagnostic.CA2224.severity = warning + +# CA2225: Operator overloads have named alternates +dotnet_diagnostic.CA2225.severity = warning + +# CA2226: Operators should have symmetrical overloads +dotnet_diagnostic.CA2226.severity = warning + +# CA2227: Collection properties should be read only +dotnet_diagnostic.CA2227.severity = warning + +# CA2231: Overload operator equals on overriding value type Equals +dotnet_diagnostic.CA2231.severity = warning + +# CA2234: Pass system uri objects instead of strings +dotnet_diagnostic.CA2234.severity = warning + +# CA2235: Mark all non-serializable fields +dotnet_diagnostic.CA2235.severity = warning + +# CA2237: Mark ISerializable types with serializable +dotnet_diagnostic.CA2237.severity = warning + +# CA2241: Provide correct arguments to formatting methods +dotnet_diagnostic.CA2241.severity = warning + +# CA2242: Test for NaN correctly +dotnet_diagnostic.CA2242.severity = warning + +# CA2243: Attribute string literals should parse correctly +dotnet_diagnostic.CA2243.severity = warning + +# CA2244: Do not duplicate indexed element initializations +dotnet_diagnostic.CA2244.severity = warning + +# CA2245: Do not assign a property to itself +dotnet_diagnostic.CA2245.severity = warning + +# CA2246: Assigning symbol and its member in the same statement +dotnet_diagnostic.CA2246.severity = warning + +# CA2247: Argument passed to TaskCompletionSource constructor should be TaskCreationOptions enum instead of TaskContinuationOptions enum +dotnet_diagnostic.CA2247.severity = warning + +# CA2248: Provide correct 'enum' argument to 'Enum.HasFlag' +dotnet_diagnostic.CA2248.severity = warning + +# CA2249: Consider using 'string.Contains' instead of 'string.IndexOf' +dotnet_diagnostic.CA2249.severity = warning + +# CA2250: Use 'ThrowIfCancellationRequested' +dotnet_diagnostic.CA2250.severity = warning + +# CA2251: Use 'string.Equals' +dotnet_diagnostic.CA2251.severity = warning + +# CA2252: This API requires opting into preview features +dotnet_diagnostic.CA2252.severity = warning + +# CA2253: Named placeholders should not be numeric values +dotnet_diagnostic.CA2253.severity = warning + +# CA2254: Template should be a static expression +dotnet_diagnostic.CA2254.severity = warning + +# CA2255: The 'ModuleInitializer' attribute should not be used in libraries +dotnet_diagnostic.CA2255.severity = warning + +# CA2256: All members declared in parent interfaces must have an implementation in a DynamicInterfaceCastableImplementation-attributed interface +dotnet_diagnostic.CA2256.severity = warning + +# CA2257: Members defined on an interface with the 'DynamicInterfaceCastableImplementationAttribute' should be 'static' +dotnet_diagnostic.CA2257.severity = warning + +# CA2258: Providing a 'DynamicInterfaceCastableImplementation' interface in Visual Basic is unsupported +dotnet_diagnostic.CA2258.severity = warning + +# CA2259: 'ThreadStatic' only affects static fields +dotnet_diagnostic.CA2259.severity = warning + +# CA2260: Use correct type parameter +dotnet_diagnostic.CA2260.severity = warning + +# CA2261: Do not use ConfigureAwaitOptions.SuppressThrowing with Task +dotnet_diagnostic.CA2261.severity = warning + +# CA2300: Do not use insecure deserializer BinaryFormatter +dotnet_diagnostic.CA2300.severity = warning + +# CA2301: Do not call BinaryFormatter.Deserialize without first setting BinaryFormatter.Binder +dotnet_diagnostic.CA2301.severity = warning + +# CA2302: Ensure BinaryFormatter.Binder is set before calling BinaryFormatter.Deserialize +dotnet_diagnostic.CA2302.severity = warning + +# CA2305: Do not use insecure deserializer LosFormatter +dotnet_diagnostic.CA2305.severity = warning + +# CA2310: Do not use insecure deserializer NetDataContractSerializer +dotnet_diagnostic.CA2310.severity = warning + +# CA2311: Do not deserialize without first setting NetDataContractSerializer.Binder +dotnet_diagnostic.CA2311.severity = warning + +# CA2312: Ensure NetDataContractSerializer.Binder is set before deserializing +dotnet_diagnostic.CA2312.severity = warning + +# CA2315: Do not use insecure deserializer ObjectStateFormatter +dotnet_diagnostic.CA2315.severity = warning + +# CA2321: Do not deserialize with JavaScriptSerializer using a SimpleTypeResolver +dotnet_diagnostic.CA2321.severity = warning + +# CA2322: Ensure JavaScriptSerializer is not initialized with SimpleTypeResolver before deserializing +dotnet_diagnostic.CA2322.severity = warning + +# CA2326: Do not use TypeNameHandling values other than None +dotnet_diagnostic.CA2326.severity = warning + +# CA2327: Do not use insecure JsonSerializerSettings +dotnet_diagnostic.CA2327.severity = warning + +# CA2328: Ensure that JsonSerializerSettings are secure +dotnet_diagnostic.CA2328.severity = warning + +# CA2329: Do not deserialize with JsonSerializer using an insecure configuration +dotnet_diagnostic.CA2329.severity = warning + +# CA2330: Ensure that JsonSerializer has a secure configuration when deserializing +dotnet_diagnostic.CA2330.severity = warning + +# CA2350: Do not use DataTable.ReadXml() with untrusted data +dotnet_diagnostic.CA2350.severity = warning + +# CA2351: Do not use DataSet.ReadXml() with untrusted data +dotnet_diagnostic.CA2351.severity = warning + +# CA2352: Unsafe DataSet or DataTable in serializable type can be vulnerable to remote code execution attacks +dotnet_diagnostic.CA2352.severity = warning + +# CA2353: Unsafe DataSet or DataTable in serializable type +dotnet_diagnostic.CA2353.severity = warning + +# CA2354: Unsafe DataSet or DataTable in deserialized object graph can be vulnerable to remote code execution attacks +dotnet_diagnostic.CA2354.severity = warning + +# CA2355: Unsafe DataSet or DataTable type found in deserializable object graph +dotnet_diagnostic.CA2355.severity = warning + +# CA2356: Unsafe DataSet or DataTable type in web deserializable object graph +dotnet_diagnostic.CA2356.severity = warning + +# CA2361: Ensure auto-generated class containing DataSet.ReadXml() is not used with untrusted data +dotnet_diagnostic.CA2361.severity = warning + +# CA2362: Unsafe DataSet or DataTable in auto-generated serializable type can be vulnerable to remote code execution attacks +dotnet_diagnostic.CA2362.severity = warning + +# CA3001: Review code for SQL injection vulnerabilities +dotnet_diagnostic.CA3001.severity = warning + +# CA3002: Review code for XSS vulnerabilities +dotnet_diagnostic.CA3002.severity = warning + +# CA3003: Review code for file path injection vulnerabilities +dotnet_diagnostic.CA3003.severity = warning + +# CA3004: Review code for information disclosure vulnerabilities +dotnet_diagnostic.CA3004.severity = warning + +# CA3005: Review code for LDAP injection vulnerabilities +dotnet_diagnostic.CA3005.severity = warning + +# CA3006: Review code for process command injection vulnerabilities +dotnet_diagnostic.CA3006.severity = warning + +# CA3007: Review code for open redirect vulnerabilities +dotnet_diagnostic.CA3007.severity = warning + +# CA3008: Review code for XPath injection vulnerabilities +dotnet_diagnostic.CA3008.severity = warning + +# CA3009: Review code for XML injection vulnerabilities +dotnet_diagnostic.CA3009.severity = warning + +# CA3010: Review code for XAML injection vulnerabilities +dotnet_diagnostic.CA3010.severity = warning + +# CA3011: Review code for DLL injection vulnerabilities +dotnet_diagnostic.CA3011.severity = warning + +# CA3012: Review code for regex injection vulnerabilities +dotnet_diagnostic.CA3012.severity = warning + +# CA3061: Do Not Add Schema By URL +dotnet_diagnostic.CA3061.severity = warning + +# CA3075: Insecure DTD processing in XML +dotnet_diagnostic.CA3075.severity = warning + +# CA3076: Insecure XSLT script processing +dotnet_diagnostic.CA3076.severity = warning + +# CA3077: Insecure Processing in API Design, XmlDocument and XmlTextReader +dotnet_diagnostic.CA3077.severity = warning + +# CA3147: Mark Verb Handlers With Validate Antiforgery Token +dotnet_diagnostic.CA3147.severity = warning + +# CA5350: Do Not Use Weak Cryptographic Algorithms +dotnet_diagnostic.CA5350.severity = warning + +# CA5351: Do Not Use Broken Cryptographic Algorithms +dotnet_diagnostic.CA5351.severity = warning + +# CA5358: Review cipher mode usage with cryptography experts +dotnet_diagnostic.CA5358.severity = warning + +# CA5359: Do Not Disable Certificate Validation +dotnet_diagnostic.CA5359.severity = warning + +# CA5360: Do Not Call Dangerous Methods In Deserialization +dotnet_diagnostic.CA5360.severity = warning + +# CA5361: Do Not Disable SChannel Use of Strong Crypto +dotnet_diagnostic.CA5361.severity = warning + +# CA5362: Potential reference cycle in deserialized object graph +dotnet_diagnostic.CA5362.severity = warning + +# CA5363: Do Not Disable Request Validation +dotnet_diagnostic.CA5363.severity = warning + +# CA5364: Do Not Use Deprecated Security Protocols +dotnet_diagnostic.CA5364.severity = warning + +# CA5365: Do Not Disable HTTP Header Checking +dotnet_diagnostic.CA5365.severity = warning + +# CA5366: Use XmlReader for 'DataSet.ReadXml()' +dotnet_diagnostic.CA5366.severity = warning + +# CA5367: Do Not Serialize Types With Pointer Fields +dotnet_diagnostic.CA5367.severity = warning + +# CA5368: Set ViewStateUserKey For Classes Derived From Page +dotnet_diagnostic.CA5368.severity = warning + +# CA5369: Use XmlReader for 'XmlSerializer.Deserialize()' +dotnet_diagnostic.CA5369.severity = warning + +# CA5370: Use XmlReader for XmlValidatingReader constructor +dotnet_diagnostic.CA5370.severity = warning + +# CA5371: Use XmlReader for 'XmlSchema.Read()' +dotnet_diagnostic.CA5371.severity = warning + +# CA5372: Use XmlReader for XPathDocument constructor +dotnet_diagnostic.CA5372.severity = warning + +# CA5373: Do not use obsolete key derivation function +dotnet_diagnostic.CA5373.severity = warning + +# CA5374: Do Not Use XslTransform +dotnet_diagnostic.CA5374.severity = warning + +# CA5375: Do Not Use Account Shared Access Signature +dotnet_diagnostic.CA5375.severity = warning + +# CA5376: Use SharedAccessProtocol HttpsOnly +dotnet_diagnostic.CA5376.severity = warning + +# CA5377: Use Container Level Access Policy +dotnet_diagnostic.CA5377.severity = warning + +# CA5378: Do not disable ServicePointManagerSecurityProtocols +dotnet_diagnostic.CA5378.severity = warning + +# CA5379: Ensure Key Derivation Function algorithm is sufficiently strong +dotnet_diagnostic.CA5379.severity = warning + +# CA5380: Do Not Add Certificates To Root Store +dotnet_diagnostic.CA5380.severity = warning + +# CA5381: Ensure Certificates Are Not Added To Root Store +dotnet_diagnostic.CA5381.severity = warning + +# CA5382: Use Secure Cookies In ASP.NET Core +dotnet_diagnostic.CA5382.severity = warning + +# CA5383: Ensure Use Secure Cookies In ASP.NET Core +dotnet_diagnostic.CA5383.severity = warning + +# CA5384: Do Not Use Digital Signature Algorithm (DSA) +dotnet_diagnostic.CA5384.severity = warning + +# CA5385: Use Rivest-Shamir-Adleman (RSA) Algorithm With Sufficient Key Size +dotnet_diagnostic.CA5385.severity = warning + +# CA5386: Avoid hardcoding SecurityProtocolType value +dotnet_diagnostic.CA5386.severity = warning + +# CA5387: Do Not Use Weak Key Derivation Function With Insufficient Iteration Count +dotnet_diagnostic.CA5387.severity = warning + +# CA5388: Ensure Sufficient Iteration Count When Using Weak Key Derivation Function +dotnet_diagnostic.CA5388.severity = warning + +# CA5389: Do Not Add Archive Item's Path To The Target File System Path +dotnet_diagnostic.CA5389.severity = warning + +# CA5390: Do not hard-code encryption key +dotnet_diagnostic.CA5390.severity = warning + +# CA5391: Use antiforgery tokens in ASP.NET Core MVC controllers +dotnet_diagnostic.CA5391.severity = warning + +# CA5392: Use DefaultDllImportSearchPaths attribute for P/Invokes +dotnet_diagnostic.CA5392.severity = warning + +# CA5393: Do not use unsafe DllImportSearchPath value +dotnet_diagnostic.CA5393.severity = warning + +# CA5394: Do not use insecure randomness +dotnet_diagnostic.CA5394.severity = warning + +# CA5395: Miss HttpVerb attribute for action methods +dotnet_diagnostic.CA5395.severity = warning + +# CA5396: Set HttpOnly to true for HttpCookie +dotnet_diagnostic.CA5396.severity = warning + +# CA5397: Do not use deprecated SslProtocols values +dotnet_diagnostic.CA5397.severity = warning + +# CA5398: Avoid hardcoded SslProtocols values +dotnet_diagnostic.CA5398.severity = warning + +# CA5399: HttpClients should enable certificate revocation list checks +dotnet_diagnostic.CA5399.severity = warning + +# CA5400: Ensure HttpClient certificate revocation list check is not disabled +dotnet_diagnostic.CA5400.severity = warning + +# CA5401: Do not use CreateEncryptor with non-default IV +dotnet_diagnostic.CA5401.severity = warning + +# CA5402: Use CreateEncryptor with the default IV +dotnet_diagnostic.CA5402.severity = warning + +# CA5403: Do not hard-code certificate +dotnet_diagnostic.CA5403.severity = warning + +# CA5404: Do not disable token validation checks +dotnet_diagnostic.CA5404.severity = warning + +# CA5405: Do not always skip token validation in delegates +dotnet_diagnostic.CA5405.severity = warning + +[*.{cs,vb}] +#### Naming styles #### + +# Naming rules + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +# Symbol specifications + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +# Naming styles + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case +dotnet_style_operator_placement_when_wrapping = beginning_of_line +tab_width = 4 +indent_size = 4 +end_of_line = crlf +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_auto_properties = true:silent +dotnet_style_object_initializer = true:suggestion +dotnet_style_prefer_collection_expression = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_prefer_simplified_boolean_expressions = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_compound_assignment = true:suggestion +dotnet_style_prefer_simplified_interpolation = true:suggestion +dotnet_style_namespace_match_folder = true:suggestion + +[*.cs] +csharp_indent_labels = one_less_than_current +csharp_space_around_binary_operators = before_and_after +csharp_using_directive_placement = outside_namespace:silent +csharp_prefer_simple_using_statement = true:suggestion +csharp_prefer_braces = true:silent +csharp_style_namespace_declarations = block_scoped:silent +csharp_style_prefer_method_group_conversion = true:silent +csharp_style_prefer_top_level_statements = true:silent +csharp_style_prefer_primary_constructors = true:suggestion +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_local_functions = false:silent +csharp_style_throw_expression = true:suggestion +csharp_style_prefer_null_check_over_type_check = true:suggestion +csharp_prefer_simple_default_expression = true:suggestion +csharp_style_prefer_local_over_anonymous_function = true:suggestion +csharp_style_prefer_index_operator = true:suggestion +csharp_style_prefer_range_operator = true:suggestion +csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion +csharp_style_prefer_tuple_swap = true:suggestion +csharp_style_prefer_utf8_string_literals = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion +csharp_style_deconstructed_variable_declaration = true:suggestion +csharp_style_unused_value_assignment_preference = discard_variable:suggestion +csharp_style_unused_value_expression_statement_preference = discard_variable:silent diff --git a/RetainerTrack/Commands/WhoCommand.cs b/RetainerTrack/Commands/WhoCommand.cs new file mode 100644 index 0000000..08bfbfb --- /dev/null +++ b/RetainerTrack/Commands/WhoCommand.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Dalamud.Game.Command; +using Dalamud.Plugin.Services; +using Lumina.Excel.GeneratedSheets; +using RetainerTrack.Handlers; + +namespace RetainerTrack.Commands; + +internal sealed class WhoCommand : IDisposable +{ + private readonly PersistenceContext _persistenceContext; + private readonly ICommandManager _commandManager; + private readonly IChatGui _chatGui; + private readonly IClientState _clientState; + private readonly Dictionary _worlds; + + public WhoCommand(PersistenceContext persistenceContext, ICommandManager commandManager, IChatGui chatGui, + IClientState clientState, IDataManager dataManager) + { + _persistenceContext = persistenceContext; + _commandManager = commandManager; + _chatGui = chatGui; + _clientState = clientState; + _worlds = dataManager.GetExcelSheet()!.Where(x => x.IsPublic) + .ToDictionary(x => x.Name.ToString().ToUpperInvariant(), x => x.RowId); + _commandManager.AddHandler("/rwho", new CommandInfo(ProcessCommand) + { + HelpMessage = + "/rwho Character Name@World → Shows all retainers for the character (will use your current world if no world is specified)" + }); + } + + private void ProcessCommand(string command, string arguments) + { + string[] nameParts = arguments.Split(' '); + if (nameParts.Length != 2) + { + _chatGui.Print($"USAGE: /{command} Character Name@World"); + } + else if (nameParts[1].Contains('@', StringComparison.Ordinal)) + { + string[] lastNameParts = nameParts[1].Split('@'); + if (_worlds.TryGetValue(lastNameParts[1].ToUpperInvariant(), out uint worldId)) + ProcessLookup($"{nameParts[0]} {lastNameParts[0]}", worldId); + else + _chatGui.PrintError($"Unknown world: {lastNameParts[1]}"); + } + else + ProcessLookup(arguments, _clientState?.LocalPlayer?.CurrentWorld?.Id ?? 0); + } + + private void ProcessLookup(string name, uint world) + { + if (world == 0) + return; + + _chatGui.Print($"Retainer names for {name}: "); + var retainers = _persistenceContext.GetRetainerNamesForCharacter(name, world); + foreach (var retainerName in retainers) + _chatGui.Print($" - {retainerName}"); + if (retainers.Count == 0) + _chatGui.Print(" (No retainers found)"); + } + + public void Dispose() + { + _commandManager.RemoveHandler("/rwho"); + } +} diff --git a/RetainerTrack/Database/Compiled/PlayerEntityType.cs b/RetainerTrack/Database/Compiled/PlayerEntityType.cs new file mode 100644 index 0000000..a312b24 --- /dev/null +++ b/RetainerTrack/Database/Compiled/PlayerEntityType.cs @@ -0,0 +1,60 @@ +// +using System; +using System.Reflection; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Sqlite.Storage.Internal; + +#pragma warning disable 219, 612, 618 +#nullable disable + +namespace RetainerTrack.Database.Compiled +{ + internal partial class PlayerEntityType + { + public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType baseEntityType = null) + { + var runtimeEntityType = model.AddEntityType( + "RetainerTrack.Database.Player", + typeof(Player), + baseEntityType); + + var localContentId = runtimeEntityType.AddProperty( + "LocalContentId", + typeof(ulong), + propertyInfo: typeof(Player).GetProperty("LocalContentId", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(Player).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + valueGenerated: ValueGenerated.OnAdd, + afterSaveBehavior: PropertySaveBehavior.Throw, + sentinel: 0ul); + localContentId.TypeMapping = SqliteULongTypeMapping.Default; + + var name = runtimeEntityType.AddProperty( + "Name", + typeof(string), + propertyInfo: typeof(Player).GetProperty("Name", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(Player).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + maxLength: 20); + name.TypeMapping = SqliteStringTypeMapping.Default; + + var key = runtimeEntityType.AddKey( + new[] { localContentId }); + runtimeEntityType.SetPrimaryKey(key); + + return runtimeEntityType; + } + + public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) + { + runtimeEntityType.AddAnnotation("Relational:FunctionName", null); + runtimeEntityType.AddAnnotation("Relational:Schema", null); + runtimeEntityType.AddAnnotation("Relational:SqlQuery", null); + runtimeEntityType.AddAnnotation("Relational:TableName", "Players"); + runtimeEntityType.AddAnnotation("Relational:ViewName", null); + runtimeEntityType.AddAnnotation("Relational:ViewSchema", null); + + Customize(runtimeEntityType); + } + + static partial void Customize(RuntimeEntityType runtimeEntityType); + } +} diff --git a/RetainerTrack/Database/Compiled/RetainerEntityType.cs b/RetainerTrack/Database/Compiled/RetainerEntityType.cs new file mode 100644 index 0000000..1f3a62a --- /dev/null +++ b/RetainerTrack/Database/Compiled/RetainerEntityType.cs @@ -0,0 +1,92 @@ +// +using System; +using System.Reflection; +using Microsoft.EntityFrameworkCore.ChangeTracking; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Sqlite.Storage.Internal; +using Microsoft.EntityFrameworkCore.Storage; + +#pragma warning disable 219, 612, 618 +#nullable disable + +namespace RetainerTrack.Database.Compiled +{ + internal partial class RetainerEntityType + { + public static RuntimeEntityType Create(RuntimeModel model, RuntimeEntityType baseEntityType = null) + { + var runtimeEntityType = model.AddEntityType( + "RetainerTrack.Database.Retainer", + typeof(Retainer), + baseEntityType); + + var localContentId = runtimeEntityType.AddProperty( + "LocalContentId", + typeof(ulong), + propertyInfo: typeof(Retainer).GetProperty("LocalContentId", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(Retainer).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + valueGenerated: ValueGenerated.OnAdd, + afterSaveBehavior: PropertySaveBehavior.Throw, + sentinel: 0ul); + localContentId.TypeMapping = SqliteULongTypeMapping.Default; + + var name = runtimeEntityType.AddProperty( + "Name", + typeof(string), + propertyInfo: typeof(Retainer).GetProperty("Name", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(Retainer).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + maxLength: 24); + name.TypeMapping = SqliteStringTypeMapping.Default; + + var ownerLocalContentId = runtimeEntityType.AddProperty( + "OwnerLocalContentId", + typeof(ulong), + propertyInfo: typeof(Retainer).GetProperty("OwnerLocalContentId", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(Retainer).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + sentinel: 0ul); + ownerLocalContentId.TypeMapping = SqliteULongTypeMapping.Default; + + var worldId = runtimeEntityType.AddProperty( + "WorldId", + typeof(ushort), + propertyInfo: typeof(Retainer).GetProperty("WorldId", BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly), + fieldInfo: typeof(Retainer).GetField("k__BackingField", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly), + sentinel: (ushort)0); + worldId.TypeMapping = UShortTypeMapping.Default.Clone( + comparer: new ValueComparer( + (ushort v1, ushort v2) => v1 == v2, + (ushort v) => (int)v, + (ushort v) => v), + keyComparer: new ValueComparer( + (ushort v1, ushort v2) => v1 == v2, + (ushort v) => (int)v, + (ushort v) => v), + providerValueComparer: new ValueComparer( + (ushort v1, ushort v2) => v1 == v2, + (ushort v) => (int)v, + (ushort v) => v), + mappingInfo: new RelationalTypeMappingInfo( + storeTypeName: "INTEGER")); + + var key = runtimeEntityType.AddKey( + new[] { localContentId }); + runtimeEntityType.SetPrimaryKey(key); + + return runtimeEntityType; + } + + public static void CreateAnnotations(RuntimeEntityType runtimeEntityType) + { + runtimeEntityType.AddAnnotation("Relational:FunctionName", null); + runtimeEntityType.AddAnnotation("Relational:Schema", null); + runtimeEntityType.AddAnnotation("Relational:SqlQuery", null); + runtimeEntityType.AddAnnotation("Relational:TableName", "Retainers"); + runtimeEntityType.AddAnnotation("Relational:ViewName", null); + runtimeEntityType.AddAnnotation("Relational:ViewSchema", null); + + Customize(runtimeEntityType); + } + + static partial void Customize(RuntimeEntityType runtimeEntityType); + } +} diff --git a/RetainerTrack/Database/Compiled/RetainerTrackContextModel.cs b/RetainerTrack/Database/Compiled/RetainerTrackContextModel.cs new file mode 100644 index 0000000..e9ca19a --- /dev/null +++ b/RetainerTrack/Database/Compiled/RetainerTrackContextModel.cs @@ -0,0 +1,47 @@ +// +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; + +#pragma warning disable 219, 612, 618 +#nullable disable + +namespace RetainerTrack.Database.Compiled +{ + [DbContext(typeof(RetainerTrackContext))] + public partial class RetainerTrackContextModel : RuntimeModel + { + private static readonly bool _useOldBehavior31751 = + System.AppContext.TryGetSwitch("Microsoft.EntityFrameworkCore.Issue31751", out var enabled31751) && enabled31751; + + static RetainerTrackContextModel() + { + var model = new RetainerTrackContextModel(); + + if (_useOldBehavior31751) + { + model.Initialize(); + } + else + { + var thread = new System.Threading.Thread(RunInitialization, 10 * 1024 * 1024); + thread.Start(); + thread.Join(); + + void RunInitialization() + { + model.Initialize(); + } + } + + model.Customize(); + _instance = model; + } + + private static RetainerTrackContextModel _instance; + public static IModel Instance => _instance; + + partial void Initialize(); + + partial void Customize(); + } +} diff --git a/RetainerTrack/Database/Compiled/RetainerTrackContextModelBuilder.cs b/RetainerTrack/Database/Compiled/RetainerTrackContextModelBuilder.cs new file mode 100644 index 0000000..9e0c24a --- /dev/null +++ b/RetainerTrack/Database/Compiled/RetainerTrackContextModelBuilder.cs @@ -0,0 +1,122 @@ +// +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Metadata.Internal; + +#pragma warning disable 219, 612, 618 +#nullable disable + +namespace RetainerTrack.Database.Compiled +{ + public partial class RetainerTrackContextModel + { + partial void Initialize() + { + var player = PlayerEntityType.Create(this); + var retainer = RetainerEntityType.Create(this); + + PlayerEntityType.CreateAnnotations(player); + RetainerEntityType.CreateAnnotations(retainer); + + AddAnnotation("ProductVersion", "8.0.5"); + AddRuntimeAnnotation("Relational:RelationalModel", CreateRelationalModel()); + } + + private IRelationalModel CreateRelationalModel() + { + var relationalModel = new RelationalModel(this); + + var player = FindEntityType("RetainerTrack.Database.Player")!; + + var defaultTableMappings = new List>(); + player.SetRuntimeAnnotation("Relational:DefaultMappings", defaultTableMappings); + var retainerTrackDatabasePlayerTableBase = new TableBase("RetainerTrack.Database.Player", null, relationalModel); + var localContentIdColumnBase = new ColumnBase("LocalContentId", "INTEGER", retainerTrackDatabasePlayerTableBase); + retainerTrackDatabasePlayerTableBase.Columns.Add("LocalContentId", localContentIdColumnBase); + var nameColumnBase = new ColumnBase("Name", "TEXT", retainerTrackDatabasePlayerTableBase); + retainerTrackDatabasePlayerTableBase.Columns.Add("Name", nameColumnBase); + relationalModel.DefaultTables.Add("RetainerTrack.Database.Player", retainerTrackDatabasePlayerTableBase); + var retainerTrackDatabasePlayerMappingBase = new TableMappingBase(player, retainerTrackDatabasePlayerTableBase, true); + retainerTrackDatabasePlayerTableBase.AddTypeMapping(retainerTrackDatabasePlayerMappingBase, false); + defaultTableMappings.Add(retainerTrackDatabasePlayerMappingBase); + RelationalModel.CreateColumnMapping((ColumnBase)localContentIdColumnBase, player.FindProperty("LocalContentId")!, retainerTrackDatabasePlayerMappingBase); + RelationalModel.CreateColumnMapping((ColumnBase)nameColumnBase, player.FindProperty("Name")!, retainerTrackDatabasePlayerMappingBase); + + var tableMappings = new List(); + player.SetRuntimeAnnotation("Relational:TableMappings", tableMappings); + var playersTable = new Table("Players", null, relationalModel); + var localContentIdColumn = new Column("LocalContentId", "INTEGER", playersTable); + playersTable.Columns.Add("LocalContentId", localContentIdColumn); + var nameColumn = new Column("Name", "TEXT", playersTable); + playersTable.Columns.Add("Name", nameColumn); + var pK_Players = new UniqueConstraint("PK_Players", playersTable, new[] { localContentIdColumn }); + playersTable.PrimaryKey = pK_Players; + var pK_PlayersUc = RelationalModel.GetKey(this, + "RetainerTrack.Database.Player", + new[] { "LocalContentId" }); + pK_Players.MappedKeys.Add(pK_PlayersUc); + RelationalModel.GetOrCreateUniqueConstraints(pK_PlayersUc).Add(pK_Players); + playersTable.UniqueConstraints.Add("PK_Players", pK_Players); + relationalModel.Tables.Add(("Players", null), playersTable); + var playersTableMapping = new TableMapping(player, playersTable, true); + playersTable.AddTypeMapping(playersTableMapping, false); + tableMappings.Add(playersTableMapping); + RelationalModel.CreateColumnMapping(localContentIdColumn, player.FindProperty("LocalContentId")!, playersTableMapping); + RelationalModel.CreateColumnMapping(nameColumn, player.FindProperty("Name")!, playersTableMapping); + + var retainer = FindEntityType("RetainerTrack.Database.Retainer")!; + + var defaultTableMappings0 = new List>(); + retainer.SetRuntimeAnnotation("Relational:DefaultMappings", defaultTableMappings0); + var retainerTrackDatabaseRetainerTableBase = new TableBase("RetainerTrack.Database.Retainer", null, relationalModel); + var localContentIdColumnBase0 = new ColumnBase("LocalContentId", "INTEGER", retainerTrackDatabaseRetainerTableBase); + retainerTrackDatabaseRetainerTableBase.Columns.Add("LocalContentId", localContentIdColumnBase0); + var nameColumnBase0 = new ColumnBase("Name", "TEXT", retainerTrackDatabaseRetainerTableBase); + retainerTrackDatabaseRetainerTableBase.Columns.Add("Name", nameColumnBase0); + var ownerLocalContentIdColumnBase = new ColumnBase("OwnerLocalContentId", "INTEGER", retainerTrackDatabaseRetainerTableBase); + retainerTrackDatabaseRetainerTableBase.Columns.Add("OwnerLocalContentId", ownerLocalContentIdColumnBase); + var worldIdColumnBase = new ColumnBase("WorldId", "INTEGER", retainerTrackDatabaseRetainerTableBase); + retainerTrackDatabaseRetainerTableBase.Columns.Add("WorldId", worldIdColumnBase); + relationalModel.DefaultTables.Add("RetainerTrack.Database.Retainer", retainerTrackDatabaseRetainerTableBase); + var retainerTrackDatabaseRetainerMappingBase = new TableMappingBase(retainer, retainerTrackDatabaseRetainerTableBase, true); + retainerTrackDatabaseRetainerTableBase.AddTypeMapping(retainerTrackDatabaseRetainerMappingBase, false); + defaultTableMappings0.Add(retainerTrackDatabaseRetainerMappingBase); + RelationalModel.CreateColumnMapping((ColumnBase)localContentIdColumnBase0, retainer.FindProperty("LocalContentId")!, retainerTrackDatabaseRetainerMappingBase); + RelationalModel.CreateColumnMapping((ColumnBase)nameColumnBase0, retainer.FindProperty("Name")!, retainerTrackDatabaseRetainerMappingBase); + RelationalModel.CreateColumnMapping((ColumnBase)ownerLocalContentIdColumnBase, retainer.FindProperty("OwnerLocalContentId")!, retainerTrackDatabaseRetainerMappingBase); + RelationalModel.CreateColumnMapping((ColumnBase)worldIdColumnBase, retainer.FindProperty("WorldId")!, retainerTrackDatabaseRetainerMappingBase); + + var tableMappings0 = new List(); + retainer.SetRuntimeAnnotation("Relational:TableMappings", tableMappings0); + var retainersTable = new Table("Retainers", null, relationalModel); + var localContentIdColumn0 = new Column("LocalContentId", "INTEGER", retainersTable); + retainersTable.Columns.Add("LocalContentId", localContentIdColumn0); + var nameColumn0 = new Column("Name", "TEXT", retainersTable); + retainersTable.Columns.Add("Name", nameColumn0); + var ownerLocalContentIdColumn = new Column("OwnerLocalContentId", "INTEGER", retainersTable); + retainersTable.Columns.Add("OwnerLocalContentId", ownerLocalContentIdColumn); + var worldIdColumn = new Column("WorldId", "INTEGER", retainersTable); + retainersTable.Columns.Add("WorldId", worldIdColumn); + var pK_Retainers = new UniqueConstraint("PK_Retainers", retainersTable, new[] { localContentIdColumn0 }); + retainersTable.PrimaryKey = pK_Retainers; + var pK_RetainersUc = RelationalModel.GetKey(this, + "RetainerTrack.Database.Retainer", + new[] { "LocalContentId" }); + pK_Retainers.MappedKeys.Add(pK_RetainersUc); + RelationalModel.GetOrCreateUniqueConstraints(pK_RetainersUc).Add(pK_Retainers); + retainersTable.UniqueConstraints.Add("PK_Retainers", pK_Retainers); + relationalModel.Tables.Add(("Retainers", null), retainersTable); + var retainersTableMapping = new TableMapping(retainer, retainersTable, true); + retainersTable.AddTypeMapping(retainersTableMapping, false); + tableMappings0.Add(retainersTableMapping); + RelationalModel.CreateColumnMapping(localContentIdColumn0, retainer.FindProperty("LocalContentId")!, retainersTableMapping); + RelationalModel.CreateColumnMapping(nameColumn0, retainer.FindProperty("Name")!, retainersTableMapping); + RelationalModel.CreateColumnMapping(ownerLocalContentIdColumn, retainer.FindProperty("OwnerLocalContentId")!, retainersTableMapping); + RelationalModel.CreateColumnMapping(worldIdColumn, retainer.FindProperty("WorldId")!, retainersTableMapping); + return relationalModel.MakeReadOnly(); + } + } +} diff --git a/RetainerTrack/Database/Migrations/.editorconfig b/RetainerTrack/Database/Migrations/.editorconfig new file mode 100644 index 0000000..88c09f9 --- /dev/null +++ b/RetainerTrack/Database/Migrations/.editorconfig @@ -0,0 +1,3 @@ +[*.cs] +# CA1062: Validate arguments of public methods +dotnet_diagnostic.CA1062.severity = none diff --git a/RetainerTrack/Database/Migrations/20240524200204_InitialCreate.Designer.cs b/RetainerTrack/Database/Migrations/20240524200204_InitialCreate.Designer.cs new file mode 100644 index 0000000..5cee9e4 --- /dev/null +++ b/RetainerTrack/Database/Migrations/20240524200204_InitialCreate.Designer.cs @@ -0,0 +1,62 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using RetainerTrack.Database; + +#nullable disable + +namespace RetainerTrack.Database.Migrations +{ + [DbContext(typeof(RetainerTrackContext))] + [Migration("20240524200204_InitialCreate")] + partial class InitialCreate + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.5"); + + modelBuilder.Entity("RetainerTrack.Database.Player", b => + { + b.Property("LocalContentId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("LocalContentId"); + + b.ToTable("Players"); + }); + + modelBuilder.Entity("RetainerTrack.Database.Retainer", b => + { + b.Property("LocalContentId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(24) + .HasColumnType("TEXT"); + + b.Property("OwnerLocalContentId") + .HasColumnType("INTEGER"); + + b.Property("WorldId") + .HasColumnType("INTEGER"); + + b.HasKey("LocalContentId"); + + b.ToTable("Retainers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/RetainerTrack/Database/Migrations/20240524200204_InitialCreate.cs b/RetainerTrack/Database/Migrations/20240524200204_InitialCreate.cs new file mode 100644 index 0000000..070b57b --- /dev/null +++ b/RetainerTrack/Database/Migrations/20240524200204_InitialCreate.cs @@ -0,0 +1,52 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace RetainerTrack.Database.Migrations +{ + /// + public partial class InitialCreate : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Players", + columns: table => new + { + LocalContentId = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Name = table.Column(type: "TEXT", maxLength: 20, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Players", x => x.LocalContentId); + }); + + migrationBuilder.CreateTable( + name: "Retainers", + columns: table => new + { + LocalContentId = table.Column(type: "INTEGER", nullable: false) + .Annotation("Sqlite:Autoincrement", true), + Name = table.Column(type: "TEXT", maxLength: 24, nullable: false), + WorldId = table.Column(type: "INTEGER", nullable: false), + OwnerLocalContentId = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Retainers", x => x.LocalContentId); + }); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "Players"); + + migrationBuilder.DropTable( + name: "Retainers"); + } + } +} diff --git a/RetainerTrack/Database/Migrations/20240524201345_ImportLegacyData.Designer.cs b/RetainerTrack/Database/Migrations/20240524201345_ImportLegacyData.Designer.cs new file mode 100644 index 0000000..584c8cc --- /dev/null +++ b/RetainerTrack/Database/Migrations/20240524201345_ImportLegacyData.Designer.cs @@ -0,0 +1,62 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using RetainerTrack.Database; + +#nullable disable + +namespace RetainerTrack.Database.Migrations +{ + [DbContext(typeof(RetainerTrackContext))] + [Migration("20240524201345_ImportLegacyData")] + partial class ImportLegacyData + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.5"); + + modelBuilder.Entity("RetainerTrack.Database.Player", b => + { + b.Property("LocalContentId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("LocalContentId"); + + b.ToTable("Players"); + }); + + modelBuilder.Entity("RetainerTrack.Database.Retainer", b => + { + b.Property("LocalContentId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(24) + .HasColumnType("TEXT"); + + b.Property("OwnerLocalContentId") + .HasColumnType("INTEGER"); + + b.Property("WorldId") + .HasColumnType("INTEGER"); + + b.HasKey("LocalContentId"); + + b.ToTable("Retainers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/RetainerTrack/Database/Migrations/20240524201345_ImportLegacyData.cs b/RetainerTrack/Database/Migrations/20240524201345_ImportLegacyData.cs new file mode 100644 index 0000000..332eaac --- /dev/null +++ b/RetainerTrack/Database/Migrations/20240524201345_ImportLegacyData.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using Dalamud.Plugin; +using LiteDB; +using Microsoft.EntityFrameworkCore.Migrations; +using RetainerTrack.LegacyDb; + +#nullable disable +#pragma warning disable 0612 + +namespace RetainerTrack.Database.Migrations +{ + /// + public partial class ImportLegacyData : Migration + { + public static DalamudPluginInterface PluginInterface { get; set; } + + private static readonly string[] PlayerColumns = new[] { "LocalContentId", "Name" }; + private static readonly string[] RetainerColumns = new []{ "LocalContentId", "Name", "WorldId", "OwnerLocalContentId" }; + + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + if (PluginInterface == null) + return; + + string legacyDatabaseFileName = Path.Join(PluginInterface.GetPluginConfigDirectory(), "retainer-data.litedb"); + if (!File.Exists(legacyDatabaseFileName)) + return; + + using var liteDatabase = new LiteDatabase(new ConnectionString + { + Filename = Path.Join(PluginInterface.GetPluginConfigDirectory(), "retainer-data.litedb"), + Connection = ConnectionType.Direct, + Upgrade = true, + }, + new BsonMapper + { + ResolveCollectionName = (type) => + { + if (type == typeof(LegacyPlayer)) + return LegacyPlayer.CollectionName; + + if (type == typeof(LegacyRetainer)) + return LegacyRetainer.CollectionName; + throw new ArgumentOutOfRangeException(nameof(type)); + } + }); + liteDatabase.GetCollection() + .EnsureIndex(x => x.Id); + liteDatabase.GetCollection() + .EnsureIndex(x => x.Id); + + List allPlayers = liteDatabase.GetCollection().FindAll().ToList(); + object[,] playersToInsert = To2DArray( + allPlayers.Select(player => new object[] { player.Id, player.Name }).ToList(), + PlayerColumns.Length); + + migrationBuilder.InsertData( + table:"Players", + columns: PlayerColumns, + values: playersToInsert); + + List allRetainers = liteDatabase.GetCollection().FindAll().ToList(); + object[,] retainersToInsert = To2DArray( + allRetainers.Select(retainer => new object[] { retainer.Id, retainer.Name, retainer.WorldId, retainer.OwnerContentId }).ToList(), + RetainerColumns.Length); + + migrationBuilder.InsertData( + table: "Retainers", + columns: RetainerColumns, values: retainersToInsert); + } + + [SuppressMessage("Performance", "CA1814")] + private static object[,] To2DArray(IReadOnlyList data, int columnCount) + { + object[,] result = new object[data.Count, columnCount]; + for (int i = 0; i < data.Count; i++) + { + for (int j = 0; j < columnCount; j++) + { + result[i, j] = data[i][j]; + } + } + return result; + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql("DELETE FROM Players"); + migrationBuilder.Sql("DELETE FROM Retainers"); + } + } +} + +#pragma warning restore 0612 diff --git a/RetainerTrack/Database/Migrations/20240524214606_CleanupBrokenPlayerIds.Designer.cs b/RetainerTrack/Database/Migrations/20240524214606_CleanupBrokenPlayerIds.Designer.cs new file mode 100644 index 0000000..78ca45d --- /dev/null +++ b/RetainerTrack/Database/Migrations/20240524214606_CleanupBrokenPlayerIds.Designer.cs @@ -0,0 +1,62 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using RetainerTrack.Database; + +#nullable disable + +namespace RetainerTrack.Database.Migrations +{ + [DbContext(typeof(RetainerTrackContext))] + [Migration("20240524214606_CleanupBrokenPlayerIds")] + partial class CleanupBrokenPlayerIds + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.5"); + + modelBuilder.Entity("RetainerTrack.Database.Player", b => + { + b.Property("LocalContentId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("LocalContentId"); + + b.ToTable("Players"); + }); + + modelBuilder.Entity("RetainerTrack.Database.Retainer", b => + { + b.Property("LocalContentId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(24) + .HasColumnType("TEXT"); + + b.Property("OwnerLocalContentId") + .HasColumnType("INTEGER"); + + b.Property("WorldId") + .HasColumnType("INTEGER"); + + b.HasKey("LocalContentId"); + + b.ToTable("Retainers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/RetainerTrack/Database/Migrations/20240524214606_CleanupBrokenPlayerIds.cs b/RetainerTrack/Database/Migrations/20240524214606_CleanupBrokenPlayerIds.cs new file mode 100644 index 0000000..94e144b --- /dev/null +++ b/RetainerTrack/Database/Migrations/20240524214606_CleanupBrokenPlayerIds.cs @@ -0,0 +1,22 @@ +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace RetainerTrack.Database.Migrations +{ + /// + public partial class CleanupBrokenPlayerIds : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql("DELETE FROM Players WHERE LocalContentId < 18014398509481984"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + + } + } +} diff --git a/RetainerTrack/Database/Migrations/RetainerTrackContextModelSnapshot.cs b/RetainerTrack/Database/Migrations/RetainerTrackContextModelSnapshot.cs new file mode 100644 index 0000000..eab2ab6 --- /dev/null +++ b/RetainerTrack/Database/Migrations/RetainerTrackContextModelSnapshot.cs @@ -0,0 +1,59 @@ +// +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using RetainerTrack.Database; + +#nullable disable + +namespace RetainerTrack.Database.Migrations +{ + [DbContext(typeof(RetainerTrackContext))] + partial class RetainerTrackContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.0.5"); + + modelBuilder.Entity("RetainerTrack.Database.Player", b => + { + b.Property("LocalContentId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(20) + .HasColumnType("TEXT"); + + b.HasKey("LocalContentId"); + + b.ToTable("Players"); + }); + + modelBuilder.Entity("RetainerTrack.Database.Retainer", b => + { + b.Property("LocalContentId") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(24) + .HasColumnType("TEXT"); + + b.Property("OwnerLocalContentId") + .HasColumnType("INTEGER"); + + b.Property("WorldId") + .HasColumnType("INTEGER"); + + b.HasKey("LocalContentId"); + + b.ToTable("Retainers"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/RetainerTrack/Database/Player.cs b/RetainerTrack/Database/Player.cs index ccda75a..77968be 100644 --- a/RetainerTrack/Database/Player.cs +++ b/RetainerTrack/Database/Player.cs @@ -1,8 +1,12 @@ -namespace RetainerTrack.Database +using System.ComponentModel.DataAnnotations; + +namespace RetainerTrack.Database; + +public class Player { - internal sealed class Player - { - public ulong Id { get; set; } - public string? Name { get; set; } - } + [Key, Required] + public ulong LocalContentId { get; set; } + + [MaxLength(20), Required] + public string? Name { get; set; } } diff --git a/RetainerTrack/Database/Retainer.cs b/RetainerTrack/Database/Retainer.cs index 4456dda..6c83623 100644 --- a/RetainerTrack/Database/Retainer.cs +++ b/RetainerTrack/Database/Retainer.cs @@ -1,10 +1,18 @@ -namespace RetainerTrack.Database +using System.ComponentModel.DataAnnotations; + +namespace RetainerTrack.Database; + +public class Retainer { - internal sealed class Retainer - { - public ulong Id { get; set; } - public string? Name { get; set; } - public ushort WorldId { get; set; } - public ulong OwnerContentId { get; set; } - } + [Key, Required] + public ulong LocalContentId { get; set; } + + [MaxLength(24), Required] + public string? Name { get; set; } + + [Required] + public ushort WorldId { get; set; } + + [Required] + public ulong OwnerLocalContentId { get; set; } } diff --git a/RetainerTrack/Database/RetainerTrackContext.cs b/RetainerTrack/Database/RetainerTrackContext.cs new file mode 100644 index 0000000..1a1a0b1 --- /dev/null +++ b/RetainerTrack/Database/RetainerTrackContext.cs @@ -0,0 +1,14 @@ +using Microsoft.EntityFrameworkCore; + +namespace RetainerTrack.Database; + +internal sealed class RetainerTrackContext : DbContext +{ + public DbSet Retainers { get; set; } + public DbSet Players { get; set; } + + public RetainerTrackContext(DbContextOptions options) + : base(options) + { + } +} diff --git a/RetainerTrack/Database/RetainerTrackContextFactory.cs b/RetainerTrack/Database/RetainerTrackContextFactory.cs new file mode 100644 index 0000000..1bebfb0 --- /dev/null +++ b/RetainerTrack/Database/RetainerTrackContextFactory.cs @@ -0,0 +1,19 @@ +#if EF +using System; +using System.IO; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Design; + +namespace RetainerTrack.Database; + +internal sealed class PalClientContextFactory : IDesignTimeDbContextFactory +{ + public RetainerTrackContext CreateDbContext(string[] args) + { + var optionsBuilder = + new DbContextOptionsBuilder().UseSqlite( + $"Data Source={Path.Join(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "XIVLauncher", "pluginConfigs", "RetainerTrack", RetainerTrackPlugin.DatabaseFileName)}"); + return new RetainerTrackContext(optionsBuilder.Options); + } +} +#endif diff --git a/RetainerTrack/Handlers/ContentIdToName.cs b/RetainerTrack/Handlers/ContentIdToName.cs index 5221467..a0d1fc1 100644 --- a/RetainerTrack/Handlers/ContentIdToName.cs +++ b/RetainerTrack/Handlers/ContentIdToName.cs @@ -1,8 +1,7 @@ -namespace RetainerTrack.Handlers +namespace RetainerTrack.Handlers; + +internal sealed class ContentIdToName { - internal sealed class ContentIdToName - { - public ulong ContentId { get; init; } - public string PlayerName { get; init; } = string.Empty; - } + public ulong ContentId { get; init; } + public string PlayerName { get; init; } = string.Empty; } diff --git a/RetainerTrack/Handlers/GameHooks.cs b/RetainerTrack/Handlers/GameHooks.cs index 6e933b3..c7af3c6 100644 --- a/RetainerTrack/Handlers/GameHooks.cs +++ b/RetainerTrack/Handlers/GameHooks.cs @@ -10,60 +10,94 @@ using Dalamud.Plugin.Services; using Dalamud.Utility.Signatures; using Microsoft.Extensions.Logging; -namespace RetainerTrack.Handlers +namespace RetainerTrack.Handlers; + +internal sealed unsafe class GameHooks : IDisposable { - internal sealed unsafe class GameHooks : IDisposable - { - private readonly ILogger _logger; - private readonly PersistenceContext _persistenceContext; + private readonly ILogger _logger; + private readonly PersistenceContext _persistenceContext; - /// - /// Processes the content id to character name packet, seen e.g. when you hover an item to retrieve the - /// crafter's signature. - /// - private delegate int CharacterNameResultDelegate(nint a1, ulong contentId, char* playerName); + /// + /// Processes the content id to character name packet, seen e.g. when you hover an item to retrieve the + /// crafter's signature. + /// + private delegate int CharacterNameResultDelegate(nint a1, ulong contentId, char* playerName); - private delegate nint SocialListResultDelegate(nint a1, nint dataPtr); + private delegate nint SocialListResultDelegate(nint a1, nint dataPtr); #pragma warning disable CS0649 - [Signature("40 53 48 83 EC 20 48 8B D9 33 C9 45 33 C9", DetourName = nameof(ProcessCharacterNameResult))] - private Hook CharacterNameResultHook { get; init; } = null!; + [Signature("40 53 48 83 EC 20 48 8B D9 33 C9 45 33 C9", DetourName = nameof(ProcessCharacterNameResult))] + private Hook CharacterNameResultHook { get; init; } = null!; - // Signature adapted from https://github.com/LittleNightmare/UsedName - [Signature("48 89 5C 24 10 56 48 83 EC 20 48 ?? ?? ?? ?? ?? ?? 48 8B F2 E8 ?? ?? ?? ?? 48 8B D8", - DetourName = nameof(ProcessSocialListResult))] - private Hook SocialListResultHook { get; init; } = null!; + // Signature adapted from https://github.com/LittleNightmare/UsedName + [Signature("48 89 5C 24 10 56 48 83 EC 20 48 ?? ?? ?? ?? ?? ?? 48 8B F2 E8 ?? ?? ?? ?? 48 8B D8", + DetourName = nameof(ProcessSocialListResult))] + private Hook SocialListResultHook { get; init; } = null!; #pragma warning restore CS0649 - public GameHooks(ILogger logger, PersistenceContext persistenceContext, IGameInteropProvider gameInteropProvider) + public GameHooks(ILogger logger, PersistenceContext persistenceContext, IGameInteropProvider gameInteropProvider) + { + _logger = logger; + _persistenceContext = persistenceContext; + + _logger.LogDebug("Initializing game hooks"); + gameInteropProvider.InitializeFromAttributes(this); + CharacterNameResultHook.Enable(); + SocialListResultHook.Enable(); + + _logger.LogDebug("Game hooks initialized"); + } + + private int ProcessCharacterNameResult(nint a1, ulong contentId, char* playerName) + { + try { - _logger = logger; - _persistenceContext = persistenceContext; + var mapping = new ContentIdToName + { + ContentId = contentId, + PlayerName = MemoryHelper.ReadString(new nint(playerName), Encoding.ASCII, 32), + }; - _logger.LogDebug("Initializing game hooks"); - gameInteropProvider.InitializeFromAttributes(this); - CharacterNameResultHook.Enable(); - SocialListResultHook.Enable(); - - _logger.LogDebug("Game hooks initialized"); + if (!string.IsNullOrEmpty(mapping.PlayerName)) + { + _logger.LogTrace("Content id {ContentId} belongs to '{Name}'", mapping.ContentId, + mapping.PlayerName); + Task.Run(() => _persistenceContext.HandleContentIdMapping(mapping)); + } + else + { + _logger.LogDebug("Content id {ContentId} didn't resolve to a player name, ignoring", + mapping.ContentId); + } + } + catch (Exception e) + { + _logger.LogError(e, "Could not process character name result"); } - private int ProcessCharacterNameResult(nint a1, ulong contentId, char* playerName) + return CharacterNameResultHook.Original(a1, contentId, playerName); + } + + private nint ProcessSocialListResult(nint a1, nint dataPtr) + { + try { - try + var result = Marshal.PtrToStructure(dataPtr); + List mappings = new(); + foreach (SocialListPlayer player in result.PlayerSpan) { var mapping = new ContentIdToName { - ContentId = contentId, - PlayerName = MemoryHelper.ReadString(new nint(playerName), Encoding.ASCII, 32), + ContentId = player.ContentId, + PlayerName = MemoryHelper.ReadString(new nint(player.CharacterName), Encoding.ASCII, 32), }; if (!string.IsNullOrEmpty(mapping.PlayerName)) { _logger.LogTrace("Content id {ContentId} belongs to '{Name}'", mapping.ContentId, mapping.PlayerName); - Task.Run(() => _persistenceContext.HandleContentIdMapping(mapping)); + mappings.Add(mapping); } else { @@ -71,92 +105,57 @@ namespace RetainerTrack.Handlers mapping.ContentId); } } - catch (Exception e) - { - _logger.LogError(e, "Could not process character name result"); - } - return CharacterNameResultHook.Original(a1, contentId, playerName); + if (mappings.Count > 0) + Task.Run(() => _persistenceContext.HandleContentIdMapping(mappings)); } - - private nint ProcessSocialListResult(nint a1, nint dataPtr) + catch (Exception e) { - try - { - var result = Marshal.PtrToStructure(dataPtr); - List mappings = new(); - foreach (SocialListPlayer player in result.PlayerSpan) - { - var mapping = new ContentIdToName - { - ContentId = player.ContentId, - PlayerName = MemoryHelper.ReadString(new nint(player.CharacterName), Encoding.ASCII, 32), - }; - - if (!string.IsNullOrEmpty(mapping.PlayerName)) - { - _logger.LogTrace("Content id {ContentId} belongs to '{Name}'", mapping.ContentId, - mapping.PlayerName); - mappings.Add(mapping); - } - else - { - _logger.LogDebug("Content id {ContentId} didn't resolve to a player name, ignoring", - mapping.ContentId); - } - } - - if (mappings.Count > 0) - Task.Run(() => _persistenceContext.HandleContentIdMapping(mappings)); - } - catch (Exception e) - { - _logger.LogError(e, "Could not process social list result"); - } - - return SocialListResultHook.Original(a1, dataPtr); + _logger.LogError(e, "Could not process social list result"); } - public void Dispose() - { - CharacterNameResultHook.Dispose(); - SocialListResultHook.Dispose(); - } + return SocialListResultHook.Original(a1, dataPtr); + } + + public void Dispose() + { + CharacterNameResultHook.Dispose(); + SocialListResultHook.Dispose(); + } + + /// + /// There are some caveats here, the social list includes a LOT of things with different types + /// (we don't care for the result type in this plugin), see sapphire for which field is the type. + /// + /// 1 = party + /// 2 = friend list + /// 3 = link shell + /// 4 = player search + /// 5 = fc short list (first tab, with company board + actions + online members) + /// 6 = fc long list (members tab) + /// + /// Both 1 and 2 are sent to you on login, unprompted. + /// + [StructLayout(LayoutKind.Explicit, Size = 0x380)] + internal struct SocialListResultPage + { + [FieldOffset(0x10)] private fixed byte Players[10 * 0x58]; + + public Span PlayerSpan => new(Unsafe.AsPointer(ref Players[0]), 10); + } + + [StructLayout(LayoutKind.Explicit, Size = 0x58)] + internal struct SocialListPlayer + { + /// + /// If this is set, it means there is a player present in this slot (even if no name can be retrieved), + /// 0 if empty. + /// + [FieldOffset(0x00)] public readonly ulong ContentId; /// - /// There are some caveats here, the social list includes a LOT of things with different types - /// (we don't care for the result type in this plugin), see sapphire for which field is the type. - /// - /// 1 = party - /// 2 = friend list - /// 3 = link shell - /// 4 = player search - /// 5 = fc short list (first tab, with company board + actions + online members) - /// 6 = fc long list (members tab) - /// - /// Both 1 and 2 are sent to you on login, unprompted. + /// This *can* be empty, e.g. if you're querying your friend list, the names are ONLY set for characters on the same world. /// - [StructLayout(LayoutKind.Explicit, Size = 0x380)] - internal struct SocialListResultPage - { - [FieldOffset(0x10)] private fixed byte Players[10 * 0x58]; - - public Span PlayerSpan => new(Unsafe.AsPointer(ref Players[0]), 10); - } - - [StructLayout(LayoutKind.Explicit, Size = 0x58)] - internal struct SocialListPlayer - { - /// - /// If this is set, it means there is a player present in this slot (even if no name can be retrieved), - /// 0 if empty. - /// - [FieldOffset(0x00)] public readonly ulong ContentId; - - /// - /// This *can* be empty, e.g. if you're querying your friend list, the names are ONLY set for characters on the same world. - /// - [FieldOffset(0x31)] public fixed byte CharacterName[32]; - } + [FieldOffset(0x31)] public fixed byte CharacterName[32]; } } diff --git a/RetainerTrack/Handlers/MarketBoardOfferingsHandler.cs b/RetainerTrack/Handlers/MarketBoardOfferingsHandler.cs index 40bc556..e35b5a4 100644 --- a/RetainerTrack/Handlers/MarketBoardOfferingsHandler.cs +++ b/RetainerTrack/Handlers/MarketBoardOfferingsHandler.cs @@ -3,77 +3,71 @@ using System.Threading.Tasks; using Dalamud.Game.Network.Structures; using Dalamud.Hooking; using Dalamud.Plugin.Services; -using FFXIVClientStructs.FFXIV.Client.UI; using FFXIVClientStructs.FFXIV.Client.UI.Info; using Microsoft.Extensions.Logging; -namespace RetainerTrack.Handlers +namespace RetainerTrack.Handlers; + +internal sealed class MarketBoardOfferingsHandler : IDisposable { - internal sealed class MarketBoardOfferingsHandler : IDisposable + private unsafe delegate void* MarketBoardOfferings(InfoProxyItemSearch* a1, nint packetData); + + private readonly ILogger _logger; + private readonly IClientState _clientState; + private readonly PersistenceContext _persistenceContext; + private readonly Hook _marketBoardOfferingsHook; + + public unsafe MarketBoardOfferingsHandler( + ILogger logger, + IClientState clientState, + IGameInteropProvider gameInteropProvider, + PersistenceContext persistenceContext) { - private unsafe delegate void* MarketBoardOfferings(InfoProxyItemSearch* a1, nint packetData); + _logger = logger; + _clientState = clientState; + _persistenceContext = persistenceContext; - private readonly ILogger _logger; - private readonly IClientState _clientState; - private readonly PersistenceContext _persistenceContext; - private readonly Hook _marketBoardOfferingsHook; + _logger.LogDebug("Setting up offerings hook"); + _marketBoardOfferingsHook = + gameInteropProvider.HookFromSignature("48 89 5C 24 ?? 57 48 81 EC ?? ?? ?? ?? 48 8B 05 ?? ?? ?? ?? 48 33 C4 48 89 84 24 ?? ?? ?? ?? 0F B6 82 ?? ?? ?? ?? 48 8B FA 48 8B D9 38 41 19 74 54", + MarketBoardOfferingsDetour); + _marketBoardOfferingsHook.Enable(); + _logger.LogDebug("Offerings hook enabled successfully"); + } - public unsafe MarketBoardOfferingsHandler( - ILogger logger, - IClientState clientState, - IGameGui gameGui, - IGameInteropProvider gameInteropProvider, - PersistenceContext persistenceContext) + public void Dispose() + { + _marketBoardOfferingsHook.Dispose(); + } + + // adapted from https://github.com/tesu/PennyPincher/commit/0f9b3963fd4a6e9b87f585ee491d4de59a93f7a3 + private unsafe void* MarketBoardOfferingsDetour(InfoProxyItemSearch* a1, nint packetData) + { + try { - _logger = logger; - _clientState = clientState; - _persistenceContext = persistenceContext; - - _logger.LogDebug("Setting up offerings hook"); - var uiModule = (UIModule*)gameGui.GetUIModule(); - var infoModule = uiModule->GetInfoModule(); - var proxy = infoModule->GetInfoProxyById(11); - _marketBoardOfferingsHook = - gameInteropProvider.HookFromAddress((nint)proxy->vtbl[12], - MarketBoardOfferingsDetour); - _marketBoardOfferingsHook.Enable(); - _logger.LogDebug("Offerings hook enabled successfully"); - } - - public void Dispose() - { - _marketBoardOfferingsHook.Dispose(); - } - - // adapted from https://github.com/tesu/PennyPincher/commit/0f9b3963fd4a6e9b87f585ee491d4de59a93f7a3 - private unsafe void* MarketBoardOfferingsDetour(InfoProxyItemSearch* a1, nint packetData) - { - try + if (packetData != nint.Zero) { - if (packetData != nint.Zero) - { - ParseOfferings(packetData); - } + ParseOfferings(packetData); } - catch (Exception e) - { - _logger.LogError(e, "Could not parse marketboard offerings."); - } - - return _marketBoardOfferingsHook.Original(a1, packetData); } - - private void ParseOfferings(nint dataPtr) + catch (Exception e) { - ushort worldId = (ushort?)_clientState.LocalPlayer?.CurrentWorld.Id ?? 0; - if (worldId == 0) - { - _logger.LogInformation("Skipping market board handler, current world unknown"); - return; - } - - var listings = MarketBoardCurrentOfferings.Read(dataPtr); - Task.Run(() => _persistenceContext.HandleMarketBoardPage(listings, worldId)); + _logger.LogError(e, "Could not parse marketboard offerings."); } + + return _marketBoardOfferingsHook.Original(a1, packetData); + } + + private void ParseOfferings(nint dataPtr) + { + ushort worldId = (ushort?)_clientState.LocalPlayer?.CurrentWorld.Id ?? 0; + if (worldId == 0) + { + _logger.LogInformation("Skipping market board handler, current world unknown"); + return; + } + + var listings = MarketBoardCurrentOfferings.Read(dataPtr); + Task.Run(() => _persistenceContext.HandleMarketBoardPage(listings, worldId)); } } diff --git a/RetainerTrack/Handlers/MarketBoardUIHandler.cs b/RetainerTrack/Handlers/MarketBoardUIHandler.cs index 6bd957b..cca9b51 100644 --- a/RetainerTrack/Handlers/MarketBoardUIHandler.cs +++ b/RetainerTrack/Handlers/MarketBoardUIHandler.cs @@ -1,80 +1,78 @@ using System; using Dalamud.Game.Addon.Lifecycle; using Dalamud.Game.Addon.Lifecycle.AddonArgTypes; -using Dalamud.Hooking; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.UI; using FFXIVClientStructs.FFXIV.Component.GUI; using Microsoft.Extensions.Logging; -namespace RetainerTrack.Handlers +namespace RetainerTrack.Handlers; + +internal sealed unsafe class MarketBoardUiHandler : IDisposable { - internal sealed unsafe class MarketBoardUiHandler : IDisposable + private const string AddonName = "ItemSearchResult"; + + private readonly ILogger _logger; + private readonly PersistenceContext _persistenceContext; + private readonly IAddonLifecycle _addonLifecycle; + + public MarketBoardUiHandler( + ILogger logger, + PersistenceContext persistenceContext, + IAddonLifecycle addonLifecycle) { - private const string AddonName = "ItemSearchResult"; + _logger = logger; + _persistenceContext = persistenceContext; + _addonLifecycle = addonLifecycle; - private readonly ILogger _logger; - private readonly PersistenceContext _persistenceContext; - private readonly IAddonLifecycle _addonLifecycle; + _addonLifecycle.RegisterListener(AddonEvent.PreDraw, AddonName, PreDraw); + } - public MarketBoardUiHandler( - ILogger logger, - PersistenceContext persistenceContext, - IAddonLifecycle addonLifecycle) + private void PreDraw(AddonEvent type, AddonArgs args) + { + UpdateRetainerNames((AddonItemSearchResult*)args.Addon); + } + + private void UpdateRetainerNames(AddonItemSearchResult* addon) + { + try { - _logger = logger; - _persistenceContext = persistenceContext; - _addonLifecycle = addonLifecycle; + if (addon == null || !addon->AtkUnitBase.IsVisible) + return; - _addonLifecycle.RegisterListener(AddonEvent.PreDraw, AddonName, PreDraw); - } + var results = addon->Results; + if (results == null) + return; - private void PreDraw(AddonEvent type, AddonArgs args) - { - UpdateRetainerNames((AddonItemSearchResult*)args.Addon); - } + int length = results->ListLength; + if (length == 0) + return; - private void UpdateRetainerNames(AddonItemSearchResult* addon) - { - try + for (int i = 0; i < length; ++i) { - if (addon == null || !addon->AtkUnitBase.IsVisible) - return; + var listItem = results->ItemRendererList[i].AtkComponentListItemRenderer; + var uldManager = listItem->AtkComponentButton.AtkComponentBase.UldManager; + if (uldManager.NodeListCount < 14) + continue; - var results = addon->Results; - if (results == null) - return; - - int length = results->ListLength; - if (length == 0) - return; - - for (int i = 0; i < length; ++i) + var retainerNameNode = (AtkTextNode*)uldManager.NodeList[5]; + string retainerName = retainerNameNode->NodeText.ToString(); + if (!retainerName.Contains('(', StringComparison.Ordinal)) { - var listItem = results->ItemRendererList[i].AtkComponentListItemRenderer; - var uldManager = listItem->AtkComponentButton.AtkComponentBase.UldManager; - if (uldManager.NodeListCount < 14) - continue; - - var retainerNameNode = (AtkTextNode*)uldManager.NodeList[5]; - string retainerName = retainerNameNode->NodeText.ToString(); - if (!retainerName.Contains('(')) - { - string playerName = _persistenceContext.GetCharacterNameOnCurrentWorld(retainerName); - if (!string.IsNullOrEmpty(playerName)) - retainerNameNode->SetText($"{playerName} ({retainerName})"); - } + string playerName = _persistenceContext.GetCharacterNameOnCurrentWorld(retainerName); + if (!string.IsNullOrEmpty(playerName)) + retainerNameNode->SetText($"{playerName} ({retainerName})"); } } - catch (Exception e) - { - _logger.LogInformation(e, "Market board draw failed"); - } } - - public void Dispose() + catch (Exception e) { - _addonLifecycle.UnregisterListener(AddonEvent.PreDraw, AddonName, PreDraw); + _logger.LogInformation(e, "Market board draw failed"); } } + + public void Dispose() + { + _addonLifecycle.UnregisterListener(AddonEvent.PreDraw, AddonName, PreDraw); + } } diff --git a/RetainerTrack/Handlers/PartyHandler.cs b/RetainerTrack/Handlers/PartyHandler.cs index 7ab02e7..d079956 100644 --- a/RetainerTrack/Handlers/PartyHandler.cs +++ b/RetainerTrack/Handlers/PartyHandler.cs @@ -5,65 +5,64 @@ using Dalamud.Memory; using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game.Group; -namespace RetainerTrack.Handlers +namespace RetainerTrack.Handlers; + +internal sealed class PartyHandler : IDisposable { - internal sealed class PartyHandler : IDisposable + private readonly IFramework _framework; + private readonly IClientState _clientState; + private readonly PersistenceContext _persistenceContext; + + private long _lastUpdate; + + public PartyHandler(IFramework framework, IClientState clientState, PersistenceContext persistenceContext) { - private readonly IFramework _framework; - private readonly IClientState _clientState; - private readonly PersistenceContext _persistenceContext; + _framework = framework; + _clientState = clientState; + _persistenceContext = persistenceContext; - private long _lastUpdate = 0; + _framework.Update += FrameworkUpdate; + } - public PartyHandler(IFramework framework, IClientState clientState, PersistenceContext persistenceContext) + private unsafe void FrameworkUpdate(IFramework _) + { + long now = Environment.TickCount64; + if (!_clientState.IsLoggedIn || _clientState.IsPvPExcludingDen || now - _lastUpdate < 180_000) + return; + + _lastUpdate = now; + + // skip if we're not in an alliance, party members are handled via social list updates + var groupManager = GroupManager.Instance(); + if (groupManager->AllianceFlags == 0x0) + return; + + List mappings = new(); + foreach (var allianceMember in groupManager->AllianceMembersSpan) + HandlePartyMember(allianceMember, mappings); + + if (mappings.Count > 0) + Task.Run(() => _persistenceContext.HandleContentIdMapping(mappings)); + } + + private static unsafe void HandlePartyMember(PartyMember partyMember, List contentIdToNames) + { + if (partyMember.ContentID == 0) + return; + + string name = MemoryHelper.ReadStringNullTerminated((nint)partyMember.Name); + if (string.IsNullOrEmpty(name)) + return; + + contentIdToNames.Add(new ContentIdToName { - _framework = framework; - _clientState = clientState; - _persistenceContext = persistenceContext; + ContentId = (ulong)partyMember.ContentID, + PlayerName = name, + }); + } - _framework.Update += FrameworkUpdate; - } - - private unsafe void FrameworkUpdate(IFramework _) - { - long now = Environment.TickCount64; - if (!_clientState.IsLoggedIn || _clientState.IsPvPExcludingDen || now - _lastUpdate < 180_000) - return; - - _lastUpdate = now; - - // skip if we're not in an alliance, party members are handled via social list updates - var groupManager = GroupManager.Instance(); - if (groupManager->AllianceFlags == 0x0) - return; - - List mappings = new(); - foreach (var allianceMember in groupManager->AllianceMembersSpan) - HandlePartyMember(allianceMember, mappings); - - if (mappings.Count > 0) - Task.Run(() => _persistenceContext.HandleContentIdMapping(mappings)); - } - - private unsafe void HandlePartyMember(PartyMember partyMember, List contentIdToNames) - { - if (partyMember.ContentID == 0) - return; - - string name = MemoryHelper.ReadStringNullTerminated((nint)partyMember.Name); - if (string.IsNullOrEmpty(name)) - return; - - contentIdToNames.Add(new ContentIdToName - { - ContentId = (ulong)partyMember.ContentID, - PlayerName = name, - }); - } - - public void Dispose() - { - _framework.Update -= FrameworkUpdate; - } + public void Dispose() + { + _framework.Update -= FrameworkUpdate; } } diff --git a/RetainerTrack/Handlers/PersistenceContext.cs b/RetainerTrack/Handlers/PersistenceContext.cs index e5877b3..848ea61 100644 --- a/RetainerTrack/Handlers/PersistenceContext.cs +++ b/RetainerTrack/Handlers/PersistenceContext.cs @@ -1,128 +1,171 @@ using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Globalization; using System.Linq; using Dalamud.Game.Network.Structures; using Dalamud.Plugin.Services; -using LiteDB; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using RetainerTrack.Database; -namespace RetainerTrack.Handlers +namespace RetainerTrack.Handlers; + +internal sealed class PersistenceContext { - internal sealed class PersistenceContext + private readonly ILogger _logger; + private readonly IClientState _clientState; + private readonly IServiceProvider _serviceProvider; + private readonly ConcurrentDictionary> _worldRetainerCache = new(); + private readonly ConcurrentDictionary _playerNameCache = new(); + + public PersistenceContext(ILogger logger, IClientState clientState, + IServiceProvider serviceProvider) { - private readonly ILogger _logger; - private readonly IClientState _clientState; - private readonly LiteDatabase _liteDatabase; - private readonly ConcurrentDictionary> _worldRetainerCache = new(); - private readonly ConcurrentDictionary _playerNameCache = new(); + _logger = logger; + _clientState = clientState; + _serviceProvider = serviceProvider; - public PersistenceContext(ILogger logger, IClientState clientState, - LiteDatabase liteDatabase) + using (IServiceScope scope = serviceProvider.CreateScope()) { - _logger = logger; - _clientState = clientState; - _liteDatabase = liteDatabase; + using var dbContext = scope.ServiceProvider.GetRequiredService(); + var retainersByWorld = dbContext.Retainers.GroupBy(retainer => retainer.WorldId); - var retainersByWorld = _liteDatabase.GetCollection().FindAll() - .GroupBy(r => r.WorldId); foreach (var retainers in retainersByWorld) { var world = _worldRetainerCache.GetOrAdd(retainers.Key, _ => new()); foreach (var retainer in retainers) { if (retainer.Name != null) - world[retainer.Name] = retainer.OwnerContentId; + world[retainer.Name] = retainer.OwnerLocalContentId; } } - foreach (var player in _liteDatabase.GetCollection().FindAll()) - _playerNameCache[player.Id] = player.Name ?? string.Empty; + foreach (var player in dbContext.Players) + _playerNameCache[player.LocalContentId] = player.Name ?? string.Empty; } + } - public string GetCharacterNameOnCurrentWorld(string retainerName) + public string GetCharacterNameOnCurrentWorld(string retainerName) + { + uint currentWorld = _clientState.LocalPlayer?.CurrentWorld.Id ?? 0; + if (currentWorld == 0) + return string.Empty; + + var currentWorldCache = _worldRetainerCache.GetOrAdd(currentWorld, _ => new()); + if (!currentWorldCache.TryGetValue(retainerName, out ulong playerContentId)) + return string.Empty; + + return _playerNameCache.TryGetValue(playerContentId, out string? playerName) ? playerName : string.Empty; + } + + public IReadOnlyList GetRetainerNamesForCharacter(string characterName, uint world) + { + using var scope = _serviceProvider.CreateScope(); + using var dbContext = scope.ServiceProvider.GetRequiredService(); + return dbContext.Players.Where(p => characterName == p.Name) + .SelectMany(player => + dbContext.Retainers.Where(x => x.OwnerLocalContentId == player.LocalContentId && x.WorldId == world)) + .Select(x => x.Name) + .Where(x => !string.IsNullOrEmpty(x)) + .Cast() + .ToList() + .AsReadOnly(); + } + + public void HandleMarketBoardPage(MarketBoardCurrentOfferings listings, ushort worldId) + { + try { - uint currentWorld = _clientState.LocalPlayer?.CurrentWorld.Id ?? 0; - if (currentWorld == 0) - return string.Empty; - - var currentWorldCache = _worldRetainerCache.GetOrAdd(currentWorld, _ => new()); - if (!currentWorldCache.TryGetValue(retainerName, out ulong playerContentId)) - return string.Empty; - - return _playerNameCache.TryGetValue(playerContentId, out string? playerName) ? playerName : string.Empty; - } - - public void HandleMarketBoardPage(MarketBoardCurrentOfferings listings, ushort worldId) - { - try - { - var updates = - listings.ItemListings.DistinctBy(o => o.RetainerId) - .Where(l => l.RetainerId != 0) - .Where(l => l.RetainerOwnerId != 0) - .Select(l => - new Retainer - { - Id = l.RetainerId, - Name = l.RetainerName, - WorldId = worldId, - OwnerContentId = l.RetainerOwnerId, - }) - .ToList(); - _liteDatabase.GetCollection().Upsert(updates); - foreach (var retainer in updates) - { - if (!_playerNameCache.TryGetValue(retainer.OwnerContentId, out string? ownerName)) - ownerName = retainer.OwnerContentId.ToString(); - _logger.LogTrace("Retainer {RetainerName} belongs to {OwnerId}", retainer.Name, - ownerName); - - if (retainer.Name != null) - { - var world = _worldRetainerCache.GetOrAdd(retainer.WorldId, _ => new()); - world[retainer.Name] = retainer.OwnerContentId; - } - } - } - catch (Exception e) - { - _logger.LogError(e, "Could not persist retainer info from market board page"); - } - } - - public void HandleContentIdMapping(ContentIdToName mapping) - => HandleContentIdMapping(new List { mapping }); - - public void HandleContentIdMapping(IReadOnlyList mappings) - { - try - { - var updates = mappings - .Where(mapping => mapping.ContentId != 0 && !string.IsNullOrEmpty(mapping.PlayerName)) - .Where(mapping => - { - if (_playerNameCache.TryGetValue(mapping.ContentId, out string? existingName)) - return mapping.PlayerName != existingName; - - return true; - }) - .Select(mapping => - new Player + var updates = + listings.ItemListings.DistinctBy(o => o.RetainerId) + .Where(l => l.RetainerId != 0) + .Where(l => l.RetainerOwnerId != 0) + .Select(l => + new Retainer { - Id = mapping.ContentId, - Name = mapping.PlayerName, + LocalContentId = l.RetainerId, + Name = l.RetainerName, + WorldId = worldId, + OwnerLocalContentId = l.RetainerOwnerId, }) .ToList(); - _liteDatabase.GetCollection().Upsert(updates); - foreach (var player in updates) - _playerNameCache[player.Id] = player.Name ?? string.Empty; - } - catch (Exception e) + + using var scope = _serviceProvider.CreateScope(); + using var dbContext = scope.ServiceProvider.GetRequiredService(); + + var ids = updates.Select(x => x.LocalContentId).ToList(); + var dbRetainers = dbContext.Retainers.Where(x => ids.Contains(x.LocalContentId)) + .ToDictionary(x => x.LocalContentId, x => x); + foreach (var retainer in updates) { - _logger.LogError(e, "Could not persist multiple mappings"); + if (dbRetainers.TryGetValue(retainer.LocalContentId, out var dbRetainer)) + { + dbRetainer.Name = retainer.Name; + dbRetainer.WorldId = retainer.WorldId; + dbRetainer.OwnerLocalContentId = retainer.OwnerLocalContentId; + dbContext.Retainers.Update(dbRetainer); + } + else + dbContext.Retainers.Add(retainer); + + if (!_playerNameCache.TryGetValue(retainer.OwnerLocalContentId, out string? ownerName)) + ownerName = retainer.OwnerLocalContentId.ToString(CultureInfo.InvariantCulture); + _logger.LogTrace("Retainer {RetainerName} belongs to {OwnerId}", retainer.Name, + ownerName); + + if (retainer.Name != null) + { + var world = _worldRetainerCache.GetOrAdd(retainer.WorldId, _ => new()); + world[retainer.Name] = retainer.OwnerLocalContentId; + } } + + dbContext.SaveChanges(); + } + catch (Exception e) + { + _logger.LogError(e, "Could not persist retainer info from market board page"); + } + } + + public void HandleContentIdMapping(ContentIdToName mapping) + => HandleContentIdMapping(new List { mapping }); + + public void HandleContentIdMapping(IReadOnlyList mappings) + { + try + { + var updates = mappings + .Where(mapping => mapping.ContentId != 0 && !string.IsNullOrEmpty(mapping.PlayerName)) + .Where(mapping => + { + if (_playerNameCache.TryGetValue(mapping.ContentId, out string? existingName)) + return mapping.PlayerName != existingName; + + return true; + }) + .Select(mapping => + new Player + { + LocalContentId = mapping.ContentId, + Name = mapping.PlayerName, + }) + .ToList(); + + + using (var scope = _serviceProvider.CreateScope()) + { + using var dbContext = scope.ServiceProvider.GetRequiredService(); + dbContext.Players.AddRange(updates); + } + + foreach (var player in updates) + _playerNameCache[player.LocalContentId] = player.Name ?? string.Empty; + } + catch (Exception e) + { + _logger.LogError(e, "Could not persist multiple mappings"); } } } diff --git a/RetainerTrack/LegacyDb/LegacyPlayer.cs b/RetainerTrack/LegacyDb/LegacyPlayer.cs new file mode 100644 index 0000000..2a9679a --- /dev/null +++ b/RetainerTrack/LegacyDb/LegacyPlayer.cs @@ -0,0 +1,12 @@ +using System; + +namespace RetainerTrack.LegacyDb; + +[Obsolete] +internal sealed class LegacyPlayer +{ + public static string CollectionName => "Player"; + + public ulong Id { get; set; } + public string? Name { get; set; } +} diff --git a/RetainerTrack/LegacyDb/LegacyRetainer.cs b/RetainerTrack/LegacyDb/LegacyRetainer.cs new file mode 100644 index 0000000..9c1afb6 --- /dev/null +++ b/RetainerTrack/LegacyDb/LegacyRetainer.cs @@ -0,0 +1,14 @@ +using System; + +namespace RetainerTrack.LegacyDb; + +[Obsolete] +internal sealed class LegacyRetainer +{ + public static string CollectionName => "Retainer"; + + public ulong Id { get; set; } + public string? Name { get; set; } + public ushort WorldId { get; set; } + public ulong OwnerContentId { get; set; } +} diff --git a/RetainerTrack/RetainerTrack.csproj b/RetainerTrack/RetainerTrack.csproj index 3ad06d5..bff2685 100644 --- a/RetainerTrack/RetainerTrack.csproj +++ b/RetainerTrack/RetainerTrack.csproj @@ -1,9 +1,9 @@ - net7.0-windows - 2.0 - 11.0 + net8.0-windows + 3.0 + 12.0 enable win-x64 true @@ -25,44 +25,49 @@ - + - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + $(DalamudLibPath)Dalamud.dll - false + false $(DalamudLibPath)ImGui.NET.dll - false + false $(DalamudLibPath)ImGuiScene.dll - false + false $(DalamudLibPath)Lumina.dll - false + false $(DalamudLibPath)Lumina.Excel.dll - false + false $(DalamudLibPath)Newtonsoft.Json.dll - false + false $(DalamudLibPath)FFXIVClientStructs.dll - false + false - + diff --git a/RetainerTrack/RetainerTrackPlugin.cs b/RetainerTrack/RetainerTrackPlugin.cs index 62fe8ee..0ea4687 100644 --- a/RetainerTrack/RetainerTrackPlugin.cs +++ b/RetainerTrack/RetainerTrackPlugin.cs @@ -1,72 +1,94 @@ -using System.IO; +using System; +using System.IO; using Dalamud.Extensions.MicrosoftLogging; using Dalamud.Plugin; using Dalamud.Plugin.Services; -using LiteDB; +using Microsoft.Data.Sqlite; +using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using RetainerTrack.Commands; using RetainerTrack.Database; +using RetainerTrack.Database.Compiled; +using RetainerTrack.Database.Migrations; using RetainerTrack.Handlers; -namespace RetainerTrack +namespace RetainerTrack; + +// ReSharper disable once UnusedType.Global +internal sealed class RetainerTrackPlugin : IDalamudPlugin { - // ReSharper disable once UnusedType.Global - internal sealed class RetainerTrackPlugin : IDalamudPlugin + public const string DatabaseFileName = "retainertrack.data.sqlite3"; + private readonly string _sqliteConnectionString; + private readonly ServiceProvider? _serviceProvider; + + public RetainerTrackPlugin( + DalamudPluginInterface pluginInterface, + IFramework framework, + IClientState clientState, + IGameGui gameGui, + IChatGui chatGui, + IGameInteropProvider gameInteropProvider, + IAddonLifecycle addonLifecycle, + ICommandManager commandManager, + IDataManager dataManager, + IPluginLog pluginLog) { - private readonly ServiceProvider? _serviceProvider; + ServiceCollection serviceCollection = new(); + serviceCollection.AddLogging(builder => builder.SetMinimumLevel(LogLevel.Trace) + .ClearProviders() + .AddDalamudLogger(pluginLog)); + serviceCollection.AddSingleton(this); + serviceCollection.AddSingleton(pluginInterface); + serviceCollection.AddSingleton(framework); + serviceCollection.AddSingleton(clientState); + serviceCollection.AddSingleton(gameGui); + serviceCollection.AddSingleton(chatGui); + serviceCollection.AddSingleton(gameInteropProvider); + serviceCollection.AddSingleton(addonLifecycle); + serviceCollection.AddSingleton(commandManager); + serviceCollection.AddSingleton(dataManager); - public RetainerTrackPlugin( - DalamudPluginInterface pluginInterface, - IFramework framework, - IClientState clientState, - IGameGui gameGui, - IGameInteropProvider gameInteropProvider, - IAddonLifecycle addonLifecycle, - IPluginLog pluginLog) - { - ServiceCollection serviceCollection = new(); - serviceCollection.AddLogging(builder => builder.SetMinimumLevel(LogLevel.Trace) - .ClearProviders() - .AddDalamudLogger(pluginLog)); - serviceCollection.AddSingleton(this); - serviceCollection.AddSingleton(pluginInterface); - serviceCollection.AddSingleton(framework); - serviceCollection.AddSingleton(clientState); - serviceCollection.AddSingleton(gameGui); - serviceCollection.AddSingleton(gameInteropProvider); - serviceCollection.AddSingleton(addonLifecycle); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); + serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(_ => - new LiteDatabase(new ConnectionString - { - Filename = Path.Join(pluginInterface.GetPluginConfigDirectory(), "retainer-data.litedb"), - Connection = ConnectionType.Direct, - Upgrade = true, - })); + _sqliteConnectionString = + $"Data Source={Path.Join(pluginInterface.GetPluginConfigDirectory(), DatabaseFileName)}"; + serviceCollection.AddDbContext(o => o + .UseSqlite(_sqliteConnectionString) + .UseModel(RetainerTrackContextModel.Instance)); - serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); - serviceCollection.AddSingleton(); + _serviceProvider = serviceCollection.BuildServiceProvider(); - _serviceProvider = serviceCollection.BuildServiceProvider(); - LiteDatabase liteDatabase = _serviceProvider.GetRequiredService(); - liteDatabase.GetCollection() - .EnsureIndex(x => x.Id); - liteDatabase.GetCollection() - .EnsureIndex(x => x.Id); + RunMigrations(_serviceProvider); - _serviceProvider.GetRequiredService(); - _serviceProvider.GetRequiredService(); - _serviceProvider.GetRequiredService(); - _serviceProvider.GetRequiredService(); - } + _serviceProvider.GetRequiredService(); + _serviceProvider.GetRequiredService(); + _serviceProvider.GetRequiredService(); + _serviceProvider.GetRequiredService(); + _serviceProvider.GetRequiredService(); + } - public void Dispose() - { - _serviceProvider?.Dispose(); - } + private static void RunMigrations(IServiceProvider serviceProvider) + { + ImportLegacyData.PluginInterface = serviceProvider.GetRequiredService(); + + using var scope = serviceProvider.CreateScope(); + using var dbContext = scope.ServiceProvider.GetRequiredService(); + dbContext.Database.Migrate(); + } + + public void Dispose() + { + _serviceProvider?.Dispose(); + + // ensure we're not keeping the file open longer than the plugin is loaded + using (SqliteConnection sqliteConnection = new(_sqliteConnectionString)) + SqliteConnection.ClearPool(sqliteConnection); } } diff --git a/RetainerTrack/packages.lock.json b/RetainerTrack/packages.lock.json index 9bf043f..0dfcfdb 100644 --- a/RetainerTrack/packages.lock.json +++ b/RetainerTrack/packages.lock.json @@ -1,14 +1,14 @@ { "version": 1, "dependencies": { - "net7.0-windows7.0": { + "net8.0-windows7.0": { "Dalamud.Extensions.MicrosoftLogging": { "type": "Direct", - "requested": "[2.0.0, )", - "resolved": "2.0.0", - "contentHash": "qp2idn5GuPouUxHHFytMrorbhlcupsgPdO87HjxlBfTY+JID+qoTfPmA5V6HBP1a4DuXGPbk4JtoO/hMmnQrtw==", + "requested": "[3.0.0, )", + "resolved": "3.0.0", + "contentHash": "jWK3r/cZUXN8H9vHf78gEzeRmMk4YAbCUYzLcTqUAcega8unUiFGwYy+iOjVYJ9urnr9r+hk+vBi1y9wyv+e7Q==", "dependencies": { - "Microsoft.Extensions.Logging": "7.0.0" + "Microsoft.Extensions.Logging": "8.0.0" } }, "DalamudPackager": { @@ -23,51 +23,395 @@ "resolved": "5.0.17", "contentHash": "cKPvkdlzIts3ZKu/BzoIc/Y71e4VFKlij4LyioPFATZMot+wB7EAm1FFbZSJez6coJmQUoIg/3yHE1MMU+zOdg==" }, + "Microsoft.EntityFrameworkCore.Sqlite": { + "type": "Direct", + "requested": "[8.0.5, )", + "resolved": "8.0.5", + "contentHash": "rBTx2TP+pa+CgXIxWmUbPdO+53WV4Nmq9Njb5Olomh4og/p5qV1jU53wPpqO92gEv+ZR6arwP5Pe11XImYTT+A==", + "dependencies": { + "Microsoft.EntityFrameworkCore.Sqlite.Core": "8.0.5", + "SQLitePCLRaw.bundle_e_sqlite3": "2.1.6" + } + }, + "Microsoft.EntityFrameworkCore.Tools": { + "type": "Direct", + "requested": "[8.0.5, )", + "resolved": "8.0.5", + "contentHash": "ZG5X2uznVmw+Mk0HVv3YHiTaGcCANDmZg81/9GLvE5zU4B11oxuM1+tndkYCFoM9CSN0/+XfB89TVYViKXYiRA==", + "dependencies": { + "Microsoft.EntityFrameworkCore.Design": "8.0.5" + } + }, "Microsoft.Extensions.DependencyInjection": { "type": "Direct", - "requested": "[7.0.0, )", - "resolved": "7.0.0", - "contentHash": "elNeOmkeX3eDVG6pYVeV82p29hr+UKDaBhrZyWvWLw/EVZSYEkZlQdkp0V39k/Xehs2Qa0mvoCvkVj3eQxNQ1Q==", + "requested": "[8.0.0, )", + "resolved": "8.0.0", + "contentHash": "V8S3bsm50ig6JSyrbcJJ8bW2b9QLGouz+G1miK3UTaOWmMtFwNNNzUf4AleyDWUmTrWMLNnFSLEQtxmxgNQnNQ==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "7.0.0" + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0" + } + }, + "Humanizer.Core": { + "type": "Transitive", + "resolved": "2.14.1", + "contentHash": "lQKvtaTDOXnoVJ20ibTuSIOf2i0uO0MPbDhd1jm238I+U/2ZnRENj0cktKZhtchBMtCUSRQ5v4xBCUbKNmyVMw==" + }, + "Microsoft.Bcl.AsyncInterfaces": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "UcSjPsst+DfAdJGVDsu346FX0ci0ah+lw3WRtn18NUwEqRt70HaOQ7lI72vy3+1LxtqI3T5GWwV39rQSrCzAeg==" + }, + "Microsoft.CodeAnalysis.Analyzers": { + "type": "Transitive", + "resolved": "3.3.3", + "contentHash": "j/rOZtLMVJjrfLRlAMckJLPW/1rze9MT1yfWqSIbUPGRu1m1P0fuo9PmqapwsmePfGB5PJrudQLvmUOAMF0DqQ==" + }, + "Microsoft.CodeAnalysis.Common": { + "type": "Transitive", + "resolved": "4.5.0", + "contentHash": "lwAbIZNdnY0SUNoDmZHkVUwLO8UyNnyyh1t/4XsbFxi4Ounb3xszIYZaWhyj5ZjyfcwqwmtMbE7fUTVCqQEIdQ==", + "dependencies": { + "Microsoft.CodeAnalysis.Analyzers": "3.3.3", + "System.Collections.Immutable": "6.0.0", + "System.Reflection.Metadata": "6.0.1", + "System.Runtime.CompilerServices.Unsafe": "6.0.0", + "System.Text.Encoding.CodePages": "6.0.0" + } + }, + "Microsoft.CodeAnalysis.CSharp": { + "type": "Transitive", + "resolved": "4.5.0", + "contentHash": "cM59oMKAOxvdv76bdmaKPy5hfj+oR+zxikWoueEB7CwTko7mt9sVKZI8Qxlov0C/LuKEG+WQwifepqL3vuTiBQ==", + "dependencies": { + "Microsoft.CodeAnalysis.Common": "[4.5.0]" + } + }, + "Microsoft.CodeAnalysis.CSharp.Workspaces": { + "type": "Transitive", + "resolved": "4.5.0", + "contentHash": "h74wTpmGOp4yS4hj+EvNzEiPgg/KVs2wmSfTZ81upJZOtPkJsVkgfsgtxxqmAeapjT/vLKfmYV0bS8n5MNVP+g==", + "dependencies": { + "Humanizer.Core": "2.14.1", + "Microsoft.CodeAnalysis.CSharp": "[4.5.0]", + "Microsoft.CodeAnalysis.Common": "[4.5.0]", + "Microsoft.CodeAnalysis.Workspaces.Common": "[4.5.0]" + } + }, + "Microsoft.CodeAnalysis.Workspaces.Common": { + "type": "Transitive", + "resolved": "4.5.0", + "contentHash": "l4dDRmGELXG72XZaonnOeORyD/T5RpEu5LGHOUIhnv+MmUWDY/m1kWXGwtcgQ5CJ5ynkFiRnIYzTKXYjUs7rbw==", + "dependencies": { + "Humanizer.Core": "2.14.1", + "Microsoft.Bcl.AsyncInterfaces": "6.0.0", + "Microsoft.CodeAnalysis.Common": "[4.5.0]", + "System.Composition": "6.0.0", + "System.IO.Pipelines": "6.0.3", + "System.Threading.Channels": "6.0.0" + } + }, + "Microsoft.Data.Sqlite.Core": { + "type": "Transitive", + "resolved": "8.0.5", + "contentHash": "JMGBNGTPsrLM14j5gDG2r5/I1nbbQd1ZdgeUnF7uca8RHYin6wZpFtQNYYqOMUpSxJak55trXE9B8/X2X+pOXw==", + "dependencies": { + "SQLitePCLRaw.core": "2.1.6" + } + }, + "Microsoft.EntityFrameworkCore": { + "type": "Transitive", + "resolved": "8.0.5", + "contentHash": "sqpDZgfzmTPXy/jCekqTaPDwqRDjtdGmIL+eqFfXtVAoH4AanWjeyxQ1ej3uVnTQO6f23+m9+ggJDVcgyPJxcA==", + "dependencies": { + "Microsoft.EntityFrameworkCore.Abstractions": "8.0.5", + "Microsoft.EntityFrameworkCore.Analyzers": "8.0.5", + "Microsoft.Extensions.Caching.Memory": "8.0.0", + "Microsoft.Extensions.Logging": "8.0.0" + } + }, + "Microsoft.EntityFrameworkCore.Abstractions": { + "type": "Transitive", + "resolved": "8.0.5", + "contentHash": "qwYdfjFKtmTXX8NIm0MuZxUkon1tcw+aF5huzR7YOVr/tR3s4fqw9DWcvc23l3Jhpo/uGHWZcNPyFlI2CD3Usg==" + }, + "Microsoft.EntityFrameworkCore.Analyzers": { + "type": "Transitive", + "resolved": "8.0.5", + "contentHash": "LzoKedC+9A8inF5d3iIzgyv/JDXgKrtpYoGIC3EqGWuHVDm9s/IHHApeTOTbzvnr7yBVV+nmYfyT1nwtzRDp0Q==" + }, + "Microsoft.EntityFrameworkCore.Design": { + "type": "Transitive", + "resolved": "8.0.5", + "contentHash": "HWYnbuMwllSCsZjfKj3Vz+HDGOCyGlTMYjI7tZH5pK7AuiGNHOdshCnWlEFEuDV6oAadWfXGTDmkmV53gwTqSQ==", + "dependencies": { + "Humanizer.Core": "2.14.1", + "Microsoft.CodeAnalysis.CSharp.Workspaces": "4.5.0", + "Microsoft.EntityFrameworkCore.Relational": "8.0.5", + "Microsoft.Extensions.DependencyModel": "8.0.0", + "Mono.TextTemplating": "2.2.1" + } + }, + "Microsoft.EntityFrameworkCore.Relational": { + "type": "Transitive", + "resolved": "8.0.5", + "contentHash": "x2bdSK3eKKEQkDdYcGxxDU+S7NqhBiz/Fciz01Mafz9P71VRdP3JskKHaZvwK0/sNEAT3hS7BTsDQGUA2F9mAA==", + "dependencies": { + "Microsoft.EntityFrameworkCore": "8.0.5", + "Microsoft.Extensions.Configuration.Abstractions": "8.0.0" + } + }, + "Microsoft.EntityFrameworkCore.Sqlite.Core": { + "type": "Transitive", + "resolved": "8.0.5", + "contentHash": "txwDTpgWFeuTLHh4gYxzKnSWx2jtpX3qxRYkMgfLmjZAe5vYxHKPsTNCa7AKR78ZqrUM7iZ5bBiS3s1Q7oZi4g==", + "dependencies": { + "Microsoft.Data.Sqlite.Core": "8.0.5", + "Microsoft.EntityFrameworkCore.Relational": "8.0.5", + "Microsoft.Extensions.DependencyModel": "8.0.0" + } + }, + "Microsoft.Extensions.Caching.Abstractions": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "3KuSxeHoNYdxVYfg2IRZCThcrlJ1XJqIXkAWikCsbm5C/bCjv7G0WoKDyuR98Q+T607QT2Zl5GsbGRkENcV2yQ==", + "dependencies": { + "Microsoft.Extensions.Primitives": "8.0.0" + } + }, + "Microsoft.Extensions.Caching.Memory": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "7pqivmrZDzo1ADPkRwjy+8jtRKWRCPag9qPI+p7sgu7Q4QreWhcvbiWXsbhP+yY8XSiDvZpu2/LWdBv7PnmOpQ==", + "dependencies": { + "Microsoft.Extensions.Caching.Abstractions": "8.0.0", + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", + "Microsoft.Extensions.Logging.Abstractions": "8.0.0", + "Microsoft.Extensions.Options": "8.0.0", + "Microsoft.Extensions.Primitives": "8.0.0" + } + }, + "Microsoft.Extensions.Configuration.Abstractions": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "3lE/iLSutpgX1CC0NOW70FJoGARRHbyKmG7dc0klnUZ9Dd9hS6N/POPWhKhMLCEuNN5nXEY5agmlFtH562vqhQ==", + "dependencies": { + "Microsoft.Extensions.Primitives": "8.0.0" } }, "Microsoft.Extensions.DependencyInjection.Abstractions": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "h3j/QfmFN4S0w4C2A6X7arXij/M/OVw3uQHSOFxnND4DyAzO1F9eMX7Eti7lU/OkSthEE0WzRsfT/Dmx86jzCw==" + "resolved": "8.0.0", + "contentHash": "cjWrLkJXK0rs4zofsK4bSdg+jhDLTaxrkXu4gS6Y7MAlCvRyNNgwY/lJi5RDlQOnSZweHqoyvgvbdvQsRIW+hg==" + }, + "Microsoft.Extensions.DependencyModel": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "NSmDw3K0ozNDgShSIpsZcbFIzBX4w28nDag+TfaQujkXGazBm+lid5onlWoCBy4VsLxqnnKjEBbGSJVWJMf43g==", + "dependencies": { + "System.Text.Encodings.Web": "8.0.0", + "System.Text.Json": "8.0.0" + } }, "Microsoft.Extensions.Logging": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "Nw2muoNrOG5U5qa2ZekXwudUn2BJcD41e65zwmDHb1fQegTX66UokLWZkJRpqSSHXDOWZ5V0iqhbxOEky91atA==", + "resolved": "8.0.0", + "contentHash": "tvRkov9tAJ3xP51LCv3FJ2zINmv1P8Hi8lhhtcKGqM+ImiTCC84uOPEI4z8Cdq2C3o9e+Aa0Gw0rmrsJD77W+w==", "dependencies": { - "Microsoft.Extensions.DependencyInjection": "7.0.0", - "Microsoft.Extensions.DependencyInjection.Abstractions": "7.0.0", - "Microsoft.Extensions.Logging.Abstractions": "7.0.0", - "Microsoft.Extensions.Options": "7.0.0" + "Microsoft.Extensions.DependencyInjection": "8.0.0", + "Microsoft.Extensions.Logging.Abstractions": "8.0.0", + "Microsoft.Extensions.Options": "8.0.0" } }, "Microsoft.Extensions.Logging.Abstractions": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "kmn78+LPVMOWeITUjIlfxUPDsI0R6G0RkeAMBmQxAJ7vBJn4q2dTva7pWi65ceN5vPGjJ9q/Uae2WKgvfktJAw==" + "resolved": "8.0.0", + "contentHash": "arDBqTgFCyS0EvRV7O3MZturChstm50OJ0y9bDJvAcmEPJm0FFpFyjU/JLYyStNGGey081DvnQYlncNX5SJJGA==", + "dependencies": { + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0" + } }, "Microsoft.Extensions.Options": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "lP1yBnTTU42cKpMozuafbvNtQ7QcBjr/CcK3bYOGEMH55Fjt+iecXjT6chR7vbgCMqy3PG3aNQSZgo/EuY/9qQ==", + "resolved": "8.0.0", + "contentHash": "JOVOfqpnqlVLUzINQ2fox8evY2SKLYJ3BV8QDe/Jyp21u1T7r45x/R/5QdteURMR5r01GxeJSBBUOCOyaNXA3g==", "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "7.0.0", - "Microsoft.Extensions.Primitives": "7.0.0" + "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", + "Microsoft.Extensions.Primitives": "8.0.0" } }, "Microsoft.Extensions.Primitives": { "type": "Transitive", - "resolved": "7.0.0", - "contentHash": "um1KU5kxcRp3CNuI8o/GrZtD4AIOXDk+RLsytjZ9QPok3ttLUelLKpilVPuaFT3TFjOhSibUAso0odbOaCDj3Q==" + "resolved": "8.0.0", + "contentHash": "bXJEZrW9ny8vjMF1JV253WeLhpEVzFo1lyaZu1vQ4ZxWUlVvknZ/+ftFgVheLubb4eZPSwwxBeqS1JkCOjxd8g==" + }, + "Mono.TextTemplating": { + "type": "Transitive", + "resolved": "2.2.1", + "contentHash": "KZYeKBET/2Z0gY1WlTAK7+RHTl7GSbtvTLDXEZZojUdAPqpQNDL6tHv7VUpqfX5VEOh+uRGKaZXkuD253nEOBQ==", + "dependencies": { + "System.CodeDom": "4.4.0" + } + }, + "SQLitePCLRaw.bundle_e_sqlite3": { + "type": "Transitive", + "resolved": "2.1.6", + "contentHash": "BmAf6XWt4TqtowmiWe4/5rRot6GerAeklmOPfviOvwLoF5WwgxcJHAxZtySuyW9r9w+HLILnm8VfJFLCUJYW8A==", + "dependencies": { + "SQLitePCLRaw.lib.e_sqlite3": "2.1.6", + "SQLitePCLRaw.provider.e_sqlite3": "2.1.6" + } + }, + "SQLitePCLRaw.core": { + "type": "Transitive", + "resolved": "2.1.6", + "contentHash": "wO6v9GeMx9CUngAet8hbO7xdm+M42p1XeJq47ogyRoYSvNSp0NGLI+MgC0bhrMk9C17MTVFlLiN6ylyExLCc5w==", + "dependencies": { + "System.Memory": "4.5.3" + } + }, + "SQLitePCLRaw.lib.e_sqlite3": { + "type": "Transitive", + "resolved": "2.1.6", + "contentHash": "2ObJJLkIUIxRpOUlZNGuD4rICpBnrBR5anjyfUFQep4hMOIeqW+XGQYzrNmHSVz5xSWZ3klSbh7sFR6UyDj68Q==" + }, + "SQLitePCLRaw.provider.e_sqlite3": { + "type": "Transitive", + "resolved": "2.1.6", + "contentHash": "PQ2Oq3yepLY4P7ll145P3xtx2bX8xF4PzaKPRpw9jZlKvfe4LE/saAV82inND9usn1XRpmxXk7Lal3MTI+6CNg==", + "dependencies": { + "SQLitePCLRaw.core": "2.1.6" + } + }, + "System.CodeDom": { + "type": "Transitive", + "resolved": "4.4.0", + "contentHash": "2sCCb7doXEwtYAbqzbF/8UAeDRMNmPaQbU2q50Psg1J9KzumyVVCgKQY8s53WIPTufNT0DpSe9QRvVjOzfDWBA==" + }, + "System.Collections.Immutable": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "l4zZJ1WU2hqpQQHXz1rvC3etVZN+2DLmQMO79FhOTZHMn8tDRr+WU287sbomD0BETlmKDn0ygUgVy9k5xkkJdA==", + "dependencies": { + "System.Runtime.CompilerServices.Unsafe": "6.0.0" + } + }, + "System.Composition": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "d7wMuKQtfsxUa7S13tITC8n1cQzewuhD5iDjZtK2prwFfKVzdYtgrTHgjaV03Zq7feGQ5gkP85tJJntXwInsJA==", + "dependencies": { + "System.Composition.AttributedModel": "6.0.0", + "System.Composition.Convention": "6.0.0", + "System.Composition.Hosting": "6.0.0", + "System.Composition.Runtime": "6.0.0", + "System.Composition.TypedParts": "6.0.0" + } + }, + "System.Composition.AttributedModel": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "WK1nSDLByK/4VoC7fkNiFuTVEiperuCN/Hyn+VN30R+W2ijO1d0Z2Qm0ScEl9xkSn1G2MyapJi8xpf4R8WRa/w==" + }, + "System.Composition.Convention": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "XYi4lPRdu5bM4JVJ3/UIHAiG6V6lWWUlkhB9ab4IOq0FrRsp0F4wTyV4Dj+Ds+efoXJ3qbLqlvaUozDO7OLeXA==", + "dependencies": { + "System.Composition.AttributedModel": "6.0.0" + } + }, + "System.Composition.Hosting": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "w/wXjj7kvxuHPLdzZ0PAUt++qJl03t7lENmb2Oev0n3zbxyNULbWBlnd5J5WUMMv15kg5o+/TCZFb6lSwfaUUQ==", + "dependencies": { + "System.Composition.Runtime": "6.0.0" + } + }, + "System.Composition.Runtime": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "qkRH/YBaMPTnzxrS5RDk1juvqed4A6HOD/CwRcDGyPpYps1J27waBddiiq1y93jk2ZZ9wuA/kynM+NO0kb3PKg==" + }, + "System.Composition.TypedParts": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "iUR1eHrL8Cwd82neQCJ00MpwNIBs4NZgXzrPqx8NJf/k4+mwBO0XCRmHYJT4OLSwDDqh5nBLJWkz5cROnrGhRA==", + "dependencies": { + "System.Composition.AttributedModel": "6.0.0", + "System.Composition.Hosting": "6.0.0", + "System.Composition.Runtime": "6.0.0" + } + }, + "System.IO.Pipelines": { + "type": "Transitive", + "resolved": "6.0.3", + "contentHash": "ryTgF+iFkpGZY1vRQhfCzX0xTdlV3pyaTTqRu2ETbEv+HlV7O6y7hyQURnghNIXvctl5DuZ//Dpks6HdL/Txgw==" + }, + "System.Memory": { + "type": "Transitive", + "resolved": "4.5.3", + "contentHash": "3oDzvc/zzetpTKWMShs1AADwZjQ/36HnsufHRPcOjyRAAMLDlu2iD33MBI2opxnezcVUtXyqDXXjoFMOU9c7SA==" + }, + "System.Reflection.Metadata": { + "type": "Transitive", + "resolved": "6.0.1", + "contentHash": "III/lNMSn0ZRBuM9m5Cgbiho5j81u0FAEagFX5ta2DKbljZ3T0IpD8j+BIiHQPeKqJppWS9bGEp6JnKnWKze0g==", + "dependencies": { + "System.Collections.Immutable": "6.0.0" + } + }, + "System.Runtime.CompilerServices.Unsafe": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==" + }, + "System.Text.Encoding.CodePages": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "ZFCILZuOvtKPauZ/j/swhvw68ZRi9ATCfvGbk1QfydmcXBkIWecWKn/250UH7rahZ5OoDBaiAudJtPvLwzw85A==", + "dependencies": { + "System.Runtime.CompilerServices.Unsafe": "6.0.0" + } + }, + "System.Text.Encodings.Web": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "yev/k9GHAEGx2Rg3/tU6MQh4HGBXJs70y7j1LaM1i/ER9po+6nnQ6RRqTJn1E7Xu0fbIFK80Nh5EoODxrbxwBQ==" + }, + "System.Text.Json": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "OdrZO2WjkiEG6ajEFRABTRCi/wuXQPxeV6g8xvUJqdxMvvuCCEk86zPla8UiIQJz3durtUEbNyY/3lIhS0yZvQ==", + "dependencies": { + "System.Text.Encodings.Web": "8.0.0" + } + }, + "System.Threading.Channels": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "TY8/9+tI0mNaUMgntOxxaq2ndTkdXqLSxvPmas7XEqOlv9lQtB7wLjYGd756lOaO7Dvb5r/WXhluM+0Xe87v5Q==" } }, - "net7.0-windows7.0/win-x64": {} + "net8.0-windows7.0/win-x64": { + "SQLitePCLRaw.lib.e_sqlite3": { + "type": "Transitive", + "resolved": "2.1.6", + "contentHash": "2ObJJLkIUIxRpOUlZNGuD4rICpBnrBR5anjyfUFQep4hMOIeqW+XGQYzrNmHSVz5xSWZ3klSbh7sFR6UyDj68Q==" + }, + "System.Text.Encoding.CodePages": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "ZFCILZuOvtKPauZ/j/swhvw68ZRi9ATCfvGbk1QfydmcXBkIWecWKn/250UH7rahZ5OoDBaiAudJtPvLwzw85A==", + "dependencies": { + "System.Runtime.CompilerServices.Unsafe": "6.0.0" + } + }, + "System.Text.Encodings.Web": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "yev/k9GHAEGx2Rg3/tU6MQh4HGBXJs70y7j1LaM1i/ER9po+6nnQ6RRqTJn1E7Xu0fbIFK80Nh5EoODxrbxwBQ==" + } + } } } \ No newline at end of file