Created
June 23, 2015 05:55
-
-
Save rgregg/2a5580d8b3523ffb6f1f to your computer and use it in GitHub Desktop.
OneDrive View.Changes Experiment Program
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
using System.Text; | |
using System.Threading.Tasks; | |
using System.Net; | |
using Newtonsoft.Json; | |
using System.IO; | |
// Requires Newtonsoft.Json and Nito.AsyncEx NuGet Packages | |
namespace OneDriveSyncExperiment | |
{ | |
class Program | |
{ | |
static void Main(string[] args) | |
{ | |
if (args.Length != 1) | |
{ | |
Console.WriteLine("Expected 1 command line argument: access_token"); | |
return; | |
} | |
string accessToken = args[0]; | |
Nito.AsyncEx.AsyncContext.Run(() => MainAsync(accessToken)); | |
} | |
static async Task MainAsync(string accessToken, string token = null, Dictionary<string, OneDriveItem> ItemMetadata = null) | |
{ | |
if (null == ItemMetadata) | |
{ | |
ItemMetadata = new Dictionary<string, OneDriveItem>(); | |
} | |
ViewChangesResults results = null; | |
DateTimeOffset startDateTime = DateTimeOffset.UtcNow; | |
do | |
{ | |
Console.WriteLine("Requesting view.changes..."); | |
Dictionary<ItemResponse, int> packageCounts = new Dictionary<ItemResponse, int>(); | |
foreach (ItemResponse value in Enum.GetValues(typeof(ItemResponse))) | |
{ | |
packageCounts[value] = 0; | |
} | |
results = await CallViewChanges(accessToken, "id,name,cTag,parentReference,deleted,eTag", token, 1000); | |
foreach (var item in results.Results) | |
{ | |
var result = ProcessItemState(ItemMetadata, item); | |
packageCounts[result] += 1; | |
} | |
token = results.NextToken; | |
Console.WriteLine("Response C:{0}, M:{1}, U:{2}, D:{3}, NM: {4}, MU: {5}", packageCounts[ItemResponse.Created], packageCounts[ItemResponse.Moved], packageCounts[ItemResponse.Modified], packageCounts[ItemResponse.Deleted], packageCounts[ItemResponse.Unmodified], packageCounts[ItemResponse.MovedAndEdited]); | |
} | |
while (null != results && results.HasMoreChanges.HasValue && results.HasMoreChanges.Value); | |
TimeSpan duration = DateTimeOffset.UtcNow.Subtract(startDateTime); | |
Console.WriteLine(); | |
Console.WriteLine("Call duration: {0}", duration); | |
Console.WriteLine("Found items: {0}", ItemMetadata.Count); | |
Console.WriteLine(); | |
char input = AskForInput("[C]ontinue, [W]rite or [Q]uit: "); | |
if (input == 'c') | |
{ | |
await MainAsync(accessToken, token, ItemMetadata); | |
} | |
else if (input == 'w') | |
{ | |
WriteFinalState(ItemMetadata); | |
} | |
} | |
static char AskForInput(string prompt) | |
{ | |
while (true) | |
{ | |
Console.Write(prompt); | |
var input = Console.ReadKey(); | |
switch (input.KeyChar) | |
{ | |
case 'C': | |
case 'c': | |
return 'c'; | |
case 'W': | |
case 'w': | |
return 'w'; | |
case 'Q': | |
case 'q': | |
return 'q'; | |
} | |
} | |
} | |
private static void WriteFinalState(Dictionary<string, OneDriveItem> ItemMetadata) | |
{ | |
Console.WriteLine("End state:"); | |
List<string> knownPaths = new List<string>(); | |
foreach (OneDriveItem item in ItemMetadata.Values) | |
{ | |
knownPaths.Add(PathForItem(item, ItemMetadata)); | |
} | |
knownPaths.Sort(); | |
foreach (var path in knownPaths) | |
{ | |
Console.WriteLine(path); | |
} | |
} | |
private static ItemResponse ProcessItemState(Dictionary<string, OneDriveItem> ItemMetadata, OneDriveItem item) | |
{ | |
OneDriveItem previouslyKnownItem = null; | |
string previousPath = null; | |
if (ItemMetadata.TryGetValue(item.Id, out previouslyKnownItem)) | |
{ | |
previousPath = PathForItem(previouslyKnownItem, ItemMetadata); | |
} | |
ItemResponse response; | |
if (null != item.Deleted) | |
{ | |
Console.WriteLine("Deleted: {0}", previousPath); | |
ItemMetadata.Remove(item.Id); | |
response = ItemResponse.Deleted; | |
} | |
else if (null != previouslyKnownItem) | |
{ | |
if (previouslyKnownItem.ETag == item.ETag) | |
{ | |
Console.WriteLine("Item not-modified: {0}", previousPath); | |
response = ItemResponse.Unmodified; | |
} | |
else if (previouslyKnownItem.ParentReference.Id != item.ParentReference.Id) | |
{ | |
if (previouslyKnownItem.CTag == item.CTag) | |
{ | |
Console.WriteLine("Item moved to {0}", PathForItem(item, ItemMetadata)); | |
response = ItemResponse.Moved; | |
} | |
else | |
{ | |
Console.WriteLine("Item moved and edited to {0}", PathForItem(item, ItemMetadata)); | |
response = ItemResponse.MovedAndEdited; | |
} | |
} | |
else | |
{ | |
Console.WriteLine("Item modified: {0}", PathForItem(item, ItemMetadata)); | |
response = ItemResponse.Modified; | |
} | |
ItemMetadata[item.Id] = item; | |
} | |
else | |
{ | |
Console.WriteLine("New item: {0}", PathForItem(item, ItemMetadata)); | |
ItemMetadata[item.Id] = item; | |
response = ItemResponse.Created; | |
} | |
return response; | |
} | |
enum ItemResponse | |
{ | |
Deleted, | |
Modified, | |
Moved, | |
Created, | |
Unmodified, | |
MovedAndEdited | |
} | |
static async Task<ViewChangesResults> CallViewChanges(string accessToken, string select = null, string previousToken = null, int? top = 200) | |
{ | |
const string rootUrl = "https://api.onedrive.com/v1.0/drive/root/view.changes"; | |
StringBuilder sb = new StringBuilder(rootUrl); | |
if (null != select || null != previousToken || null != top) | |
{ | |
sb.Append("?"); | |
} | |
if (null != select) | |
{ | |
if (sb.Length > 1) sb.Append("&"); | |
sb.Append("select="); | |
sb.Append(select); | |
} | |
if (null != previousToken) | |
{ | |
if (sb.Length > 1) sb.Append("&"); | |
sb.Append("token="); | |
sb.Append(previousToken); | |
} | |
if (null != top) | |
{ | |
if (sb.Length > 1) sb.Append("&"); | |
sb.Append("top="); | |
sb.Append(top.Value); | |
} | |
HttpWebRequest request = HttpWebRequest.CreateHttp(sb.ToString()); | |
request.Headers.Add("Authorization", "Bearer " + accessToken); | |
request.Accept = "application/json"; | |
request.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip; | |
HttpWebResponse response = null; | |
try | |
{ | |
response = await request.GetResponseAsync() as HttpWebResponse; | |
} | |
catch (WebException ex) | |
{ | |
response = ex.Response as HttpWebResponse; | |
} | |
if (null == response) | |
{ | |
throw new InvalidOperationException("Error making request to server."); | |
} | |
using (response) | |
{ | |
var responseStream = response.GetResponseStream(); | |
if (response.StatusCode >= HttpStatusCode.BadRequest) | |
{ | |
Console.WriteLine("Error from service: {0} {1}", response.StatusCode, response.StatusDescription); | |
using (StreamReader reader = new StreamReader(responseStream)) | |
{ | |
Console.WriteLine(reader.ReadToEnd()); | |
} | |
return null; | |
} | |
else | |
{ | |
var result = CreateFromJsonStream<ViewChangesResults>(responseStream); | |
return result; | |
} | |
} | |
} | |
static T CreateFromJsonStream<T>(Stream stream) | |
{ | |
JsonSerializer serializer = new JsonSerializer(); | |
T data; | |
using (StreamReader streamReader = new StreamReader(stream)) | |
{ | |
data = (T)serializer.Deserialize(streamReader, typeof(T)); | |
} | |
return data; | |
} | |
static string PathForItem(OneDriveItem item, Dictionary<string, OneDriveItem> knownItems) | |
{ | |
StringBuilder path = new StringBuilder(); | |
OneDriveItem previousItem = item; | |
while (null != previousItem) | |
{ | |
if (path.Length > 0) | |
path.Insert(0, "/"); | |
path.Insert(0, previousItem.Name); | |
if (!knownItems.TryGetValue(previousItem.ParentReference.Id, out previousItem)) | |
{ | |
previousItem = null; | |
} | |
} | |
while (null != previousItem) ; | |
return path.ToString(); | |
} | |
} | |
class ViewChangesResults | |
{ | |
[JsonProperty("@changes.hasMoreChanges")] | |
public bool? HasMoreChanges { get; set; } | |
//[JsonProperty("@odata.nextLink")] | |
//public string NextLink { get; set; } | |
[JsonProperty("@changes.token")] | |
public string NextToken { get; set; } | |
[JsonProperty("value")] | |
public List<OneDriveItem> Results { get; set; } | |
} | |
class OneDriveItem | |
{ | |
[JsonProperty("id")] | |
public string Id { get; set; } | |
[JsonProperty("name")] | |
public string Name { get; set; } | |
[JsonProperty("size")] | |
public long Size { get; set; } | |
[JsonProperty("eTag")] | |
public string ETag { get; set; } | |
[JsonProperty("cTag")] | |
public string CTag { get; set; } | |
[JsonProperty("@content.downloadUrl")] | |
public string ContentDownloadUrl { get; set; } | |
[JsonProperty("deleted")] | |
public dynamic Deleted { get; set; } | |
[JsonProperty("parentReference")] | |
public OneDriveItemReference ParentReference { get; set; } | |
} | |
class OneDriveItemReference | |
{ | |
[JsonProperty("driveId")] | |
public string DriveId { get; set; } | |
[JsonProperty("id")] | |
public string Id { get; set; } | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment