Hackviking.com He killed Chuck Norris, he ruled dancing so he took up a new hobby…

1Feb/140

Python: Remove querystring from URL

Needed to clean an URL from it's querystring in Python. Found a lot of examples telling me to use urlparse and then put all the bits and pieces back together. An easier and more efficient  way is this:

url = 'http://www.hackviking.com/?var=value'
url = url[:url.find('?')]

url now reads 'http://www.hackviking.com/'

16Jan/140

.Net C#: Webbrowser control print line break

I was updating an old project for a client today. I needed to print a simple list and figured that the easiest way should be to format the data in HTML. Most developers out there has built webpages now days and we all know that it is a quick way to format the information and print it. So after a quick creation of the HTML i found an article named "Displaying custom HTML in WebBrowser control" by Gunnar Peipman. So easy enough to print the custom HTML from the control, based on Gunnar's article I came up with this code:

WebBrowser webPrint = new WebBrowser();
webPrint.Navigate("about:blank");

if (webPrint.Document != null)
{
webPrint.Document.Write(string.Empty);
}

webPrint.DocumentText = printHTML.ToString();
webPrint.DocumentCompleted += webPrint_DocumentCompleted;
void webPrint_DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
{
WebBrowser webPrint = (WebBrowser)sender;
webPrint.ShowPrintDialog();
}

If you don't use the "DocumentComplete" event handler your printout will be blank. In the print I used a basic HTML table to show the data with rows like this:

<table cellpadding="3" style="width:100%">
<tr style="color:White;background-color:Black;font-weight:bold;"><td>Box ID</td><td>Gång</td><td>Hylla</td><td>Nivå</td><td>Nåbar från golvet</td></tr>
<tr><td>10346</td><td>AB</td><td>3</td><td>F</td><td>True</td></tr>
<tr><td>10294</td><td>AB</td><td>3</td><td>D</td><td>True</td></tr>
<tr><td>10296</td><td>AB</td><td>3</td><td>C</td><td>True</td></tr>
<tr><td>10298</td><td>AB</td><td>3</td><td>C</td><td>True</td></tr>
<tr><td>10300</td><td>AB</td><td>3</td><td>B</td><td>True</td></tr>
<tr><td>10302</td><td>AB</td><td>3</td><td>B</td><td>True</td></tr>

When printing data that took up several pages I ended up with this:

pagebreak error

The data was cut of at the line breaks. I Googled a lot and found a few suggestions about css "page-break.." but they didn't have any effect on the webbrowser control print layout. Finally I found a simple soulition, just add a bland column to the left of the table and the line break works. Like this:

<table cellpadding="3" style="width:100%">
<tr style="color:White;background-color:Black;font-weight:bold;"><td> </td><td>Box ID</td><td>Gång</td><td>Hylla</td><td>Nivå</td><td>Nåbar från golvet</td></tr>
<tr><td> </td><td>10346</td><td>AB</td><td>3</td><td>F</td><td>True</td></tr>
<tr><td> </td><td>10294</td><td>AB</td><td>3</td><td>D</td><td>True</td></tr>
<tr><td> </td><td>10296</td><td>AB</td><td>3</td><td>C</td><td>True</td></tr>
<tr><td> </td><td>10298</td><td>AB</td><td>3</td><td>C</td><td>True</td></tr>
<tr><td> </td><td>10300</td><td>AB</td><td>3</td><td>B</td><td>True</td></tr>
<tr><td> </td><td>10302</td><td>AB</td><td>3</td><td>B</td><td>True</td></tr>
<tr><td> </td><td>10382</td><td>AB</td><td>4</td><td>F</td><td>True</td></tr>

Then the printout looked like this:

pagebreak fix

30Oct/130

Google Picasa API Python: Developers Guide

Before you ask... Yes I struggled with the title of this post, but give me a break it's 4:30 am here! :)

I'm currently developing in python for Google App Engine and are using the Picasa API. I found this great guide from Google with exampels: https://developers.google.com/picasa-web/docs/1.0/developers_guide_python It's far from complete but the examples pushes me in the right direction and then some testing, trial and error gives me a chance to learn even more! So i thought I would share one of the things that got me in to trouble this morning.

