Skip to content
This repository has been archived by the owner on Dec 12, 2023. It is now read-only.

Commit

Permalink
Merge pull request #25 from gregneagle/master
Browse files Browse the repository at this point in the history
Add and integrate gurl.py for URL retrieval
  • Loading branch information
grahamgilbert committed Apr 23, 2015
2 parents bfbcafe + 99a8b04 commit de9d8f0
Show file tree
Hide file tree
Showing 6 changed files with 3,131 additions and 5,048 deletions.
4 changes: 4 additions & 0 deletions Imagr.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
objects = {

/* Begin PBXBuildFile section */
C06979C21AE83F3500914535 /* gurl.py in Resources */ = {isa = PBXBuildFile; fileRef = C06979C11AE83F3500914535 /* gurl.py */; };
FC98DA231AD6B2DC00F90405 /* com.grahamgilbert.first-boot-pkg.plist in Resources */ = {isa = PBXBuildFile; fileRef = FC98DA1F1AD6B2DC00F90405 /* com.grahamgilbert.first-boot-pkg.plist */; };
FC98DA241AD6B2DC00F90405 /* first-boot in Resources */ = {isa = PBXBuildFile; fileRef = FC98DA201AD6B2DC00F90405 /* first-boot */; };
FC98DA251AD6B2DC00F90405 /* LoginLog.app in Resources */ = {isa = PBXBuildFile; fileRef = FC98DA211AD6B2DC00F90405 /* LoginLog.app */; };
Expand Down Expand Up @@ -55,6 +56,7 @@
/* End PBXBuildFile section */

/* Begin PBXFileReference section */
C06979C11AE83F3500914535 /* gurl.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = gurl.py; sourceTree = "<group>"; };
FC98DA1F1AD6B2DC00F90405 /* com.grahamgilbert.first-boot-pkg.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "com.grahamgilbert.first-boot-pkg.plist"; sourceTree = "<group>"; };
FC98DA201AD6B2DC00F90405 /* first-boot */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = "first-boot"; sourceTree = "<group>"; };
FC98DA211AD6B2DC00F90405 /* LoginLog.app */ = {isa = PBXFileReference; lastKnownFileType = wrapper.application; path = LoginLog.app; sourceTree = "<group>"; };
Expand Down Expand Up @@ -185,6 +187,7 @@
FCEA80B51AD0101100DBA039 /* Supporting Files */ = {
isa = PBXGroup;
children = (
C06979C11AE83F3500914535 /* gurl.py */,
FC98DA1E1AD6B2DC00F90405 /* Resources */,
FCF6B30D1AD1D73E00311D3A /* Installer.icns */,
FCF6B30E1AD1D73E00311D3A /* package.icns */,
Expand Down Expand Up @@ -298,6 +301,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
C06979C21AE83F3500914535 /* gurl.py in Resources */,
FCEA81061AD018E500DBA039 /* timer.py in Resources */,
FC98DA251AD6B2DC00F90405 /* LoginLog.app in Resources */,
FCEA80F51AD018E500DBA039 /* certs_test.py in Resources */,
Expand Down
23 changes: 5 additions & 18 deletions Imagr/AppDelegate.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,28 +9,15 @@

from Foundation import *
from AppKit import *
import MainController

class AppDelegate(NSObject):
password = objc.IBOutlet()
passwordLabel = objc.IBOutlet()
loginLabel = objc.IBOutlet()
loginButton = objc.IBOutlet()
errorField = objc.IBOutlet()
mainWindow = objc.IBOutlet()

mainView = objc.IBOutlet()
loginView = objc.IBOutlet()

progressPanel = objc.IBOutlet()
progressIndicator = objc.IBOutlet()
progressText = objc.IBOutlet()
mainController = objc.IBOutlet()

def applicationDidFinishLaunching_(self, sender):
NSLog("Application did finish launching.")
if self.mainWindow:
#self.mainWindow.setCanBecomeVisibleWithoutLogin_(True)
#self.mainWindow.setLevel_(NSScreenSaverWindowLevel - 1)
self.mainWindow.center()

if self.mainController:
self.mainController.runStartupTasks()

if NSApp.respondsToSelector_('disableRelaunchOnLogin'):
NSApp.disableRelaunchOnLogin()
36 changes: 20 additions & 16 deletions Imagr/MainController.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,15 +71,19 @@ class MainController(NSObject):
restartAction = None
blessTarget = None

def awakeFromNib(self):
def runStartupTasks(self):
self.loginView.setHidden_(self)
self.progressPanel.center()
self.password.becomeFirstResponder()
self.mainWindow.center()
#self.progressPanel.center()
#self.password.becomeFirstResponder()
# Run app startup - get the images, password, volumes - anything that takes a while

self.progressText.setStringValue_("Application Starting...")
NSApp.beginSheet_modalForWindow_modalDelegate_didEndSelector_contextInfo_(
self.progressPanel, self.mainWindow, self, None, None)
self.progressIndicator.setIndeterminate_(True)
self.progressIndicator.setUsesThreadedAnimation_(True)
self.progressIndicator.startAnimation_(self)
NSThread.detachNewThreadSelector_toTarget_withObject_(self.loadData, self, None)

def loadData(self):
Expand All @@ -103,30 +107,30 @@ def loadData(self):
def loadDataComplete(self):
# end modal sheet and close the panel
NSApp.endSheet_(self.progressPanel)
self.progressPanel.orderOut_(self)
if not self.passwordHash:
self.password.setEnabled_(False)
self.loginButton.setEnabled_(False)
self.disableAllButtons(self)
self.startUpDiskText.setStringValue_(
"No Server URL has been set. Please contact your administrator.")
self.setStartupDisk_(self)
self.progressPanel.orderOut_(self)
self.loginView.setHidden_(False)
self.mainView.setHidden_(self)
return
self.mainView.setHidden_(True)
self.mainWindow.makeFirstResponder_(self.password)

@objc.IBAction
def login_(self, sender):

password_value = self.password.stringValue()
if Utils.getPasswordHash(password_value) != self.passwordHash or password_value == "":
self.errorField.setEnabled_(sender)
self.errorField.setStringValue_("Incorrect password")
else:
self.loginView.setHidden_(sender)
self.mainView.setHidden_(False)
self.chooseImagingTarget_(sender)
self.enableAllButtons_(self)
if self.passwordHash:
password_value = self.password.stringValue()
if Utils.getPasswordHash(password_value) != self.passwordHash or password_value == "":
self.errorField.setEnabled_(sender)
self.errorField.setStringValue_("Incorrect password")
else:
self.loginView.setHidden_(sender)
self.mainView.setHidden_(False)
self.chooseImagingTarget_(sender)
self.enableAllButtons_(self)

@objc.IBAction
def setStartupDisk_(self, sender):
Expand Down
164 changes: 140 additions & 24 deletions Imagr/Utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@
# Copyright (c) 2015 Graham Gilbert. All rights reserved.
#

import urllib2
#import urllib2
import hashlib
import os
import FoundationPlist
import math
#import math
import plistlib
import shutil
from SystemConfiguration import *
Expand All @@ -21,12 +21,138 @@
import tempfile
import subprocess

from gurl import Gurl

class GurlError(Exception):
pass

class HTTPError(Exception):
pass

def get_url(url, destinationpath, message=None, follow_redirects=False):
"""Gets an HTTP or HTTPS URL and stores it in
destination path. Returns a dictionary of headers, which includes
http_result_code and http_result_description.
Will raise GurlError if Gurl returns an error.
Will raise HTTPError if HTTP Result code is not 2xx or 304.
If destinationpath already exists, you can set 'onlyifnewer' to true to
indicate you only want to download the file only if it's newer on the
server.
If you set resume to True, Gurl will attempt to resume an
interrupted download."""

tempdownloadpath = destinationpath + '.download'
if os.path.exists(tempdownloadpath):
os.remove(tempdownloadpath)

options = {'url': url,
'file': tempdownloadpath,
'follow_redirects': follow_redirects,
'logging_function': NSLog}
NSLog('gurl options: %@', options)

connection = Gurl.alloc().initWithOptions_(options)
stored_percent_complete = -1
stored_bytes_received = 0
connection.start()
try:
while True:
# if we did `while not connection.isDone()` we'd miss printing
# messages and displaying percentages if we exit the loop first
connection_done = connection.isDone()
if message and connection.status and connection.status != 304:
# log always, display if verbose is 1 or more
# also display in MunkiStatus detail field
NSLog(message)
# now clear message so we don't display it again
message = None
if (str(connection.status).startswith('2')
and connection.percentComplete != -1):
if connection.percentComplete != stored_percent_complete:
# display percent done if it has changed
stored_percent_complete = connection.percentComplete
NSLog('Percent done: %@', stored_percent_complete)
elif connection.bytesReceived != stored_bytes_received:
# if we don't have percent done info, log bytes received
stored_bytes_received = connection.bytesReceived
NSLog('Bytes received: %@', stored_bytes_received)
if connection_done:
break

except (KeyboardInterrupt, SystemExit):
# safely kill the connection then re-raise
connection.cancel()
raise
except Exception, err: # too general, I know
# Let us out! ... Safely! Unexpectedly quit dialogs are annoying...
connection.cancel()
# Re-raise the error as a GurlError
raise GurlError(-1, str(err))

if connection.error != None:
# Gurl returned an error
NSLog('Download error %@: %@', connection.error.code(),
connection.error.localizedDescription())
if connection.SSLerror:
NSLog('SSL error detail: %@', str(connection.SSLerror))
NSLog('Headers: %@', str(connection.headers))
if os.path.exists(tempdownloadpath):
os.remove(tempdownloadpath)
raise GurlError(connection.error.code(),
connection.error.localizedDescription())

if connection.response != None:
NSLog('Status: %@', connection.status)
NSLog('Headers: %@', connection.headers)
if connection.redirection != []:
NSLog('Redirection: %@', connection.redirection)

temp_download_exists = os.path.isfile(tempdownloadpath)
connection.headers['http_result_code'] = str(connection.status)
description = NSHTTPURLResponse.localizedStringForStatusCode_(
connection.status)
connection.headers['http_result_description'] = description

if str(connection.status).startswith('2') and temp_download_exists:
os.rename(tempdownloadpath, destinationpath)
return connection.headers
elif connection.status == 304:
# unchanged on server
NSLog('Item is unchanged on the server.')
return connection.headers
else:
# there was an HTTP error of some sort; remove our temp download.
if os.path.exists(tempdownloadpath):
try:
os.unlink(tempdownloadpath)
except OSError:
pass
raise HTTPError(connection.status,
connection.headers.get('http_result_description',''))

def downloadFile(url):
# Migrate this to requests to actually do a bit of cert validation. This
# was lazy
data = urllib2.urlopen(url)
new_data = data.read()
return new_data
temp_file = os.path.expanduser('~/Library/temporary_data')
try:
headers = get_url(url, temp_file)
except HTTPError, err:
NSLog("HTTP Error: %@", err)
return False
except GurlError, err:
NSLog("Gurl Error: %@", err)
return False
try:
file_handle = open(temp_file)
data = file_handle.read()
file_handle.close()
except (OSError, IOError):
NSLog('Couldn\'t read %@', temp_file)
return False
try:
os.unlink(temp_file)
except (OSError, IOError):
pass
return data


def getPasswordHash(password):
return hashlib.sha512(password).hexdigest()
Expand Down Expand Up @@ -158,25 +284,15 @@ def downloadPackage(url, target, number, package_count):

def downloadChunks(url, file):
try:
req = urllib2.urlopen(url)
total_size = int(req.info().getheader('Content-Length').strip())
downloaded = 0
CHUNK = 256 * 10240
with open(file, 'wb') as fp:
while True:
chunk = req.read(CHUNK)
downloaded += len(chunk)
#print math.floor( (downloaded / total_size) * 100 )
if not chunk: break
fp.write(chunk)
except urllib2.HTTPError, e:
#print "HTTP Error:",e.code , url
headers = get_url(url, file)
except HTTPError, err:
NSLog("HTTP Error: %@", err)
return False
except urllib2.URLError, e:
#print "URL Error:",e.reason , url
except GurlError, err:
NSLog("Gurl Error: %@", err)
return False

return file
else:
return file


def copyFirstBoot(root):
Expand Down
Loading

0 comments on commit de9d8f0

Please sign in to comment.