// OpenTween - Client of Twitter
// Copyright (c) 2013 kim_upsilon (@kim_upsilon)
// All rights reserved.
//
// This file is part of OpenTween.
//
// This program is free software; you can redistribute it and/or modify it
// under the terms of the GNU General Public License as published by the Free
// Software Foundation; either version 3 of the License, or (at your option)
// any later version.
//
// This program is distributed in the hope that it will be useful, but
// WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
// for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program. If not, see , or write to
// the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
// Boston, MA 02110-1301, USA.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections;
using System.Linq;
using System.Runtime.Serialization.Json;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.Xml.XPath;
using OpenTween.Api.DataModel;
using System.Net.Http.Headers;
namespace OpenTween.Api
{
public class TwitterApiStatus
{
public TwitterApiAccessLevel AccessLevel { get; set; }
public EndpointLimits AccessLimit { get; }
public ApiLimit MediaUploadLimit { get; set; }
public class AccessLimitUpdatedEventArgs : EventArgs
{
public string EndpointName { get; }
public AccessLimitUpdatedEventArgs(string endpointName)
{
this.EndpointName = endpointName;
}
}
public event EventHandler AccessLimitUpdated;
public TwitterApiStatus()
{
this.AccessLimit = new EndpointLimits(this);
}
public void Reset()
{
this.AccessLevel = TwitterApiAccessLevel.Anonymous;
this.AccessLimit.Clear();
this.MediaUploadLimit = null;
}
internal static ApiLimit ParseRateLimit(IDictionary header, string prefix)
{
var limitCount = (int?)ParseHeaderValue(header, prefix + "Limit");
var limitRemain = (int?)ParseHeaderValue(header, prefix + "Remaining");
var limitReset = ParseHeaderValue(header, prefix + "Reset");
if (limitCount == null || limitRemain == null || limitReset == null)
return null;
var limitResetDate = UnixEpoch.AddSeconds(limitReset.Value).ToLocalTime();
return new ApiLimit(limitCount.Value, limitRemain.Value, limitResetDate);
}
internal static TwitterApiAccessLevel? ParseAccessLevel(IDictionary header, string headerName)
{
if (!header.ContainsKey(headerName))
return null;
// たまに出てくる空文字列は無視する
if (string.IsNullOrEmpty(header[headerName]))
return null;
switch (header[headerName])
{
case "read-write-directmessages":
case "read-write-privatemessages":
return TwitterApiAccessLevel.ReadWriteAndDirectMessage;
case "read-write":
return TwitterApiAccessLevel.ReadWrite;
case "read":
return TwitterApiAccessLevel.Read;
default:
MyCommon.TraceOut("Unknown ApiAccessLevel:" + header[headerName]);
return TwitterApiAccessLevel.ReadWriteAndDirectMessage;
}
}
internal static long? ParseHeaderValue(IDictionary dict, params string[] keys)
{
foreach (var key in keys)
{
if (!dict.ContainsKey(key)) continue;
long result;
if (long.TryParse(dict[key], out result))
return result;
}
return null;
}
public void UpdateFromHeader(HttpResponseHeaders header, string endpointName)
{
this.UpdateFromHeader(header.ToDictionary(x => x.Key, x => string.Join(",", x.Value)), endpointName);
}
public void UpdateFromHeader(IDictionary header, string endpointName)
{
var rateLimit = TwitterApiStatus.ParseRateLimit(header, "X-Rate-Limit-");
if (rateLimit != null)
this.AccessLimit[endpointName] = rateLimit;
var mediaLimit = TwitterApiStatus.ParseRateLimit(header, "X-MediaRateLimit-");
if (mediaLimit != null)
this.MediaUploadLimit = mediaLimit;
var accessLevel = TwitterApiStatus.ParseAccessLevel(header, "X-Access-Level");
if (accessLevel.HasValue)
this.AccessLevel = accessLevel.Value;
}
private static readonly DateTime UnixEpoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
public void UpdateFromJson(string json)
{
using (var jsonReader = JsonReaderWriterFactory.CreateJsonReader(Encoding.UTF8.GetBytes(json), XmlDictionaryReaderQuotas.Max))
{
var xElm = XElement.Load(jsonReader);
XNamespace a = "item";
var q =
from res in xElm.Element("resources").Descendants(a + "item") // a:item 要素を列挙
select new {
endpointName = res.Attribute("item").Value,
limit = new ApiLimit(
int.Parse(res.Element("limit").Value),
int.Parse(res.Element("remaining").Value),
UnixEpoch.AddSeconds(long.Parse(res.Element("reset").Value)).ToLocalTime()
),
};
this.AccessLimit.AddAll(q.ToDictionary(x => x.endpointName, x => x.limit));
}
}
protected virtual void OnAccessLimitUpdated(AccessLimitUpdatedEventArgs e)
{
this.AccessLimitUpdated?.Invoke(this, e);
}
public class EndpointLimits : IEnumerable>
{
public TwitterApiStatus Owner { get; }
private ConcurrentDictionary innerDict = new ConcurrentDictionary();
public EndpointLimits(TwitterApiStatus owner)
{
this.Owner = owner;
}
public ApiLimit this[string endpoint]
{
get
{
ApiLimit limit;
return this.innerDict.TryGetValue(endpoint, out limit)
? limit
: null;
}
set
{
this.innerDict[endpoint] = value;
this.Owner.OnAccessLimitUpdated(new AccessLimitUpdatedEventArgs(endpoint));
}
}
public void Clear()
{
this.innerDict.Clear();
this.Owner.OnAccessLimitUpdated(new AccessLimitUpdatedEventArgs(null));
}
public void AddAll(IDictionary resources)
{
foreach (var res in resources)
{
this.innerDict[res.Key] = res.Value;
}
this.Owner.OnAccessLimitUpdated(new AccessLimitUpdatedEventArgs(null));
}
public IEnumerator> GetEnumerator()
{
return this.innerDict.GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
}
}
}