I was trying to add a new album to the feed and got this error:

(404, 'Not Found', 'Unknown user.')

Even though the user is authenticated via oauth. But then i realized that this was the same issue I head before, the users e-mail isn't in the mix. I needed it for layout purposes before, to display profiles and so on, you can read about that here: Python: Get user info after oauth All the request I have done before, getting feeds of albums, pictures and so on, has been uri input from me in the code. This function just looks like this:

gd_client.InsertAlbum(title='Test', summary='a test album')

Where the gd_client is an instance of gdata.photos.service.PhotosService() with the oauth token all-ready specified using the code example from the official Google guide and spiced with the functions from my previous mentioned post. But still it doesn't work! So I realized that even though it is authorized it has no idea who the user is trying to insert the new album. According to the feed documentation you should do a post like this:

POST https://picasaweb.google.com/data/feed/api/user/userID

<entry xmlns='http://www.w3.org/2005/Atom'
xmlns:media='http://search.yahoo.com/mrss/'
xmlns:gphoto='http://schemas.google.com/photos/2007'>
<title type='text'>Trip To Italy</title>
<summary type='text'>This was the recent trip I took to Italy.</summary>
<gphoto:location>Italy</gphoto:location>
<gphoto:access>public</gphoto:access>
<gphoto:timestamp>1152255600000</gphoto:timestamp>
<media:group>
<media:keywords>italy, vacation</media:keywords>
</media:group>
<category scheme='http://schemas.google.com/g/2005#kind'
term='http://schemas.google.com/photos/2007#album'></category>
</entry>

So here is what stuff goes wrong. Looking what the gd_client is actually doing is posting to https://picasaweb.google.com/data/feed/api/user/. There is a really simple soloution for this, if we return to the oauth example from the guide:

gd_client = gdata.photos.service.PhotosService()
gd_client.auth_token = authsub_token
gd_client.UpgradeToSessionToken()

This is a cut out where you get the "use once" token back and upgrade it to a session token. Here you can do two things to make the gd_client work with the oauth user as a default all the time, either:

gd_client = gdata.photos.service.PhotoService(email='default')

or

gd_client.email = 'default'

Then it all will work just fine!

30Oct/130

Google Code Project Home Page: Tips & Tricks

When I was updating the home page for Picasa Web Downloader on Google Code I found two things that might interest others! :)

Paypal Donation Buttons

I found more written on the subject then about any other issue on Google Code markup. I have to say that there is a really easy way! Just create a donation button on paypal, copy the url for the image, and use the email donation link to create a common <a> in the markup like this:

<a href="https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=XFGNUAPH5YR8C"><img src="http://www.paypalobjects.com/en_US/SE/i/btn/btn_donateCC_LG.gif" /></a>

Just be shore to close the img tag like /> otherwise it will mess up the markup.

Download Links

Google stopped the function of download links because of misuse. I found a blog entry written by one of there techs suggesting using Google Drive instead. In what way can you misuse free shared storage on Google Code that you cant misuse on Google Drive? Any way, I put up the link but when someone clicks the link for the latest release they get a view of the zip file contents on Google Drive instead. There is an easy fix for this! The link you get when you share it looks like this:

https://drive.google.com/file/d/0B7xgtMzrLFNNTE1XUUtjNXJsYVU/edit?usp=sharing

Just take the ID part (0B7xgtMzrLFNNTE1XUUtjNXJsYVU) and put it in this line below:

https://docs.google.com/uc?export=download&id=YourIndividualID

That will send the user directly to the download!

 

Filed under: Google Code No Comments
29Oct/130

KC Host Editor a blast from the past

Back in 2009 I did a lot of web development and that usually results in changing around the Windows host file all the time. Back then I used Windows Vista for windows development and wrote a quick sidebar gadget that can view, edit and temporarily comment out entries in your host file. It runs on Windows 7 as well if you unzip it and put the KCHostEditor.gadget folder into: %USERPROFILE%\AppData\Local\Microsoft\Windows Sidebar\Gadgets

