Set "client_updated_time" when uploading file to OneDrive - onedrive

I have nearly completed a basic OneDrive interface, and am able to handle creating and writing folders, files, and so forth. The last element to get working is to update a file's date/time stamp to match that of the local (originating) file.
When I drop a file via the browser interface, the file's date/time stamp shows correctly in the view. This is reflected in the "client_updated_time" when I read the file's properties later in my application. Clear enough.
However, I cannot find any way to update this field programmatically from within my application. I am using the following code, to no avail. I have a valid _accessToken value, a valid fileId for the new file, and the call results always indicates success.
The "name" and "updated_time" elements are just in there to see if anything happens, and the file will indeed rename if I mangle the fileName variable a bit. I didn't expect the "updated_time" to updated, but it seems to me that the "client_updated_time" element should work.
Using fiddler, it appears that the browser-based interface (java?) opens a session, sends the file over, and then in the close-session call uses a header entry labelled "X-Last-Modified-ISO8601" to set the file's date-time stamp. However, using the REST interface, I cannot find any examples of this. The documentation for setting file properties mentions renaming only (which works in this code).
Any feedback on how to accomplish setting "client_updated_time" with REST calls would be much appreciated!
Here's the relevant code:
Private _liveURL As String = "https://apis.live.net/v5.0/"
Private Sub AddAuthorizationHeader(hc As HttpClient, authorization As String)
If Len(authorization) > 0 Then
hc.DefaultRequestHeaders.Authorization = New Headers.AuthenticationHeaderValue("Bearer", authorization)
End If
End Sub
Public Function WebPUT(uri As String, contentType As String, authorization As String, data As String) As String
Dim response As String = String.Empty
Try
Dim hc As New HttpClient()
Dim content As New Http.StringContent(data, System.Text.Encoding.UTF8, contentType)
AddAuthorizationHeader(hc, authorization)
Using r = hc.PutAsync(uri, content).Result
response = r.Content.ReadAsStringAsync.Result
End Using
Catch ex As Exception
' fake it
response = "{""error"": {""code"": ""invalid_request"", ""message"": """ + ex.Message + """}}"
End Try
Return response
End Function
Private Function UpdateFileDateTime(fileId As String, fileName As String, fileDt As String) As Boolean
Dim response As Boolean = False
Dim wr As String = WebHelper.WebPUT(_liveURL + fileId, "application/json", _accessToken, "{ ""name"": """ + fileName + """, ""updated_time"": """ + fileDt + """, ""client_updated_time"": """ + fileDt + """ }")
'... parse wr for response
Return response
End Function

Based on the file object reference, that field is read-only via the REST API: http://msdn.microsoft.com/en-us/library/dn631834.aspx

Related

How to get the FolderID from Google Drive folders using Chilkat DLL

Im using the ChilKat to develop a tool using VB.NET, that performs a single file upload to my google drive account.
Im able to get the folderID within a root folder, but im struggling to get the folderID if there is a path of folders.
At this moment the argument FolderPath is not being used (i will, when i uncover how to properly get the FolderID). For now, i can get the "Nova" folder id, but none of the other folders within the following tree:
Is there an easier way to get the folderID from GoogleDrive?
I would also like to create the path of folders on Google Drive, in case they dont exist.
I never worked with JSON or HTTP requests, so im kind of lost here.
Any help would be mostly appreciated!
Thanks in advance!
Private Function FolderID(ByVal FolderPath As String) As String
Dim rest As New Chilkat.Rest
' Connect using TLS.
Dim success As Boolean = rest.Connect("www.googleapis.com", 443, True, True)
' Provide the authentication credentials (i.e. the access token)
Dim gAuth As New Chilkat.AuthGoogle
gAuth.AccessToken = M_AccessToken
rest.SetAuthGoogle(gAuth)
Dim json As New Chilkat.JsonObject
json.EmitCompact = False
' Get the folder Testes folder that is in the Google Drive root.
rest.AddQueryParam("q", "'root' in parents and name='Testes'")
Dim jsonResponse As String = rest.FullRequestNoBody("GET", "/drive/v3/files")
If Not rest.LastMethodSuccess Then
Return rest.LastErrorText
Exit Function
End If
json.Load(jsonResponse)
rest.ClearAllQueryParams()
' Now that we know the ID for the Testes directory, get the id for the folder Nova having Testes as the parent.
Dim sbQuery As New Chilkat.StringBuilder
sbQuery.Append("name = 'nova' and '")
sbQuery.Append(json.StringOf("files[0].id"))
sbQuery.Append("' in parents")
rest.AddQueryParamSb("q", sbQuery)
jsonResponse = rest.FullRequestNoBody("GET", "/drive/v3/files")
If Not rest.LastMethodSuccess Then
Return (rest.LastErrorText)
Exit Function
End If
json.Load(jsonResponse)
Return json.StringOf("files[0].id")
End Function
I managed a way by performing iterative requests.
Don't know if this is the correct way, but it works...
Here's the code, now using the FolderPath with format /folder1/folder2/folderN
Private Function GetFolderID(ByVal FolderPath As String) As String
Dim Rest As New Chilkat.Rest
' Connect to Google APIs server
Dim Connected As Boolean = Rest.Connect("www.googleapis.com", 443, True, True)
If Not Connected Then
Return "Error attempting to connect: " & Rest.ConnectFailReason
Exit Function
End If
' Provide the Access token
Dim GAuth As New Chilkat.AuthGoogle
GAuth.AccessToken = M_AccessToken
Rest.SetAuthGoogle(GAuth)
' Instance to JSON object
Dim JSON As New Chilkat.JsonObject
JSON.EmitCompact = False
' Parse the provided path and split to array
Dim ParseFolder As String = Strings.Right(FolderPath, Len(FolderPath) - 1)
Dim Folders As String() = Split(ParseFolder, "/")
' Get the root folder that is in the Google Drive folders structure
Rest.AddQueryParam("q", "'root' in parents and name='" & Folders(0) & "'")
Dim Response As String = Rest.FullRequestNoBody("GET", "/drive/v3/files")
If Not Rest.LastMethodSuccess Then
Return Rest.LastErrorText
Exit Function
End If
JSON.Load(Response)
'Iterate on the folders to get the last folder's id
Rest.ClearAllQueryParams()
For i = 1 To Folders.Length - 1
Dim sbQuery As New Chilkat.StringBuilder
sbQuery.Append("name = '" & Folders(i) & "' and '")
sbQuery.Append(JSON.StringOf("files[0].id"))
sbQuery.Append("' in parents")
Rest.AddQueryParamSb("q", sbQuery)
Response = Rest.FullRequestNoBody("GET", "/drive/v3/files")
If Not Rest.LastMethodSuccess Then
Return Rest.LastErrorText
Exit Function
End If
JSON.Load(Response)
Next
' Get the folder id
Return JSON.StringOf("files[0].id")
End Function

.NET Google API access token failing with no refresh token specified

I am trying to set up a class that can wrap around the .NET Google API so that I can use an Access Token that I have previously obtained to access a user's Google Drive. As of right now, I am just trying to get it to work so that I do not require a Refresh Token (more on that in a second). The ultimate goal is for somebody to go through a web page I have set up to authenticate where I obtain both an Access Token and a Refresh Token by directly calling to the Google Rest API (which I store in a database). They can then request to upload/download files onto their Drive on a different page which will first obtain the appropriate information from the database and then use the .NET Google API Library when accessing Drive.
However, when I attempt to access their Drive I get the the following error:
The access token has expired and could not be refreshed. Errors: refresh error, refresh error, refresh error
I know that the Access Token is valid because I obtain it only seconds earlier during my testing. Here is my code for setting up the Drive Service:
' NOTE: Code altered for brevity
Public Sub Initialize(accessToken As String)
' Set up the client secret information based on the default constants
Dim clientSecrets As New ClientSecrets()
clientSecrets.ClientId = DEFAULT_CLIENT_ID
clientSecrets.ClientSecret = DEFAULT_CLIENT_SECRET
' Set up a token based on the token data we got
' NOTE: Is it OK to leave some strings as NULL?
Dim token As New Responses.TokenResponse()
token.AccessToken = accessToken
token.RefreshToken = ""
token.TokenType = "Bearer"
token.IssuedUtc = DateTime.Now
token.ExpiresInSeconds = 3600
token.Scope = "drive"
token.IdToken = ""
' Set up a flow for the user credential
Dim init As New GoogleAuthorizationCodeFlow.Initializer()
init.ClientSecrets = clientSecrets
init.Scopes = New String() {DriveService.Scope.Drive}
init.Clock = Google.Apis.Util.SystemClock.Default
' Set up everything else and initialize the service
Dim baseInit As New BaseClientService.Initializer()
baseInit.HttpClientInitializer = New UserCredential(New GoogleAuthorizationCodeFlow(init), "user", token)
baseInit.ApplicationName = APP_NAME
_service = New DriveService(baseInit)
End Sub
Shortly after that, I then use the following code to request a folder so I can check to see if it exists or not.
Private Function GetDriveFolder(folderPath As String, ByRef folderIds As String(), Optional createMissingFolders As Boolean = False, Optional parentFolderId As String = "root") As Data.File
Dim creatingFolderPath As Boolean = False
Dim currentFolder As Data.File = Nothing
Dim folderPathSplit As String() = folderPath.Replace("/", "\").Trim("\").Split("\")
Dim folderIdList As New List(Of String)
folderIds = {}
' Loop through each folder in the path and seek each out until we reach the end
For x As Integer = 0 To folderPathSplit.Length - 1
Dim result As FileList = Nothing
If Not creatingFolderPath Then
' Build a list request which we will use to seek out the next folder
Dim request As FilesResource.ListRequest = _service.Files.List()
request.Q = "mimeType='application/vnd.google-apps.folder' and name='" & folderPathSplit(x) & "'"
If currentFolder Is Nothing Then
request.Q &= " and '" & EscapeDriveValue(parentFolderId) & "' in parents"
Else
request.Q &= " and '" & EscapeDriveValue(currentFolder.Id) & "' in parents"
End If
request.Spaces = "drive"
request.Fields = "files(id, name)"
' Execute the search, we should only get a single item back
' NOTE: Error thrown on this request
result = request.Execute()
End If
' So on.....
So, I'm just trying to get it to work with only the Access Token for the time being because if it ends up getting refreshed I'll need to know so that I can update my database. However, if I do include the Refresh Token I get the following error:
Error:"unauthorized_client", Description:"Unauthorized", Uri:""
I'm guessing this has something to do with the way I have configured my application through the Dev Console but if I authenticate through the Google API Library by having it launch a browser to get my credentials everything works fine. So, I'm really not sure where to go from here as I haven't found anybody having similar problems and the guides don't cover specifying your own Access Token.
Also, as a quick note this is the URL I am using when having the user authenticate:
String.Format("https://accounts.google.com/o/oauth2/v2/auth?client_id={0}&state={1}&redirect_uri={2}&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fdrive&access_type=offline&include_granted_scopes=true&prompt=select_account%20consent&response_type=code", GOOGLEAPI_CLIENTID, validateState, redirectUri)
Thanks for the help!
If you have an access-token then the simplest way to create a google credential is to use the GoogleCredential.FromAccessToken() method passing in your access token.
This returns you a GoogleCredential instance which you can use to set the HttpClientInitializer property when building the DriveService.
If you then still get an error when accessing the drive service, then it's likely there's something incorrect in how you are asking for the access-token.

How do I upload data to a server with valid SSL certificate using a client vb.net program?

I am working on a program and uploads a shipping manifest to a the shippers website. When I try to upload, I get a nondescript error back from their server, and when checking with the shipper, they tell me that "there is an issue with the SSL" I am using.
I've spent quite a bit of time piecing together code that, from what I seem to understand, is supposed to work, but I'm not making any progress. As far as I know everything else is fine with the upload, but there is a problem with my SSL certificate
If I understand what this code is supposed to do correctly, I should get a certificate from the shippers website, which allows certification to my program for a space of time during which I can upload the data. I'm really not sure that this is what my code is doing at all, but the only code examples I have seen show it something like this.
Here's my code with the URLs changed:
'This references a custom class that compiles the manifest I'm going to upload
Dim StringToUpload As String = Compile_Manifest(MyDate, UseTestDB)
Dim webClient As New System.Net.WebClient
webClient.Credentials = System.Net.CredentialCache.DefaultCredentials
'From what I understand,
'this is supposed to set up properties used in next section of code
System.Net.ServicePointManager.SecurityProtocol = Net.SecurityProtocolType.Ssl3
System.Net.ServicePointManager.ServerCertificateValidationCallback = _
AddressOf AcceptAllCertifications
'I can see that this reaches the server,
'but I don't know how it relates to the next section of code
'that actually uploads the manifest
Dim ServerRequest As System.Net.WebRequest = _
System.Net.WebRequest.Create("https://www.certify.some-shippper.com:443/somefolder")
Dim ServerResponse As System.Net.WebResponse
ServerResponse = ServerRequest.GetResponse()
ServerResponse.Close()
'This code works for the upload of the manifest,
'and it seems the above code is unrelated and does not use a SSL certificate.
'When this code runs I get the same error back from the shippers server,
'indicating an issue with my SSL, with or without the two sections of code above.
Dim StrResult As String = ""
Dim WrappedString As String = TransmitPLD.WrapPldFile(StringToUpload)
'This references a custom class that wraps the data to upload
'in information from the shipper.
Dim ByesToUpload As Byte() = _
System.Web.HttpUtility.UrlEncodeToBytes(WrappedString, _
System.Text.ASCIIEncoding.ASCII)
Dim Result As Byte() = _
webClient.UploadData("https://www.certify.some-shippper.com:443/somefolder", _
ByesToUpload)
StrResult = System.Web.HttpUtility.UrlDecode(Result, _
System.Text.ASCIIEncoding.ASCII)
MessageBox.Show(StrResult)
So it turns out I went about it the wrong way. I needed to upload my data through System.Net.WebRequest and it takes care of the certificates for me. Not implementing all the parts of the code I needed, it didn't handle the retrieval of the shipper's certificate.
In case anyone else gets confused about the matter like I did, here's my working code for anyone to see, adapt and use. My resource for fixing the code (and by that I mean starting from scratch) was the MSDN page for the WebRequest class, and it has code examples much the same as what I have below in C++, C#, and VB.NET and here is the link.
First there are some global variables that need to be set and class that needs to be created for to store the upload response:
' This is set in the function that Upload function
' and uploads the data in the ReadCallback sub
Private Shared WrappedString As String
' This is used to wait for the callback in the Upload function
Private Shared allDone As New Threading.ManualResetEvent(False)
Friend Class RequestState
' This class stores the request state of the request.
Public request As Net.WebRequest
Public Sub New()
request = Nothing
End Sub ' New
End Class ' RequestState
Then there is a sub needed for the upload part web request which will be called further below in the upload function:
Private Shared Sub ReadCallback(asynchronousResult As IAsyncResult)
Try
Dim myRequestState As RequestState = CType(asynchronousResult.AsyncState, RequestState)
Dim myWebRequest As Net.WebRequest = myRequestState.request
' End the request.
Dim streamResponse As IO.Stream = myWebRequest.EndGetRequestStream(asynchronousResult)
' Convert the string into a byte array.
Dim byteArray As Byte() = System.Text.Encoding.ASCII.GetBytes(WrappedString)
' Write the data to the stream.
streamResponse.Write(byteArray, 0, byteArray.Length)
streamResponse.Close()
' Allow the main thread to resume.
allDone.Set()
Catch ex As Exception
Throw New Exception("Error in " & Reflection.MethodBase.GetCurrentMethod.Name.ToString & " **" & ex.Message, ex)
End Try
End Sub ' ReadCallback
Finally, this is the function that should be called to upload the data, which uses all the code above:
Public Shared Function Upload(ByVal MyDate As Date) As String
Dim StrResult As String = ""
UploadSucess = False
Try
' This is my code that builds the manifest that I want to upload
Dim StringToUpload As String = Compile_PLD200(MyDate)
WrappedString = TransmitPLD.WrapPldFile(StringToUpload)
Dim myWebRequest As Net.WebRequest
myWebRequest = Net.WebRequest.Create("https://www.some.website.com:443/someplace")
' Create an instance of the RequestState and assign
' myWebRequest to it's request field.
Dim myRequestState As New RequestState()
myRequestState.request = myWebRequest
myWebRequest.ContentType = "multipart/mixed; boundary=BOUNDARY"
myRequestState.request.Method = "POST"
' Start the asynchronous 'BeginGetRequestStream' method call.
Dim r As IAsyncResult = CType(myWebRequest.BeginGetRequestStream(AddressOf ReadCallback, myRequestState), IAsyncResult)
' Pause the current thread until the async operation completes.
allDone.WaitOne()
' Send the Post and get the response.
Dim myWebResponse As Net.WebResponse = myWebRequest.GetResponse()
Dim streamResponse As IO.Stream = myWebResponse.GetResponseStream()
Dim streamRead As New IO.StreamReader(streamResponse)
Dim readBuff(256) As [Char]
Dim count As Integer = streamRead.Read(readBuff, 0, 256)
While count > 0
Dim outputData As New [String](readBuff, 0, count)
Console.WriteLine(outputData)
count = streamRead.Read(readBuff, 0, 256)
StrResult += outputData
End While
' Close the Stream Object.
streamResponse.Close()
streamRead.Close()
' Release the HttpWebResponse Resource.
myWebResponse.Close()
Catch ex As Exception
Throw New Exception("Error in " & Reflection.MethodBase.GetCurrentMethod.Name.ToString & " **" & ex.Message, ex)
End Try
Return StrResult
End Function ' Upload
Again here is the MSDN page for the WebRequest class which has a code example too.
Hope this helps anyone who was stuck like I was. And any criticisms as to the implementation of the code are welcome. This just happen to do what I want, I can't say it is the most efficient implementation.

How can I fetch the last-modified value of a remote file?

I'd like to know the last-modifed date of a remote file (defined via url).
And only download it, if it's newer than my locally stored one.
I managed to do that for local files, but can't find a solution to do that for remote files (without downloading them)
working:
Dim infoReader As System.IO.FileInfo = My.Computer.FileSystem.GetFileInfo("C:/test.txt")
MsgBox("File was last modified on " & infoReader.LastWriteTime)
not working:
Dim infoReader As System.IO.FileInfo = My.Computer.FileSystem.GetFileInfo("http://google.com/robots.txt")
MsgBox("File was last modified on " & infoReader.LastWriteTime)
I'd love to have a solution which will only have to download the headers of a file
You can use the System.Net.Http.HttpClient class to fetch the last modified date from the server. Because it's sending a HEAD request, it will not fetch the file contents:
Dim client = New HttpClient()
Dim msg = New HttpRequestMessage(HttpMethod.Head, "http://google.com/robots.txt")
Dim resp = client.SendAsync(msg).Result
Dim lastMod = resp.Content.Headers.LastModified
You could also use the If-Modified-Since request header with a GET request. This way the response should be 304 - Not Modified if the file has not been changed (no file content sent), or 200 - OK if the file has been changed (and the contents of the file will be sent in the response), although the server is not required to honor this header.
Dim client = New HttpClient()
Dim msg = New HttpRequestMessage(HttpMethod.Get, "http://google.com/robots.txt")
msg.Headers.IfModifiedSince = DateTimeOffset.UtcNow.AddDays(-1) ' use the date of your copy of the file
Dim resp = client.SendAsync(msg).Result
Select Case resp.StatusCode
Case HttpStatusCode.NotModified
' Your copy is up-to-date
Case HttpStatusCode.OK
' Your copy is out of date, so save it
File.WriteAllBytes("C:\robots.txt", resp.Content.ReadAsByteArrayAsync.Result)
End Select
Note the use of .Result, since I was testing in a console application - you should probably await instead.
If the server offers it, you can get it through the HTTP header Last-Modified property. But your still stuck at downloading the full file.
You could get it through FTP.
See if the server allows you to see the list of files in a folder.
If the website offer the date somewhere that you could pull through screen scrapping.
I know this is a little bit old question but, there's still a better answer.
Dim req As WebRequest = HttpWebRequest.Create("someurl")
req.Method = "HEAD"
Dim resp As WebResponse = req.GetResponse()
Dim remoteFileLastModified As String = resp.Headers.Get("Last-Modified")
Dim remoteFileLastModifiedDateTime As DateTime
If DateTime.TryParse(remoteFileLastModified, remoteFileLastModifiedDateTime) Then
MsgBox("Date Last Modified:" + remoteFileLastModifiedDateTime.ToString("d MMMM yyyy dddd HH:mm:ss"))
Else
MsgBox("could not determine")
End If

Check if downloaded file exists by comparing it's file size

Is there a way to check a downloaded file is already exists by comparing it's file size?
Below is my download code.
Private Sub bgw_DoWork(ByVal sender As Object, ByVal e As System.ComponentModel.DoWorkEventArgs)
Dim TestString As String = "http://123/abc.zip," & _
"http://abc/134.zip,"
address = TestString.Split(CChar(",")) 'Split up the file names into an array
'loop through each file to download and create/start a new BackgroundWorker for each one
For Each add As String In address
'get the path and name of the file that you save the downloaded file to
Dim fname As String = IO.Path.Combine("C:\Temp", IO.Path.GetFileName(add))
My.Computer.Network.DownloadFile(add, fname, "", "", False, 60000, True) 'You can change the (False) to True if you want to see the UI
'End If
Next
End Sub
The size of a local file can be determined using the File or FileInfo class from the System.IO namespace. To determine the size of a file to be downloaded by HTTP, you can use an HttpWebRequest. If you set it up as though you were going to download the file but then set the Method to Head instead of Get you will get just the response headers, from which you can read the file size.
I've never done it myself, or even used an HttpWebRequest to download a file, so I'm not going to post an example. I'd have to research it and you can do that just as easily as I can.
Here's an existing thread that shows how it's done in C#:
How to get the file size from http headers
Here's a VB translation of the code from the top answer:
Dim req As System.Net.WebRequest = System.Net.HttpWebRequest.Create("https://stackoverflow.com/robots.txt")
req.Method = "HEAD"
Using resp As System.Net.WebResponse = req.GetResponse()
Dim ContentLength As Integer
If Integer.TryParse(resp.Headers.Get("Content-Length"), ContentLength)
'Do something useful with ContentLength here
End If
End Using
A better practice would be to write this line:
req.Method = "HEAD"
like this:
req.Method = System.Net.WebRequestMethods.Http.Head