Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
ppy
GitHub Repository: ppy/osu
Path: blob/master/osu.Game/Skinning/RealmBackedResourceStore.cs
2264 views
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using osu.Framework.Extensions;
using osu.Framework.IO.Stores;
using osu.Game.Database;
using osu.Game.Extensions;
using Realms;

namespace osu.Game.Skinning
{
    public class RealmBackedResourceStore<T> : ResourceStore<byte[]>
        where T : RealmObject, IHasRealmFiles, IHasGuidPrimaryKey
    {
        private Lazy<Dictionary<string, string>> fileToStoragePathMapping;

        private readonly Live<T> liveSource;
        private readonly IDisposable? realmSubscription;

        public RealmBackedResourceStore(Live<T> source, IResourceStore<byte[]> underlyingStore, RealmAccess? realm)
            : base(underlyingStore)
        {
            liveSource = source;

            invalidateCache();
            Debug.Assert(fileToStoragePathMapping != null);

            // Required local for iOS. Will cause runtime crash if inlined.
            Guid id = source.ID;

            realmSubscription = realm?.RegisterForNotifications(r => r.All<T>().Where(s => s.ID == id), skinChanged);
        }

        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);
            realmSubscription?.Dispose();
        }

        private void skinChanged(IRealmCollection<T> sender, ChangeSet? changes) => invalidateCache();

        protected override IEnumerable<string> GetFilenames(string name)
        {
            foreach (string filename in base.GetFilenames(name))
            {
                string? path = getPathForFile(filename.ToStandardisedPath());
                if (path != null)
                    yield return path;
            }
        }

        private string? getPathForFile(string filename)
        {
            if (fileToStoragePathMapping.Value.TryGetValue(filename.ToLowerInvariant(), out string? path))
                return path;

            return null;
        }

        private void invalidateCache() => fileToStoragePathMapping = new Lazy<Dictionary<string, string>>(initialiseFileCache);

        private Dictionary<string, string> initialiseFileCache() => liveSource.PerformRead(source =>
        {
            var dictionary = new Dictionary<string, string>();
            dictionary.Clear();
            foreach (var f in source.Files)
                dictionary[f.Filename.ToLowerInvariant()] = f.File.GetStoragePath();

            return dictionary;
        });

        public override IEnumerable<string> GetAvailableResources() => fileToStoragePathMapping.Value.Keys;
    }
}