You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
QuickPlay/QuickPlay/Configuration.cs

242 lines
8.6 KiB
C#

using System;
using System.Net.Http;
using System.IO;
using System.Threading.Tasks;
using System.Collections.Generic;
namespace QuickPlay
{
#pragma warning disable CS0659 // This is basically an almost-singleton, which will never be put in any hash table.
/// <summary>
/// Application (global) configuration data class
/// </summary>
[Serializable]
public sealed class AppConfiguration
{
// XXX: All the fields need to be checked in overriden Equals method
// Also: sensible defaults should be provided in defaultConfiguration field
public string playerConfigUrl;
// XXX: Any other fields of this model need [NonSerialized] attribute
[NonSerialized]
public static readonly string configFilePath =
Path.Combine(
System.Environment.GetFolderPath(System.Environment.SpecialFolder.ApplicationData),
"appConfig.xml"
);
public static AppConfiguration loadConfiguration()
{
var cfg = loadSavedConfiguration();
if (cfg == null)
{
cfg = defaultConfiguration;
}
return cfg;
}
public static AppConfiguration loadSavedConfiguration()
{
var serializer = new System.Xml.Serialization.XmlSerializer(typeof(AppConfiguration));
AppConfiguration result = null;
try
{
using (var stream = new System.IO.FileStream(configFilePath, System.IO.FileMode.Open))
{
result = (AppConfiguration)serializer.Deserialize(stream);
}
} catch (FileNotFoundException)
{
// This is fine, we will return null anyway.
} catch (InvalidCastException) {
// This is not so much fine, TODO log it.
// Still, we cannot supply config, so treat it as if it does not exist.
}
return result;
}
public void saveConfiguration()
{
// First we save the config
var serializer = new System.Xml.Serialization.XmlSerializer(typeof(AppConfiguration));
using (var stream = new System.IO.FileStream(configFilePath, System.IO.FileMode.Create))
{
serializer.Serialize(stream, this);
}
// Make sure that the configuration is same
var newConfig = AppConfiguration.loadSavedConfiguration();
if (this != newConfig) throw new InvalidDataException("Saved configuration is different from the supplied one.");
}
[NonSerialized]
public static readonly AppConfiguration defaultConfiguration = new AppConfiguration
{
playerConfigUrl = "http://www.ms.mff.cuni.cz/~turinskp/znelky.ini",
};
public async Task<PlayerConfiguration> GetPlayerConfig()
{
IPlayerConfigurationProvider cfgProvider;
if (playerConfigUrl.StartsWith("http:") || playerConfigUrl.StartsWith("https:"))
{
cfgProvider = new HttpConfigurationProvider(playerConfigUrl);
} else if (playerConfigUrl.StartsWith("file://"))
{
string filename = playerConfigUrl.Substring("file://".Length);
var reader = new StreamReader(filename);
cfgProvider = new FileConfigurationProvider(reader);
} else if (playerConfigUrl == "default") {
cfgProvider = new DummyConfigurationProvider();
} else
{
throw new InvalidOperationException("Schema of player config URL not supported");
}
PlayerConfiguration playercfg;
try
{
playercfg = await cfgProvider.GetConfigurationAsync();
}
// FIXME: These are provider-specific exceptions!
catch (Exception e) when (e is Java.Net.UnknownHostException || e is OperationCanceledException || e is HttpRequestException)
{
var ctx = Android.App.Application.Context;
var t = Android.Widget.Toast.MakeText(ctx, "Could not load config:" + e.Message, Android.Widget.ToastLength.Long);
t.Show();
cfgProvider = new DummyConfigurationProvider();
playercfg = await cfgProvider.GetConfigurationAsync();
}
return playercfg;
}
// We want to compare by values.
// It might actually be a bit more sensible to compare serialized
// versions of the objects, but that seems unneccessarily hard.
public override bool Equals(object obj)
{
var other = (AppConfiguration)obj;
// These fields have to match...
if (this.playerConfigUrl != other.playerConfigUrl) return false;
return true;
}
}
#pragma warning restore CS0659
public enum PlayerType
{
MPD,
}
/// <summary>
/// Configuration of the player.
///
/// Contains details about connection, configured songs &c.
/// </summary>
public class PlayerConfiguration
{
public Dictionary<string, IPlayable> songs = new Dictionary<string, IPlayable>();
public string playerName = "";
public PlayerType playerType;
public string playerConnectionDetails = ""; // This is opaque to this class, the player will make sense of it.
public static PlayerConfiguration FromFile(StreamReader reader)
{
var parser = new IniParser(reader);
var ini = parser.Parse();
var result = new PlayerConfiguration();
foreach (var elem in ini)
{
// Two options: either general options, or song descriptions
if (elem.Key == "general")
{
result.playerName = elem.Value["name"];
result.playerType = PlayerType.MPD; //FIXME: not always.
result.playerConnectionDetails = elem.Value["connection"];
} else
{
var song = new Song(elem.Key, elem.Value);
result.songs[elem.Key] = song;
}
}
return result;
}
public IPlayer GetPlayer()
{
IPlayer result;
switch (playerType)
{
case PlayerType.MPD:
result = new MpdPlayer(this);
break;
default:
throw new InvalidOperationException("Cannot happen: Player had no type.");
}
return result;
}
}
interface IPlayerConfigurationProvider
{
Task<PlayerConfiguration> GetConfigurationAsync();
}
sealed class HttpConfigurationProvider : IPlayerConfigurationProvider
{
readonly string configUrl;
public HttpConfigurationProvider(string url)
{
configUrl = url;
}
public async Task<PlayerConfiguration> GetConfigurationAsync()
{
var client = new HttpClient() ;
var resp = await client.GetStreamAsync(configUrl);
var sr = new StreamReader(resp);
return PlayerConfiguration.FromFile(sr);
}
}
sealed class FileConfigurationProvider : IPlayerConfigurationProvider
{
StreamReader reader;
public FileConfigurationProvider(StreamReader reader)
{
this.reader = reader;
}
public FileConfigurationProvider(string filename)
{
this.reader = new StreamReader(filename);
}
public Task<PlayerConfiguration> GetConfigurationAsync()
{
return Task.FromResult(PlayerConfiguration.FromFile(reader));
}
}
sealed class DummyConfigurationProvider : IPlayerConfigurationProvider
{
public Task<PlayerConfiguration> GetConfigurationAsync()
{
PlayerConfiguration cfg = new PlayerConfiguration();
cfg.playerType = PlayerType.MPD;
cfg.playerName = "Dummy player";
cfg.playerConnectionDetails = "127.0.0.1";
return Task.FromResult(cfg);
}
}
class Song: IPlayable
{
public string Identifier { get; private set; }
public PlayableMetadata Metadata { get; private set; }
public Song(string id, Dictionary<string, string> data) {
Identifier = id;
Metadata = new PlayableMetadata
{
usualPlayingTime = TimeSpan.Parse(data["time"]),
filePath = data["path"],
};
}
}
}