Then you can just right click on the desktop an select "Gadgets" and add it. Just tried it out before running of so I'm not sure if it works for editing, I remember that I had to be admin on the machine. The machine I'm on now just gives me standard user, for now.... ;) But try it out if you like and if you want to do your own gadget refer to this guide:  http://msdn.microsoft.com/en-us/library/windows/desktop/bb456468(v=vs.85).aspx

[wpdm_file id=5]

Filed under: Customize, Windows No Comments
21Oct/132

Python: Get user info after oauth

Building webapps with Google APIs are pretty straight forward for the most part. Specially when using the Google API Client Libraries! But if you want to accomplish something that are outside of all the nice tutorials that Google supply us with it get's trickier! Being a C# programmer for many years I have noticed that there is loads of examples, tutorials and how-to write ups on the web for any and (almost) all problems I might run into. With the Google APIs and Google App Engine, not as much. I don't know why it should be pretty many people in that community willing to share?

Background

I'm currently developing a web application running on Google App Engine that uses the Picasa Web API. User get's redirected to the oauth site to approve access and I get a token back, easy enough! Getting lists of albums, pictures and movies is easy but getting user info? I'm fairly new to both python and Google App Engine so I browsed around for a long while without finding any solution to what I wanted to do! So before going to bed, defeated, I published a question on stackoverflow: http://stackoverflow.com/questions/19473250/how-to-get-user-email-after-oauth-with-google-api-python-client

Usually you get pretty quick answers from your peers but this time, nothing! Again... I'm I the only one doing stuff like this? Doubt it!
So I needed to solve the issue my self, getting dirty!

Break it down...

So my problem was that I wanted the users profile information like email, name and so on. I started by reading the protocol definition for the Picasa Web API, found here: https://developers.google.com/picasa-web/docs/2.0/reference Soon I realized that what I needed wasn't even sent back to me. So I needed to request the information!

After some Googleing for the issue I found a couple of suggestions that I should use Google Contacts API and get the author field from my own contacts, not a pretty solution!

Went over to the OAuth 2.0 Playground to check out what feeds would help me get the user info. In the list on the playground I didn't find much! Some of the other services provided the information with there feeds but I wanted something that just replied the information that I wanted. So I went to the source, Google APIs Explorer. I found a couple of scopes that would fit my needs:

  • https://www.googleapis.com/auth/userinfo.email
  • https://www.googleapis.com/auth/userinfo.profile

Checking out the scopes

So over to the Oauth playground and tried them out to see what info I could get out of them.

Scope: https://www.googleapis.com/auth/userinfo.email

Request URL: https://www.googleapis.com/userinfo/v2/me

Response:

{
"id": "113303504361682404626",
"email": "puh.kallsbo@gmail.com",
"verified_email": true
}

More or less exactly what I was looking for, needed the e-mail to get the gravatar. But why stop here let's see what else is available...

Scope: https://www.googleapis.com/auth/userinfo.profile

Request URL: https://www.googleapis.com/userinfo/v2/me

Response:

{
"id": "113303504361682404626",
"name": "Kristofer Källsbo",
"given_name": "Kristofer",
"family_name": "Källsbo",
"link": "https://profiles.google.com/puh.kallsbo",
"picture": "http://lh3.googleusercontent.com/-n6hDipRgm5k/AAAAAAAAAAI/AAAAAAAAVYY/XZd9zlnMmGk/photo.jpg",
"gender": "male",
"locale": "en"
}

So this more or less gave me all the user info but not the e-mail. So combining this with the one above would be a good idea!

Getting down to business (python)

So now I knew where to find the information. But how do a I access it as well as the Picasa Web API? So if we start from this exampel: https://developers.google.com/picasa-web/docs/1.0/developers_guide_python?csw=1#AuthSub

def GetAuthSubUrl():
next = 'http://www.example.com/welcome.pyc'
scope = 'https://picasaweb.google.com/data/'
secure = False
session = True
gd_client = gdata.photos.service.PhotosService()
return gd_client.GenerateAuthSubURL(next, scope, secure, session);

