2019-01-24 08:43:03 +00:00
|
|
|
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
|
|
|
// See the LICENCE file in the repository root for full licence text.
|
2018-04-13 09:19:50 +00:00
|
|
|
|
|
|
|
|
|
using System;
|
2019-06-10 07:45:45 +00:00
|
|
|
|
using System.Collections.Generic;
|
2018-04-13 09:19:50 +00:00
|
|
|
|
using System.IO;
|
|
|
|
|
using System.Linq;
|
2019-06-10 07:45:45 +00:00
|
|
|
|
using System.Linq.Expressions;
|
|
|
|
|
using Microsoft.EntityFrameworkCore;
|
2018-04-13 09:19:50 +00:00
|
|
|
|
using osu.Framework.Extensions;
|
|
|
|
|
using osu.Framework.IO.Stores;
|
|
|
|
|
using osu.Framework.Logging;
|
|
|
|
|
using osu.Framework.Platform;
|
|
|
|
|
using osu.Game.Database;
|
|
|
|
|
|
|
|
|
|
namespace osu.Game.IO
|
|
|
|
|
{
|
|
|
|
|
/// <summary>
|
|
|
|
|
/// Handles the Store and retrieval of Files/FileSets to the database backing
|
|
|
|
|
/// </summary>
|
|
|
|
|
public class FileStore : DatabaseBackedStore
|
|
|
|
|
{
|
|
|
|
|
public readonly IResourceStore<byte[]> Store;
|
|
|
|
|
|
|
|
|
|
public new Storage Storage => base.Storage;
|
|
|
|
|
|
2019-02-28 04:31:40 +00:00
|
|
|
|
public FileStore(IDatabaseContextFactory contextFactory, Storage storage)
|
|
|
|
|
: base(contextFactory, storage.GetStorageForDirectory(@"files"))
|
2018-04-13 09:19:50 +00:00
|
|
|
|
{
|
|
|
|
|
Store = new StorageBackedResourceStore(Storage);
|
|
|
|
|
}
|
|
|
|
|
|
2019-06-10 07:45:45 +00:00
|
|
|
|
/// <summary>
|
|
|
|
|
/// Perform a lookup query on available <see cref="FileInfo"/>s.
|
|
|
|
|
/// </summary>
|
|
|
|
|
/// <param name="query">The query.</param>
|
|
|
|
|
/// <returns>Results from the provided query.</returns>
|
|
|
|
|
public IEnumerable<FileInfo> QueryFiles(Expression<Func<FileInfo, bool>> query) => ContextFactory.Get().Set<FileInfo>().AsNoTracking().Where(f => f.ReferenceCount > 0).Where(query);
|
|
|
|
|
|
2018-04-13 09:19:50 +00:00
|
|
|
|
public FileInfo Add(Stream data, bool reference = true)
|
|
|
|
|
{
|
|
|
|
|
using (var usage = ContextFactory.GetForWrite())
|
|
|
|
|
{
|
|
|
|
|
string hash = data.ComputeSHA2Hash();
|
|
|
|
|
|
|
|
|
|
var existing = usage.Context.FileInfo.FirstOrDefault(f => f.Hash == hash);
|
|
|
|
|
|
|
|
|
|
var info = existing ?? new FileInfo { Hash = hash };
|
|
|
|
|
|
|
|
|
|
string path = info.StoragePath;
|
|
|
|
|
|
|
|
|
|
// we may be re-adding a file to fix missing store entries.
|
2019-09-20 06:00:27 +00:00
|
|
|
|
bool requiresCopy = !Storage.Exists(path);
|
|
|
|
|
|
|
|
|
|
if (!requiresCopy)
|
|
|
|
|
{
|
|
|
|
|
// even if the file already exists, check the existing checksum for safety.
|
|
|
|
|
using (var stream = Storage.GetStream(path))
|
|
|
|
|
requiresCopy |= stream.ComputeSHA2Hash() != hash;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (requiresCopy)
|
2018-04-13 09:19:50 +00:00
|
|
|
|
{
|
|
|
|
|
data.Seek(0, SeekOrigin.Begin);
|
|
|
|
|
|
|
|
|
|
using (var output = Storage.GetStream(path, FileAccess.Write))
|
|
|
|
|
data.CopyTo(output);
|
|
|
|
|
|
|
|
|
|
data.Seek(0, SeekOrigin.Begin);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (reference || existing == null)
|
|
|
|
|
Reference(info);
|
|
|
|
|
|
|
|
|
|
return info;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Reference(params FileInfo[] files)
|
|
|
|
|
{
|
|
|
|
|
if (files.Length == 0) return;
|
|
|
|
|
|
|
|
|
|
using (var usage = ContextFactory.GetForWrite())
|
|
|
|
|
{
|
|
|
|
|
var context = usage.Context;
|
|
|
|
|
|
|
|
|
|
foreach (var f in files.GroupBy(f => f.ID))
|
|
|
|
|
{
|
|
|
|
|
var refetch = context.Find<FileInfo>(f.First().ID) ?? f.First();
|
|
|
|
|
refetch.ReferenceCount += f.Count();
|
|
|
|
|
context.FileInfo.Update(refetch);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void Dereference(params FileInfo[] files)
|
|
|
|
|
{
|
|
|
|
|
if (files.Length == 0) return;
|
|
|
|
|
|
|
|
|
|
using (var usage = ContextFactory.GetForWrite())
|
|
|
|
|
{
|
|
|
|
|
var context = usage.Context;
|
|
|
|
|
|
|
|
|
|
foreach (var f in files.GroupBy(f => f.ID))
|
|
|
|
|
{
|
|
|
|
|
var refetch = context.FileInfo.Find(f.Key);
|
|
|
|
|
refetch.ReferenceCount -= f.Count();
|
|
|
|
|
context.FileInfo.Update(refetch);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public override void Cleanup()
|
|
|
|
|
{
|
|
|
|
|
using (var usage = ContextFactory.GetForWrite())
|
|
|
|
|
{
|
|
|
|
|
var context = usage.Context;
|
|
|
|
|
|
|
|
|
|
foreach (var f in context.FileInfo.Where(f => f.ReferenceCount < 1))
|
|
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
Storage.Delete(f.StoragePath);
|
|
|
|
|
context.FileInfo.Remove(f);
|
|
|
|
|
}
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
{
|
|
|
|
|
Logger.Error(e, $@"Could not delete beatmap {f}");
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|