Sign in to follow this  
Followers 0

_ShellFile() - Create an entry in the shell contextmenu when selecting an assigned filetype, includes the program icon as well.

72 posts in this topic

Posted (edited)

I created this after I developed >_ShellFolder() because I was interested in the entry displaying when an associated file was right clicked on. I was also intrigued to see how easy it would be to register a file type with my program, quite easy it appears. The UDF is a little different to what I've seen on the forum as this works with the Current User and/or All Users and an icon is created in the ContextMenu too.

The entry will pass the file name to your program via a commandline argument, so you'll have to use $CmdLine/$CmdLineRaw to access the file that was selected.

Any problems or suggestions then please post below. Thanks.

UDF:

#include-once

; #AutoIt3Wrapper_Au3Check_Parameters=-d -w 1 -w 2 -w 3 -w- 4 -w 5 -w 6 -w 7
; #INDEX# =======================================================================================================================
; Title .........: _ShellFile
; AutoIt Version : v3.2.12.1 or higher
; Language ......: English
; Description ...: Create an entry in the shell contextmenu when selecting an assigned filetype, includes the program icon as well.
; Note ..........:
; Author(s) .....: guinness
; Remarks .......:
; ===============================================================================================================================

; #INCLUDES# ====================================================================================================================
#include <Constants.au3>

; #GLOBAL VARIABLES# ============================================================================================================
; None

; #CURRENT# =====================================================================================================================
; _ShellFile_Install: Creates an entry in the 'All Users/Current Users' registry for displaying a program entry in the shell contextmenu, but only displays when selecting an assigned filetype to the program.
; _ShellFile_Uninstall: Deletes an entry in the 'All Users/Current Users' registry for displaying a program entry in the shell contextmenu.
; ===============================================================================================================================

; #INTERNAL_USE_ONLY#============================================================================================================
; None
; ===============================================================================================================================