authSubUrl = GetAuthSubUrl();
print '<a href="%s">Login to your Google account</a>' % authSubUrl

So we need to add an additional scope for the user to authorize. Instead of passing a string as the scope variable we can pass a list of strings like this:

scope = ['https://picasaweb.google.com/data/', 'https://www.googleapis.com/auth/userinfo.email']

That will generate a token that is valid for both scopes! Following the example about the Picasa Web API we go ahead and retrieve a list of the users albums, but then what?

#Setup a basic gdata client
userClient = gdata.client.GDClient()
#Create a gauth sub token object from the session token we allready have in the gd_client
oauthToken = gdata.gauth.AuthSubToken(gd_client.GetAuthSubToken())
#Get the request, this userinfo is in json
response = userClient.request('GET','https://www.googleapis.com/userinfo/v2/me', oauthToken)
#Parse the webresponse
jobj = json.loads(response.read())
#Display
self.response.write('Your email is: {0}'.format(jobj['email']))

We start a gdata client and use that to retrieve the json. Line by line we actually doing the following:

userClient = gdata.client.GDClient() - Just creating an instanse of the Gdata client.

oauthToken = gdata.gauth.AuthSubToken(gd_client.GetAuthSubToken()) - Retrieving the OAuth token all ready used in the gd_client for the Picasa Web Albums. To be able to use it in the Gdata client we have to pass it as an object of type AuthSubToken or similar.

response = userClient.request('GET','https://www.googleapis.com/userinfo/v2/me', oauthToken) - Will return a standard python httplib HTTPResponse with the data returned from the server.

jobj = json.loads(response.read()) - The .read() function returns the body of the HTTPResponse object that we then can parse into a json object.

self.response.write('Your email is: {0}'.format(jobj['email'])) - Finally we have the users e-mail to do what we need to display profile info and so on...

Conclusion

Not all the documentation is great when it comes to Google APIs and Google App Engine. I have been programming sense the age of 8 so I usually find my way through it but I realize that this isn't a beginners language or field. On the other hand all the technical info is there like protocol specifications and stuff like that. Then there is some great tools to try the API out like OAuth playground. I hope this helps someone and a complete demo project for Google App Engine is available on Google Code, link below.

Links

16Oct/130

Google Code “Get started” is wrong

I was fixing with my project on Google code and as I remembered it you could have a download tab for compressed files with releases and so on. But I couldn't find the download tab, I referred to the  "Getting started guide" for Google Code and there it was, a reference to the downloads tab!

 

google code release sharing

But I still couldn't find it! After a while a come across this: http://google-opensource.blogspot.se/2013/05/a-change-to-google-code-download-service.html
People miss used it to much so it has been removed.... That's to bad, nice to have it all in one place. How ever.... Update needed on the "Getting started guide"!

15Oct/130

Picasa Web Downloader Source

Had a few downloads of the Picasa Web Downloader that I put up yesterday. Also had a few request of the source code, people seem to be nervous about putting there login information into an application that isn't open source.

So now it is open source available at: https://code.google.com/p/picasa-web-downloader/
Compiled and readu to run: [wpdm_file id=4]

Filed under: .Net, C#, Google, Picasa Web No Comments
14Oct/130

Picasa Web Downloader

I store all my memories on Picasa! Even spent a lot of time to scan all old pictures and upload them. Trusting a provider to keep your data safe from data loss is something that doesn't make me sleep well at night. Even though I trust the guys at Google to take care of the data I want a backup. Before I used Backupify.com but they don't provide this service anymore. The Picasa application it self is really bad! Maybe if your an old iTunes user you can figure out how it works? All kidding aside... It actually didn't manage to download all my pictures and got some strange errors. So I looked around the internet for a third party application for downloading all my content but I couldn't find anything useful!

