Skip to content

Commit

Permalink
feat(MigrationManager): migrating to repo versions
Browse files Browse the repository at this point in the history
  • Loading branch information
richardschneider committed Aug 5, 2019
1 parent f133804 commit 3e43d26
Show file tree
Hide file tree
Showing 4 changed files with 314 additions and 0 deletions.
50 changes: 50 additions & 0 deletions src/Migration/IMigration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Ipfs.Engine.Migration
{
/// <summary>
/// Provides a migration path to the repository.
/// </summary>
public interface IMigration
{
/// <summary>
/// The repository version that is created.
/// </summary>
int Version { get; }

/// <summary>
/// Indicates that an upgrade can be performed.
/// </summary>
bool CanUpgrade { get; }

/// <summary>
/// Indicates that an downgrade can be performed.
/// </summary>
bool CanDowngrade { get; }

/// <summary>
/// Upgrade the repository.
/// </summary>
/// <param name="ipfs">
/// The IPFS system to upgrade.
/// </param>
/// <param name="cancel"></param>
/// <returns></returns>
Task UpgradeAsync(IpfsEngine ipfs, CancellationToken cancel = default(CancellationToken));

/// <summary>
/// Downgrade the repository.
/// </summary>
/// <param name="ipfs">
/// The IPFS system to downgrade.
/// </param>
/// <param name="cancel"></param>
/// <returns></returns>
Task DowngradeAsync(IpfsEngine ipfs, CancellationToken cancel = default(CancellationToken));
}
}
92 changes: 92 additions & 0 deletions src/Migration/MigrateTo1.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Ipfs.Engine.Migration
{
class MigrateTo1 : IMigration
{
class Pin1
{
public Cid Id;
}

public int Version => 1;

public bool CanUpgrade => true;

public bool CanDowngrade => true;

public async Task DowngradeAsync(IpfsEngine ipfs, CancellationToken cancel = default(CancellationToken))
{
var path = Path.Combine(ipfs.Options.Repository.Folder, "pins");
var folder = new DirectoryInfo(path);
if (!folder.Exists)
{
return;
}

var store = new FileStore<Cid, Pin1>
{
Folder = path,
NameToKey = (cid) => cid.Hash.ToBase32(),
KeyToName = (key) => new MultiHash(key.FromBase32())
};

var files = folder.EnumerateFiles()
.Where(fi => fi.Length != 0);
foreach (var fi in files)
{
try
{
var name = store.KeyToName(fi.Name);
var pin = await store.GetAsync(name, cancel).ConfigureAwait(false);
File.Create(Path.Combine(store.Folder, pin.Id));
File.Delete(store.GetPath(name));
}
catch
{

}
}
}

public async Task UpgradeAsync(IpfsEngine ipfs, CancellationToken cancel = default(CancellationToken))
{
var path = Path.Combine(ipfs.Options.Repository.Folder, "pins");
var folder = new DirectoryInfo(path);
if (!folder.Exists)
{
return;
}

var store = new FileStore<Cid, Pin1>
{
Folder = path,
NameToKey = (cid) => cid.Hash.ToBase32(),
KeyToName = (key) => new MultiHash(key.FromBase32())
};

var files = folder.EnumerateFiles()
.Where(fi => fi.Length == 0);
foreach (var fi in files)
{
try
{
var cid = Cid.Decode(fi.Name);
await store.PutAsync(cid, new Pin1 { Id = cid }, cancel).ConfigureAwait(false);
File.Delete(fi.FullName);
}
catch
{

}
}
}

}
}
129 changes: 129 additions & 0 deletions src/Migration/MigrationManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
using Common.Logging;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Ipfs.Engine.Migration
{
/// <summary>
/// Allows migration of the repository.
/// </summary>
public class MigrationManager
{
static ILog log = LogManager.GetLogger(typeof(MigrationManager));

readonly IpfsEngine ipfs;

/// <summary>
/// Creates a new instance of the <see cref="MigrationManager"/> class
/// for the specifed <see cref="IpfsEngine"/>.
/// </summary>
public MigrationManager(IpfsEngine ipfs)
{
this.ipfs = ipfs;

Migrations = typeof(MigrationManager).Assembly
.GetTypes()
.Where(x => typeof(IMigration).IsAssignableFrom(x) && !x.IsInterface && !x.IsAbstract)
.Select(x => (IMigration)Activator.CreateInstance(x))
.OrderBy(x => x.Version)
.ToList();
}

/// <summary>
/// The list of migrations that can be performed.
/// </summary>
public List<IMigration> Migrations { get; private set; }

/// <summary>
/// Gets the latest supported version number of a repository.
/// </summary>
public int LatestVersion => Migrations.Last().Version;

/// <summary>
/// Gets the current vesion number of the repository.
/// </summary>
public int CurrentVersion
{
get
{
var path = VersionPath();
if (File.Exists(path))
{
using (var reader = new StreamReader(path))
{
var s = reader.ReadLine();
return int.Parse(s, CultureInfo.InvariantCulture);
}
}

return 0;
}
private set
{
File.WriteAllText(VersionPath(), value.ToString(CultureInfo.InvariantCulture));
}
}

/// <summary>
/// Upgrade/downgrade to the specified version.
/// </summary>
/// <param name="version">
/// The required version of the repository.
/// </param>
/// <param name="cancel">
/// </param>
/// <returns></returns>
public async Task MirgrateToVersionAsync(int version, CancellationToken cancel = default(CancellationToken))
{
if (version != 0 && !Migrations.Any(m => m.Version == version))
{
throw new ArgumentOutOfRangeException("version", $"Repository version '{version}' is unknown.");
}

var currentVersion = CurrentVersion;
var increment = CurrentVersion < version ? 1 : -1;
while (currentVersion != version)
{
var nextVersion = currentVersion + increment;
log.InfoFormat("Migrating to version {0}", nextVersion);

if (increment > 0)
{
var migration = Migrations.FirstOrDefault(m => m.Version == nextVersion);
if (migration.CanUpgrade)
{
await migration.UpgradeAsync(ipfs, cancel);
}
}
else if (increment < 0)
{
var migration = Migrations.FirstOrDefault(m => m.Version == currentVersion);
if (migration.CanDowngrade)
{
await migration.DowngradeAsync(ipfs, cancel);
}
}

CurrentVersion = nextVersion;
currentVersion = nextVersion;
}
}

/// <summary>
/// Gets the FQN of the version file.
/// </summary>
/// <returns>
/// The path to the version file.
/// </returns>
string VersionPath()
{
return Path.Combine(ipfs.Options.Repository.ExistingFolder(), "version");
}
}
}
43 changes: 43 additions & 0 deletions test/Migration/MigrationManagerTest.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Threading.Tasks;

namespace Ipfs.Engine.Migration
{
[TestClass]
public class MigrationManagerTest
{
[TestMethod]
public void HasMigrations()
{
var migrator = new MigrationManager(TestFixture.Ipfs);
var migrations = migrator.Migrations;
Assert.AreNotEqual(0, migrations.Count);
}

[TestMethod]
public void MirgrateToUnknownVersion()
{
var migrator = new MigrationManager(TestFixture.Ipfs);
ExceptionAssert.Throws<ArgumentOutOfRangeException>(() =>
{
migrator.MirgrateToVersionAsync(int.MaxValue).Wait();
});
}

[TestMethod]
public async Task MigrateToLowestThenHighest()
{
using (var ipfs = new TempNode())
{
var migrator = new MigrationManager(ipfs);
await migrator.MirgrateToVersionAsync(0);
Assert.AreEqual(0, migrator.CurrentVersion);

await migrator.MirgrateToVersionAsync(migrator.LatestVersion);
Assert.AreEqual(migrator.LatestVersion, migrator.CurrentVersion);
}
}
}
}

0 comments on commit 3e43d26

Please sign in to comment.