; #FUNCTION# ====================================================================================================================
; Name ..........: _ShellFile_Install
; Description ...: Creates an entry in the 'All Users/Current Users' registry for displaying a program entry in the shell contextmenu, but only displays when selecting an assigned filetype to the program.
; Syntax ........: _ShellFile_Install($sText, $sFileType[, $sName = @ScriptName[, $sFilePath = @ScriptFullPath[, $sIconPath = @ScriptFullPath[,
;                  $iIcon = 0[, $fAllUsers = False[, $fExtended = False]]]]]])
; Parameters ....: $sText               - Text to be shown in the contextmenu.
;                  $sFileType           - Filetype to be associated with the application e.g. .autoit or autoit.
;                  $sName               - [optional] Name of the program. Default is @ScriptName.
;                  $sFilePath           - [optional] Location of the program executable. Default is @ScriptFullPath.
;                  $sIconPath           - [optional] Location of the icon e.g. program executable or dll file. Default is @ScriptFullPath.
;                  $iIcon               - [optional] Index of icon to be used. Default is 0.
;                  $fAllUsers           - [optional] Add to Current Users (False) or All Users (True) Default is False.
;                  $fExtended           - [optional] Show in the Extended contextmenu using Shift + Right click. Default is False.
; Return values .: Success - Returns True
;                  Failure - Returns False and sets @error to non-zero.
; Author ........: guinness
; Example .......: Yes
; ===============================================================================================================================
Func _ShellFile_Install($sText, $sFileType, $sName = @ScriptName, $sFilePath = @ScriptFullPath, $sIconPath = @ScriptFullPath, $iIcon = 0, $fAllUsers = False, $fExtended = False)
	Local $i64Bit = '', $sRegistryKey = ''

	If $iIcon = Default Then
		$iIcon = 0
	EndIf
	If $sFilePath = Default Then
		$sFilePath = @ScriptFullPath
	EndIf
	If $sIconPath = Default Then
		$sIconPath = @ScriptFullPath
	EndIf
	If $sName = Default Then
		$sName = @ScriptName
	EndIf
	If @OSArch = 'X64' Then
		$i64Bit = '64'
	EndIf
	If $fAllUsers Then
		$sRegistryKey = 'HKEY_LOCAL_MACHINE' & $i64Bit & '\SOFTWARE\Classes\'
	Else
		$sRegistryKey = 'HKEY_CURRENT_USER' & $i64Bit & '\SOFTWARE\Classes\'
	EndIf

	$sFileType = StringRegExpReplace($sFileType, '^\.+', '')
	$sName = StringLower(StringRegExpReplace($sName, '\.[^.\\/]*$', ''))
	If StringStripWS($sName, $STR_STRIPALL) = '' Or FileExists($sFilePath) = 0 Or StringStripWS($sFileType, $STR_STRIPALL) = '' Then
		Return SetError(1, 0, False)
	EndIf

	_ShellFile_Uninstall($sFileType, $fAllUsers)

	Local $iReturn = 0
	$iReturn += RegWrite($sRegistryKey & '.' & $sFileType, '', 'REG_SZ', $sName)
	$iReturn += RegWrite($sRegistryKey & $sName & '\DefaultIcon\', '', 'REG_SZ', $sIconPath & ',' & $iIcon)
	$iReturn += RegWrite($sRegistryKey & $sName & '\shell\open', '', 'REG_SZ', $sText)
	$iReturn += RegWrite($sRegistryKey & $sName & '\shell\open', 'Icon', 'REG_EXPAND_SZ', $sIconPath & ',' & $iIcon)
	$iReturn += RegWrite($sRegistryKey & $sName & '\shell\open\command\', '', 'REG_SZ', '"' & $sFilePath & '" "%1"')
	$iReturn += RegWrite($sRegistryKey & $sName, '', 'REG_SZ', $sText)
	$iReturn += RegWrite($sRegistryKey & $sName, 'Icon', 'REG_EXPAND_SZ', $sIconPath & ',' & $iIcon)
	$iReturn += RegWrite($sRegistryKey & $sName & '\command', '', 'REG_SZ', '"' & $sFilePath & '" "%1"')
	If $fExtended Then
		$iReturn += RegWrite($sRegistryKey & $sName, 'Extended', 'REG_SZ', '')
	EndIf
	Return $iReturn > 0
EndFunc   ;==>_ShellFile_Install

; #FUNCTION# ====================================================================================================================
; Name ..........: _ShellFile_Uninstall
; Description ...: Deletes an entry in the 'All Users/Current Users' registry for displaying a program entry in the shell contextmenu.
; Syntax ........: _ShellFile_Uninstall($sFileType[, $fAllUsers = False])
; Parameters ....: $sFileType           - Filetype to be associated with the application e.g. .autoit or autoit.
;                  $fAllUsers           - [optional] Add to Current Users (False) or All Users (True) Default is False.
; Return values .: Success - Returns True
;                  Failure - Returns False and sets @error to non-zero.
; Author ........: guinness
; Example .......: Yes
; ===============================================================================================================================
Func _ShellFile_Uninstall($sFileType, $fAllUsers = False)
	Local $i64Bit = '', $sRegistryKey = ''

	If @OSArch = 'X64' Then
		$i64Bit = '64'
	EndIf
	If $fAllUsers Then
		$sRegistryKey = 'HKEY_LOCAL_MACHINE' & $i64Bit & '\SOFTWARE\Classes\'
	Else
		$sRegistryKey = 'HKEY_CURRENT_USER' & $i64Bit & '\SOFTWARE\Classes\'
	EndIf

	$sFileType = StringRegExpReplace($sFileType, '^\.+', '')
	If StringStripWS($sFileType, $STR_STRIPALL) = '' Then
		Return SetError(1, 0, False)
	EndIf

	Local $iReturn = 0, $sName = RegRead($sRegistryKey & '.' & $sFileType, '')
	If @error Then
		Return SetError(2, 0, False)
	EndIf
	$iReturn += RegDelete($sRegistryKey & '.' & $sFileType)
	$iReturn += RegDelete($sRegistryKey & $sName)
	Return $iReturn > 0
EndFunc   ;==>_ShellFile_Uninstall
Example 1:

#include <GUIConstantsEx.au3>

#include '_ShellFile.au3'

If @Compiled = 0 Then
	Exit MsgBox($MB_SYSTEMMODAL, '@Compiled Returned 0.', 'Please compile the program before testing. Thanks.')
EndIf

_Main()

Func _Main()
	Local $sFilePath = ''
	If $CmdLine[0] > 0 Then
		$sFilePath = $CmdLine[1]
	EndIf

	Local $hGUI = GUICreate('_ShellFile() Example', 370, 110)
	GUICtrlCreateEdit(_GetFile($sFilePath), 10, 5, 350, 65) ; If a file was passed via commandline then random text will appear in the GUICtrlCreateEdit().

	Local $iAdd = GUICtrlCreateButton('Add FileType', 10, 80, 75, 25)
	Local $iRemove = GUICtrlCreateButton('Remove FileType', 90, 80, 95, 25)

	GUISetState(@SW_SHOW, $hGUI)

	While 1
		Switch GUIGetMsg()
			Case $GUI_EVENT_CLOSE
				ExitLoop

			Case $iAdd
				_ShellFile_Install('Open with _ShellFile()', 'autoit') ; Add the running EXE to the Shell ContextMenu.
				If @error Then
					MsgBox($MB_SYSTEMMODAL, 'Association NOT Created.', '".autoit" was not associated due to an error occurring.')
				Else
					MsgBox($MB_SYSTEMMODAL, 'Association Created.', '"RandomFile.autoit" file was created to show that the filetype ".autoit" has been associtated with ' & @ScriptName & '.' & _
							@CRLF & @CRLF & 'If you restart the computer you''ll see the icon of "RandomFile.autoit" is the same as the program icon.' & _
							@CRLF & @CRLF & 'Now close the program and double/right click on "RandomFile.autoit" to display the random text in the edit box.')
				EndIf
				_SetFile(_RandomText(5000), @ScriptDir & '\RandomFile.autoit', 1) ; Create a file with Random text.

			Case $iRemove
				_ShellFile_Uninstall('autoit') ; Remove the running EXE from the Shell ContextMenu.
				If @error Then
					MsgBox($MB_SYSTEMMODAL, 'Association NOT Deleted.', '".autoit" was not deleted from the Registry due to an error occurring.')
				Else
					MsgBox($MB_SYSTEMMODAL, 'Association Deleted.', '".autoit" was successfully deleted from the Registry and is no longer associated with ' & @ScriptName & '.')
				EndIf

		EndSwitch
	WEnd
	GUIDelete($hGUI)
EndFunc   ;==>_Main

Func _GetFile($sFilePath, $sFormat = 0)
	Local $hFileOpen = FileOpen($sFilePath, $sFormat)
	If $hFileOpen = -1 Then
		Return SetError(1, 0, 'No File Was Passed Via Commandline.')
	EndIf
	Local $sData = FileRead($hFileOpen)
	FileClose($hFileOpen)
	Return $sData
EndFunc   ;==>_GetFile

Func _RandomText($iLength = 7)
	Local $iCount = 0, $iCRLF, $sData = '', $sRandom
	For $i = 1 To $iLength
		$sRandom = Random(55, 116, 1)
		If $iCount = 100 Then
			$iCRLF = @CRLF
			$iCount = 0
		EndIf
		$sData &= Chr($sRandom + 6 * ($sRandom > 90) - 7 * ($sRandom < 65)) & $iCRLF
		$iCount += 1
		$iCRLF = ''
	Next
	Return $sData
EndFunc   ;==>_RandomText

Func _SetFile($sString, $sFilePath, $iOverwrite = 0)
	Local $hFileOpen = FileOpen($sFilePath, $iOverwrite + 1)
	FileWrite($hFileOpen, $sString)
	FileClose($hFileOpen)
	If @error Then
		Return SetError(1, 0, $sString)
	EndIf
	Return $sString
EndFunc   ;==>_SetFile
All of the above has been included in a ZIP file. ShellFile.zip Edited by guinness
n1maS and Luigi like this

Share this post


Link to post
Share on other sites



Posted

Changelog:

UPDATED: x64 bit support and structure of code.

Share this post


Link to post
Share on other sites

Posted (edited)

Hi,

This UDF is really interesting for a project I have right now.

Is it possible to assign a context menu to not only one $sFileType but to all files ?

Also, your script seems to replace all contaxt menu for this filetype. Is there a way to not replace, but add one context menu entry ?

Edited by kiboost

Share this post


Link to post
Share on other sites

Posted

Look at and due to the nature of windows you can only assign one filetype to an application.

Share this post


Link to post
Share on other sites

Posted

I've updated the UDF by improving the syntax and suppressing bugs.

Share this post


Link to post
Share on other sites

Posted

guinness, can you post an example in how to pass the files selected in the context menu to the main script, with and without ShellFolder?

Share this post


Link to post
Share on other sites

Posted

I'm not following. This UDF is _ShellFile not _ShellFolder.

Share this post


Link to post
Share on other sites

Posted

I'm not following. This UDF is _ShellFile not _ShellFolder.

My bad.

But either way. How can i retrieve the files/folders paths and pass them to the script?

Can you show me an example without using your script?

Because i started using your script but i don't like to use UDFs much. Can you teach me how to pass the selected context menu files/folders paths and differenciate them in my own script so i can attribute some actions to the selected files/folders?

Share this post


Link to post
Share on other sites

Posted

Unfortunately I can't help you as I would be duplicating the same work as I did above and since you don't like UDFs (for whatever reason) it would be a waste of my time.

Share this post


Link to post
Share on other sites

Posted

hello.

how I can create two items in context menu in one submenu.

e.g I have program, want to add 2 items in context menu: convert to Georgian and convert to English, but I want to place these items in one submenu called convert to.

how I can do it with this udf?

Share this post


Link to post
Share on other sites

Posted

Unfortunately I can't help you as I would be duplicating the same work as I did above and since you don't like UDFs (for whatever reason) it would be a waste of my time.

I just like to have all coding inside the same script. And the other main reason for not using this is i can't seem to find a way to do a submenu in context.

Could you post an option in how to assign actions to all selected files/folders? Please?

I really couldn't understand the example, i'm afraid i'm a bit noobish. :(

Share this post


Link to post
Share on other sites

Posted (edited)

hello.

how I can create two items in context menu in one submenu.

e.g I have program, want to add 2 items in context menu: convert to Georgian and convert to English, but I want to place these items in one submenu called convert to.

how I can do it with this udf?

At present this UDF is only a single entry when a supported file extension is supported. I will have a look around about adding submenus, though I will have to do a little bit of re-thinking about the parameters.

telmob,

We all start from somewhere, I didn't jump head first into AutoIt when I discovered it, instead I went through the help file and Forum creating small snippets to understand what was going on with the syntax, I had zero programming knowledge.

You might not understand the code above but did you at least compile and run the example? I could also create an independent example literally using the code above, but no matter how much effort I put in, you still have to provide at least 50% (if not more) to breaking it down and looking at the help file. I'm happy to look into adding submenus, if you're happy to learn about 'user defined functions.' Your point of having everything in one script is really going to limit you when you get deeper into AutoIt, trust me on that one.

Edited by guinness

Share this post


Link to post
Share on other sites

Posted

Oh, ok. I understand what you're saying. My context menu alone is almost 1000 lines of code :)

I did compile the example and i understood there is the option to work with multiple files. When i select multiple files/folders, several windows open in your example, which is simillar to what i want, but i was lost in the code. I guess it was a bit advanced for me to understand and replicate.

You think a submenu would be viable to you?

My only problem is dealing with the files chosen with the submenu. I have no idea how to use them...

Share this post


Link to post
Share on other sites

Posted

This doesn't work with multiple files, that's the downside to using this registry key I'm afraid. If you select multiple files it will open multiple instances of the application. Search the Forum, I have provided an option of using WM_COPYDATA and _ShellFile/_ShellAll.

In terms of adding sub-menus I will have to look into it, but don't expect anything in the next week or so.

Share this post


Link to post
Share on other sites

Posted (edited)

Read this >>

OR

This >>

Edited by guinness

Share this post


Link to post
Share on other sites

Posted

Thank you for this Guinness.

From what i understand, there is still no 'clean' solution to get the paths to all files.

I'm trying JackDinn's script, maybe its enough for me.

Again... thank you so much! ;)

Share this post


Link to post
Share on other sites

Posted

I would advise against using that option due certain characteristics which limit the script. I have an idea which I might put into code today. It will use the AutoIt Hidden window, which I have discussed many many times before.

Share this post


Link to post
Share on other sites

Posted (edited)

Come to think of it, your WM_COPYDATA example is almost perfect, but there's a limitation when selecting folders. The last folder does not show its path. For example, if i select two folders, the first folder path is shown, the second is not. If i select three folders, the first and second folder path is shown, the third is not... :)

Oh, i'm in Win7 x64

Edit: Tested it again. Sometimes i get the problem, sometimes i don't. Gonna try it with a more practical example and will post the results. ;)

Edit again..: Why doesn't this work?

Func _Optimise()
Return MsgBox(4096, '1st Instance: ' & @AutoItPID, _WM_COPYDATA_GetData()) ; Get the WM_COPYDATA file.
Return IniWriteSection(@ScriptDir & "\ini.ini", "General", @AutoItPID, _WM_COPYDATA_GetData())
EndFunc   ;==>_Optimise

It doesn't write to ini file!?

Edited by telmob

Share this post


Link to post
Share on other sites

Posted

Because the first return is blocking the second return.

Share this post


Link to post
Share on other sites

Posted (edited)

Nice work guinness! :thumbsup:

very nice. you can even add a few more things like 'Position', 'HasLUAShield', .....

Edited by n1maS

Share this post


Link to post
Share on other sites

Posted

Nice work guinness! :thumbsup:

very nice. you can even add a few more things like 'Position', 'HasLUAShield', .....

I will have a look. Thanks.

Share this post


Link to post
Share on other sites

Posted (edited)

Because the first return is blocking the second return.

It was wrong, sorry.

But the paths are badly formated and it doesn't display all paths but one path only.

C:UsersTelmoDesktopSRPENF~1SRPEFI~1.EXE

I'm very surprised there's no clean solution to perform this task. Don't get me wrong, i really apreciate your help on this and your script is very good.

Edited by telmob

Share this post


Link to post
Share on other sites

Posted (edited)

As I said a couple of posts before, I will work on a solution that uses the AutoIt Hidden window. In the mean time I suggest reading tutorials around the Forum.

Edit: Right now I'm working on a full-proof 'trial period' function and having some difficulty working out the best approach. Once complete I will probably upload to the Forum.

Edited by guinness

Share this post


Link to post
Share on other sites

Posted

Thanks guinness, you're the man! ;)

Share this post


Link to post
Share on other sites

Posted (edited)

You will need _ShellAll, NOT _ShellFile and WM_COPYDATA. Look at the code and read the associated comments.

#NoTrayIcon
#include <Array.au3>
#include <GUIConstantsEx.au3>

#include '_ShellAll.au3' ; Download From: http://www.autoitscript.com/forum/topic/133942-shellall-create-an-entry-in-the-shell-contextmenu-when-selecting-a-file-and-folder-includes-the-program-icon-as-well/#entry933527
#include 'WM_COPYDATA.au3' ; http://www.autoitscript.com/forum/topic/119502-solved-wm-copydata-x64-issue

If @Compiled Then
	Example()
EndIf

Func Example()
	Local $iFileSent = _WM_COPYDATA_Initial('UniqueIDString', Default) ; Start the communication process.
	If @error Then
		If Not _WM_COPYDATA_SendStart(StringReplace(StringStripWS($CmdLineRaw, 3), '"', '')) Then ; Send $CmdLineRaw if there is process already running.
			MsgBox(4096, '2nd Instance: ' & @error, 'Seems there was an @error, but more than likely $CmdLineRaw was blank.' & @CRLF & @CRLF & $CmdLineRaw)
		EndIf
		Exit
	EndIf

	Local $iPID = Run('notepad.exe', @ScriptDir, @SW_SHOW)
	Local $hWnd = WinWait('[CLASS:Notepad]', '', 5)
	If Not $hWnd Then
		Return False
	EndIf
	WinSetTitle($hWnd, '', 'Example by guinness ' & @YEAR)

	Local $aFileList = 0, $sFileList = ''
	If $CmdLine[0] Then
		For $i = 1 To $CmdLine[0]
			$sFileList &= $CmdLine[$i] & @CRLF
		Next
		ControlSetText($hWnd, '', 'Edit1', $sFileList)
		$sFileList = ''
	EndIf

	_ShellAll_Install('Start Example') ; Add the running EXE to the Shell ContextMenu. This will only display when selecting a file and folder.

	While ProcessExists($iPID)
		Switch GUIGetMsg()
			Case $iFileSent ; If the WM_COPYDATA message is interecepted then add the data sent to notepad.
				$aFileList = _WM_COPYDATA_SendShutdown(True)
				If Not @error Then
					For $i = 1 To $aFileList[0]
						$sFileList &= $aFileList[$i] & @CRLF
					Next
					ControlSetText($hWnd, '', 'Edit1', ControlGetText($hWnd, '', 'Edit1') & $sFileList)
					_ArrayDisplay($aFileList)
				EndIf
				$aFileList = 0
				$sFileList = ''

		EndSwitch
	WEnd

	_ShellAll_Uninstall() ; Remove the running EXE from the Shell ContextMenu.
	_WM_COPYDATA_Shutdown()

	Return True
EndFunc   ;==>Example

Func _WM_COPYDATA_Initial($sIDString, $hGUI, $fCheckOnly = Default)
	_WM_COPYDATA_SetID($sIDString)
	Local Const $vReturn = _WM_COPYDATA_Start($hGUI, $fCheckOnly)
	Local Const $iError = @error
	Local $hWnd = WinGetHandle('[REGEXPTITLE:(?m)^' & _WM_COPYDATA_GetID() & '$]')
	$hWnd = HWnd(ControlGetText($hWnd, '', ControlGetHandle($hWnd, '', 'Edit1')))
	_WM_COPYDATA_SetGUI($hWnd)
	WinSetTitle($hWnd, '', 'START_PROCESS')
	Return SetError($iError, 0, $vReturn)
EndFunc   ;==>_WM_COPYDATA_Initial

Func _WM_COPYDATA_SendShutdown($fReturnArray = Default)
	If StringStripWS(_WM_COPYDATA_GetData(), 8) <> 'START_PROCESS' Then
		Return SetError(1, 0, '')
	EndIf

	Local $aReturn[1] = [0], $aWinList = 0, $sData = '', $sReturn = ''
	Do
		Sleep(50)
		$aWinList = WinList('[REGEXPTITLE:' & _WM_COPYDATA_GetID() & '__CHILD__' & '\d+]')
		If @error Then
			ExitLoop
		EndIf
		If $aWinList[0][0] Then
			ReDim $aReturn[($aReturn[0] + 1) + $aWinList[0][0]]
			For $i = 1 To $aWinList[0][0]
				$sData = StringStripWS(ControlGetText($aWinList[$i][1], '', ControlGetHandle($aWinList[$i][1], '', 'Edit1')), 3)
				If $sData Then
					$aReturn[0] += 1
					$aReturn[$aReturn[0]] = $sData
					$sReturn &= $sData & @CRLF
				EndIf
				ControlSetText($aWinList[$i][1], '', ControlGetHandle($aWinList[$i][1], '', 'Edit1'), '')
			Next
		EndIf
	Until $aWinList[0][0] == 0

	WinSetTitle(_WM_COPYDATA_GetGUI(), '', 'START_PROCESS')
	If $fReturnArray Then
		ReDim $aReturn[($aReturn[0] + 1)]
		Return SetError($sReturn == '', 0, $aReturn)
	EndIf
	$aReturn = 0
	Return SetError($sReturn == '', 0, $sReturn)
EndFunc   ;==>_WM_COPYDATA_SendShutdown

Func _WM_COPYDATA_SendStart($sData)
	Local Const $sTitle = _WM_COPYDATA_GetID() & '__CHILD__' & @AutoItPID
	AutoItWinSetTitle($sTitle)
	Local Const $hWait = WinGetHandle($sTitle)
	If @error Then
		Return SetError(1, 0, False)
	EndIf
	ControlSetText($hWait, '', ControlGetHandle($hWait, '', 'Edit1'), $sData)

	If WinGetTitle(_WM_COPYDATA_GetGUI()) = 'START_PROCESS' Then
		If _WM_COPYDATA_Send('START_PROCESS') Then
			WinSetTitle(_WM_COPYDATA_GetGUI(), '', 'IN_PROCESS')
		EndIf
	EndIf

	While 1
		If StringStripWS(ControlGetText($hWait, '', ControlGetHandle($hWait, '', 'Edit1')), 8) == '' Then
			ExitLoop
		EndIf
		Sleep(250)
	WEnd
	Return True
EndFunc   ;==>_WM_COPYDATA_SendStart
Edited by guinness

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now
Sign in to follow this  
Followers 0