I did how ever find a great application named Picasa Web Sync (http://www.geekytidbits.com/picasawebsync/) written by Brady Holt. Great peace of software for syncing up to Picasa but not the way I want to do it! So i started to check out the API for developing my own application, I took a quick look at the Google API Client Libraries. Not that surprising the java and python libraries was awesome and complete. The .Net C# however is not that good! There are things missing and stuff that doesn't work, spelling mistakes and so on. Google usually delivers above and beyond but in this case not so much.

How ever I started to code and used what I could from the client libraries, mostly to parse the feeds from the API. Then I realized there was a load of videos in my albums so I started to try to download them. Nothing in the documentation, that I could find anyway. But I found a way to do it and now I have a complete offline backup of my Picasa Web Albums, on a USB drive... Encrypted of course!

So the first working version of this console application is available for download! Written in c# and source will be posted soon for those who like that.

[wpdm_file id=4]

Filed under: .Net, C#, Google, Picasa Web No Comments
24Mar/130

PSC: Move share

shareerrorI have decided what the Power of scripting script should do! If you move a share by moving it in windows explorer the share will stop work. If you move the folder with File System Object from vbscript the share will still be there but will be pointing at the old path that no longer exists. So how to solve this?

I know a few ways that you don't. First attempt was to use the winnt://localhost/lanmanserver/ object to try updating the path of the share. No luck the property is read only!

Next attempt was using WMI instead. Same thing, unable to update the path. So i thought I delete the share and then create it again. Well enough that worked how ever I didn't manage to get the rights for the share to come along to the new one. There is methods to extract the access mask for the share and supply it when creating a new one, so I think it can be solved. I didn't have the time to do so this time and I think I proved my point about the power of scripting.

At the same time I have learned or realized a few things that I didn't thought about. Most scripts I have written in the past has been for controlled environments.  By that I mean a system where a specific task was asked from the script and the run was always with the same parameters. Here I'm writing a script that should run on different versions of the operating system and with several different variables.

So some of the limitations I ran into can be expressed in the words: Access Denied! It was issues with running some of the commands from the script without running it with elevation. This is no issue running it from the command prompt with cscript moveshare.vbs as long as you start the cmd with elevation (right-click and select Run As Administrator). There are ways around this even with the "double-click execution" of scripts that are discussed in detail here.

So the window version of the script is pretty useless with a standard setup. How ever if you run the script with elevation from the cmd you will be able to move shares around on the disk. The script is pretty ruff around the edges and some more error handling and stuff needs to be added. There probably will be solvable to get the share to keep it's access list as well. This script should be seen as a proof of concept.

[wpdm_file id=3]


'********************************************************************************************
'*
'*		Power of scripting challange!	
'*
'*		moveshare.vbs
'*
'*		Purpose: Move a shared folder from one location to another
'*		without losing the share!
'*
'*		Written by Kristofer Källsbo 
'*
'*		http://www.hackviking.com
'*
'********************************************************************************************
Option explicit
Dim FSO

'Get the filesystem object ready
Set FSO = CreateObject("Scripting.FileSystemObject")

'First check if we are running in console mode or window mode
If (InStr(1, WScript.FullName, "cscript", vbTextCompare)) Then
    InitConsole
ElseIf (InStr(1, WScript.FullName, "wscript", vbTextCompare)) Then
    InitWindow
Else
	' Hope this never fires! :)
    wscript.echo "How the hell did you start this script?"
End If

'Clean up
Set FSO = Nothing

'Handles the command line run of the script
Sub InitConsole()
	'Check if we have any parameters
	If (Wscript.Arguments.Count < 2) Then
		'Display help
		wscript.echo "Move share script"
		wscript.echo ""
		wscript.echo "Use: cscript moveshare.vbs {source} {destination}"
		wscript.echo ""
		wscript.echo "source = complete path of the shared folder"
		wscript.echo "destination = full path of the folder where the shared folder should be moved"
		wscript.echo ""
		wscript.echo "moveshare.vbs		Kristofer Källsbo	http://www.hackviking.com"
	Else
		'Verrify the parameters
		Dim sourcePath, destPath
		sourcePath = Wscript.Arguments(0)
		destPath = Wscript.Arguments(1)
		
		If Not (FSO.FolderExists(sourcePath)) Then
			'Display error
			wscript.echo "Source path was not found!"
			Exit Sub
		End If
		
		If Not (FSO.FolderExists(destPath)) Then
			'Display error
			wscript.echo "Destination path was not found!"
			
			'End this execution run
			Exit Sub
		End If
		
		'Do the move
		wscript.echo MoveShare(sourcePath, destPath)
	End If
End Sub

'Handles the double click run of the script
Sub InitWindow()
	Dim sourcePath, destPath, msgboxRetval
	
	'Get source path from user
	sourcePath = InputBox("This script will move an entire shared folder to a new location withou losing the share!" & vbNewLine & vbNewLine & "Enter path of share:", "Move Share Script")
	
	'Validate source path
	If (sourcePath = "") Then
		'Display error and end it
		msgboxRetval = MsgBox("Source path not entered or cancel was clicked!", vbOKOnly+vbCritical, "Source path error!")
		Exit Sub
	Else
		'Check that path exists
		If Not (FSO.FolderExists(sourcePath)) Then
			'Display error
			msgboxRetval = MsgBox("Source path was not found!", vbRetryCancel+vbCritical+vbDefaultButton1, "Source path error!") 
			
			'Check if user wants to retry
			If (msgboxRetval = 4) Then
				InitWindow
			End If
			
			'End this execution run
			Exit Sub
		End If
	End if
	
	'Get destination path from user
	destPath = InputBox("Enter destination path of share:", "Move share script")
	
	'Validate destination path
	If (destPath = "") Then
		'Display error and end it
		msgboxRetval = MsgBox("Destination path not entered or cancel was clicked!", vbOKOnly+vbCritical, "Destination path error!")
		Exit Sub
	Else
		'Check that path exists
		If Not (FSO.FolderExists(destPath)) Then
			'Display error
			msgboxRetval = MsgBox("Destination path was not found!", vbRetryCancel+vbCritical+vbDefaultButton1, "Destination path error!") 
			
			'Check if user wants to retry
			If (msgboxRetval = 4) Then
				InitWindow
			End If
			
			'End this execution run
			Exit Sub
		End If
	End If
	
	'We are all good to go, lets move
	wscript.echo MoveShare(sourcePath, destPath)
End Sub	

	
Function MoveShare(sourcePath, destPath)
	Dim retval, objWMI, objInstances, objInstance, sourceFolder, sourceName, AccessMask, AllowMaximum, Caption, Description, MaximumAllowed, Name, shareType, delRetval, objNewShare, createRetval

	'Add a \ to the destpath, making shure it will be treated as a folder
	destPath = destPath & "\"
	
	'Get the shares
	Set objWMI = GetObject("winmgmts://./root\cimv2")
	Set objInstances = objWMI.InstancesOf("Win32_Share",48)
	
	'Get the correct folder path with upper and lower cases
	Set sourceFolder = FSO.GetFolder(sourcePath)
	sourcePath = sourceFolder.Path
	sourceName = sourceFolder.Name
	
	'Get the share
	'On Error Resume Next
	For Each objInstance in objInstances
		If(objInstance.Path = sourcePath) Then
			'Store all the options
			With objInstance
				AccessMask = .AccessMask
				AllowMaximum = .AllowMaximum
				Caption = .Caption
				Description = .Description
				MaximumAllowed = .MaximumAllowed
				Name = .Name
				shareType = .Type
			End With
			
			'Delete the share
			delRetval = objInstance.Delete()
			
			'Check that delete worked
			If Not(delRetval = 0) Then
				retval = "Error: Unable to delete share!" & vbNewLine & "Error code: " & delRetval
				MoveShare = retval
				Exit Function
			End If
		End If
	Next
	On Error Goto 0
	
	'Move the folder
	FSO.MoveFolder sourcePath, destPath
	
	'Setup the share in the new location
	Set objNewShare = objWMI.Get("Win32_Share") 
	createRetval = objNewShare.Create(destPath & sourceName, Name, shareType, MaximumAllowed, Description, null, null)
	
	'Check that the new share was created
	If Not(createRetval = 0) Then
		retval = "Error: New share not created!"
	End If
	
	'Return result
	MoveShare = retval
End Function