Skip to content

Instantly share code, notes, and snippets.

@rgregg
Created June 23, 2015 05:55
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save rgregg/2a5580d8b3523ffb6f1f to your computer and use it in GitHub Desktop.
Save rgregg/2a5580d8b3523ffb6f1f to your computer and use it in GitHub Desktop.
OneDrive View.Changes Experiment Program
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