Sparkle Appcast <Enclosure Line changed?

I have been using Sparkle Appcast APP since initially released by Shane for enhanced apps.

Just last week I dragged and dropped to no result. I converted the app so I could test and see what’s going on and it worked with very little changes. I added choose file & changed the handler to on run instead of on open.

I post the XML and Zip file to the server address I have always used. The app gets an ‘An error occurred in retrieving update information. Please try again later.’ error when I check for updates.

I have compared setup of the app and all is identical to previous.
There is one file that is different now…the XML.

The enclosure line now is different than previous versions. The new version

<enclosure sparkle:version="37" length="4759516" sparkle:shortVersionString="1.0" type="application/octet-stream" sparkle:dsaSignature="my signature"></enclosure>

The old version:
<enclosure sparkle:version="8235" sparkle:shortVersionString="2.0" length="7860333" url="https:/mysite.com/swupdates/myapp.zip" type="application/octet-stream" sparkle:dsaSignature="my signature"></enclosure>

Suggestions on how I could resolve this issue is appreciated.

I was stepping through the code and saw in script debugger in the following line URL was blue:

theEnclosure's setAttributesWithDictionary:{URL:appcastZipLink, |sparkle:version|:buildVersion, |sparkle:shortVersionString|:shortVersion, |length|:sizeString, |type|:"application/octet-stream", |sparkle:dsaSignature|:theSignature}

I edited it to enclose URL with pipes and this resolved my issue. But I have no idea why.

theEnclosure's setAttributesWithDictionary:{|URL|:appcastZipLink, |sparkle:version|:buildVersion, |sparkle:shortVersionString|:shortVersion, |length|:sizeString, |type|:"application/octet-stream", |sparkle:dsaSignature|:theSignature}

I am running Mac OS version 13.1. Did URL somehow make it into the AppleScript’s keywords in a recent update?

No, it’s been part of Standard Additions since almost forever.

Thanks. Not sure why it worked before and not now in 13.1. Have you updated the app? Does it work for you?

I haven’t had cause to use it for a long time, sorry.

I upgraded Shane’s Appcast example to fix the issue with URL, used his PrefsStorageLib & Dialog Toolkit Plus to maintain a change log and enter them. If anyone wants or wants to offer feedback on making it better:

--	Created by: Shane Stanley
--	Created on: 22/3/18
--  Modified by: Dean Marshall 1/19/2023

use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use framework "AppKit"
use script "Dialog Toolkit Plus" ---version "1.1.0"
use script "PrefsStorageLib"
use scripting additions

-- classes, constants, and enums used
property NSCharacterSet : a reference to current application's NSCharacterSet
property NSURL : a reference to current application's NSURL
property NSWorkspace : a reference to current application's NSWorkspace
property NSXMLTextKind : a reference to 7
property NSXMLNodeIsCDATA : a reference to 1
property NSNumberFormatterNoStyle : a reference to 0
property NSXMLNodePrettyPrint : a reference to 131072
property NSFileManager : a reference to current application's NSFileManager
property |NSURL| : a reference to current application's |NSURL|
property NSDateFormatter : a reference to current application's NSDateFormatter
property NSTimeZone : a reference to current application's NSTimeZone
property NSXMLNode : a reference to current application's NSXMLNode
property NSXMLDocument : a reference to current application's NSXMLDocument
property NSNumberFormatter : a reference to current application's NSNumberFormatter
property NSURLFileSizeKey : a reference to current application's NSURLFileSizeKey
property NSBundle : a reference to current application's NSBundle
property NSString : a reference to current application's NSString
property NSDate : a reference to current application's NSDate

on run
	----set oneFile to choose file
	set diaResponse to dialogAppcast("Create Appcast File")
	if item 1 of diaResponse is not "" then
		set logString to item 1 of diaResponse as string
		set appPosixPathAS to item 4 of diaResponse as string
	else
		return
	end if
	---set appPosixPathAS to POSIX path of oneFile
	set appPosixPath to NSString's stringWithString:appPosixPathAS
	set destFolder to appPosixPath's stringByDeletingLastPathComponent() -- where to place new file; same folder as applet
	
	-- get Info.plist details from applet 
	set theNSBundle to NSBundle's bundleWithPath:appPosixPath
	set theNSDictionary to theNSBundle's infoDictionary()
	set appName to theNSDictionary's objectForKey:"CFBundleName"
	set shortVersion to theNSDictionary's objectForKey:"CFBundleShortVersionString"
	set buildVersion to theNSDictionary's objectForKey:"CFBundleVersion"
	set appcastLink to theNSDictionary's objectForKey:"SUFeedURL"
	set shortDate to short date string of (current date)
	set prettyVersion to ("Version " & shortVersion & "." & buildVersion) as string
	set appBundleIdentifier to theNSDictionary's objectForKey:"CFBundleIdentifier"
	set appDomain to appBundleIdentifier as string
	prepare storage for domain appDomain
	set prefsChangeLog to value for key "App_Update"
	
	set templist to my explode(appDomain, ".")
	set appShortName to (item (length of templist) of templist) as string
	set dateNow to current date
	set prettyDate to date string of dateNow
	
	if prefsChangeLog = missing value then
		set prefsChangeLog to ""
	end if
	
	set changeParas to paragraphs of logString
	set changeLog to ""
	
	repeat with x from 1 to (length of changeParas)
		set aLine to item x of changeParas
		log aLine
		if aLine is not "" then
			set theLine to "<li>" & aLine & "</li>"
			set changeLog to (changeLog & theLine & return) as string
		end if
	end repeat
	
	if appcastLink = missing value then error "The application does not have an entry for SUFeedURL in its Info.plist file."
	
	-- choose DSA key file
	using terms from scripting additions
		--set privateDSAPathAS to POSIX path of (choose file with prompt "Choose your private DSA key file (dsa_priv.pem):" of type {"pem"})
		set privateDSAPathAS to POSIX path of "somepath/dsa_priv.pem" ---hard code yourprivateDSAPath here
	end using terms from
	
	-- build local path for .zip
	set zipName to appPosixPath's lastPathComponent()'s stringByDeletingPathExtension()'s stringByAppendingPathExtension:"zip"
	set zipDestPath to appPosixPath's stringByDeletingLastPathComponent()'s stringByAppendingPathComponent:zipName
	
	-- remove any old version
	set theNSFileManager to NSFileManager's defaultManager()
	theNSFileManager's removeItemAtPath:zipDestPath |error|:(missing value)
	
	-- make zip file and sign it
	using terms from scripting additions
		do shell script "ditto -c -k --keepParent " & quoted form of appPosixPathAS & space & quoted form of (zipDestPath as text)
		set theSignature to do shell script "openssl dgst -sha1 -binary < " & (quoted form of (zipDestPath as text)) & " | openssl dgst -dss1 -sign " & (quoted form of privateDSAPathAS) & "| openssl enc -base64"
	end using terms from
	-- get zip size
	set zipNSURL to NSURL's fileURLWithPath:zipDestPath
	set {theResult, theSize} to zipNSURL's getResourceValue:(reference) forKey:(NSURLFileSizeKey) |error|:(missing value)
	set sizeString to NSNumberFormatter's localizedStringFromNumber:theSize numberStyle:NSNumberFormatterNoStyle
	
	-- build appcast link to app .zip 
	set appcastName to appcastLink's lastPathComponent()
	set appcastDir to appcastLink's substringToIndex:((appcastLink's |length|()) - (appcastName's |length|()))
	set appcastZipLink to appcastDir's stringByAppendingPathComponent:(zipName's stringByAddingPercentEncodingWithAllowedCharacters:(NSCharacterSet's URLQueryAllowedCharacterSet()))
	set appcastDestPath to appPosixPath's stringByDeletingLastPathComponent()'s stringByAppendingPathComponent:appcastName
	
	-- make xml
	set rootElement to NSXMLNode's elementWithName:"rss"
	rootElement's setAttributesWithDictionary:{|version|:"2.0", |xmlns:sparkle|:"http://www.andymatuschak.org/xml-namespaces/sparkle", |xmlns:dc|:"http://purl.org/dc/elements/1.1/"}
	set theXMLDocument to NSXMLDocument's alloc()'s initWithRootElement:rootElement
	theXMLDocument's setCharacterEncoding:"UTF-8"
	theXMLDocument's setVersion:"1.0"
	set theChanel to NSXMLNode's elementWithName:"channel"
	rootElement's addChild:theChanel
	theChanel's addChild:(NSXMLNode's elementWithName:"title" stringValue:(appName's stringByAppendingString:" Changelog"))
	theChanel's addChild:(NSXMLNode's elementWithName:"description" stringValue:"Update")
	theChanel's addChild:(NSXMLNode's elementWithName:"link" stringValue:appcastLink)
	theChanel's addChild:(NSXMLNode's elementWithName:"language" stringValue:"en")
	set theItem to NSXMLNode's elementWithName:"item"
	theChanel's addChild:theItem
	theItem's addChild:(NSXMLNode's elementWithName:"title" stringValue:("Version " & shortVersion))
	theItem's addChild:(NSXMLNode's elementWithName:"pubDate" stringValue:(my dateString()))
	set theEnclosure to NSXMLNode's elementWithName:"enclosure"
	theEnclosure's setAttributesWithDictionary:{|url|:appcastZipLink, |sparkle:version|:buildVersion, |sparkle:shortVersionString|:shortVersion, |length|:sizeString, |type|:"application/octet-stream", |sparkle:dsaSignature|:theSignature}
	
	theItem's addChild:theEnclosure
	set theDesc to NSXMLNode's elementWithName:"description"
	set cdataNode to NSXMLNode's alloc()'s initWithKind:NSXMLTextKind options:NSXMLNodeIsCDATA
	
	set updatedChangeLog to "<p><strong>" & prettyVersion & " Bug Fixes and New Features</strong></p>
						<ul>
							" & changeLog & "
						</ul>
						"
	cdataNode's setStringValue:("
				<div class=\"meta\"><i class=\"far fa-clock\"></i>Last updated: " & prettyDate & "</div>
					" & updatedChangeLog & "
					<hr style=\"border: 1px dashed black;\" />
		  			<p><strong>Previous Updates</strong></p>
					<ul>
						" & prefsChangeLog & "
					</ul>
								")
	
	theDesc's addChild:cdataNode
	theItem's addChild:theDesc
	
	-- write appcast file
	set theData to theXMLDocument's XMLDataWithOptions:NSXMLNodePrettyPrint
	theData's writeToFile:appcastDestPath atomically:true
	
	---add to apps changeLogHistory
	
	set prefsUpdateChangeLog to (updatedChangeLog & return & prefsChangeLog) as string
	assign value prefsUpdateChangeLog to key "App_Update"
	--assign value "" to key "App_Update"
	
	-- show the files in the Finder
	NSWorkspace's sharedWorkspace()'s activateFileViewerSelectingURLs:{NSURL's fileURLWithPath:appcastDestPath, zipNSURL}
	--current application's NSBeep()
	(current application's NSSound's soundNamed:"Glass")'s play() ---because I like this better!
	
end run



on dateString()
	set theNSDateFormatter to NSDateFormatter's new()
	theNSDateFormatter's setDateFormat:"EEE, dd MMM yyy HH:mm:ss Z"
	theNSDateFormatter's setTimeZone:(NSTimeZone's timeZoneForSecondsFromGMT:0)
	return theNSDateFormatter's stringFromDate:(NSDate's |date|())
end dateString

--Sub-routine to explode a string into a list
on explode(theText, theDelim)
	set AppleScript's text item delimiters to theDelim
	set theList to text items of theText
	set AppleScript's text item delimiters to ""
	return theList
end explode

on dialogAppcast(theApp)
	set accViewWidth to 400
	set {theButtons, minWidth} to create buttons {"Cancel", "OK"} default button 2
	if minWidth > accViewWidth then set accViewWidth to minWidth -- make sure buttons fit
	set {instructionsField, instructionsLabel, theTop} to create top labeled field "" placeholder text "Enter your change log here" bottom 0 field width accViewWidth extra height 60 label text "Instructions" with accepts linebreak and tab
	set {theRule, theTop} to create rule (theTop + 12) rule width accViewWidth
	set {thePathControl, pathLabel, theTop} to create labeled path control (POSIX path of (path to documents folder)) bottom (theTop + 12) control width accViewWidth label text "Choose or drag the file here:"
	set allControls to {instructionsField, instructionsLabel, theRule, thePathControl, pathLabel}
	set {buttonName, controlsResults} to display enhanced window theApp acc view width accViewWidth acc view height theTop acc view controls allControls buttons theButtons active field instructionsField with align cancel button
	return controlsResults
end dialogAppcast

The problem is that you have used:

use scripting additions

If you use this instead:

on run
	using terms from scripting additions
		set oneFile to choose file with prompt "Choose your App file:" of type {"app"}
	end using terms from
	…

then Shane’s script works as before.

Makes sense but the original reason it failed doesn’t because I added that afterwards when I updated the applet. It did not have the line but fails to run on OS 13.1. What OS are you running? This is the original code that fails.

--
--	Created by: Shane Stanley
--	Created on: 22/3/18
--

use AppleScript version "2.4" -- Yosemite (10.10) or later
use framework "Foundation"
use framework "AppKit"

-- classes, constants, and enums used
property NSCharacterSet : a reference to current application's NSCharacterSet
property NSURL : a reference to current application's NSURL
property NSWorkspace : a reference to current application's NSWorkspace
property NSXMLTextKind : a reference to 7
property NSXMLNodeIsCDATA : a reference to 1
property NSNumberFormatterNoStyle : a reference to 0
property NSXMLNodePrettyPrint : a reference to 131072
property NSFileManager : a reference to current application's NSFileManager
property |NSURL| : a reference to current application's |NSURL|
property NSDateFormatter : a reference to current application's NSDateFormatter
property NSTimeZone : a reference to current application's NSTimeZone
property NSXMLNode : a reference to current application's NSXMLNode
property NSXMLDocument : a reference to current application's NSXMLDocument
property NSNumberFormatter : a reference to current application's NSNumberFormatter
property NSURLFileSizeKey : a reference to current application's NSURLFileSizeKey
property NSBundle : a reference to current application's NSBundle
property NSString : a reference to current application's NSString
property NSDate : a reference to current application's NSDate

on open {oneFile}
	set appPosixPathAS to POSIX path of oneFile
	set appPosixPath to NSString's stringWithString:appPosixPathAS
	set destFolder to appPosixPath's stringByDeletingLastPathComponent() -- where to place new file; same folder as applet
	
	-- get Info.plist details from applet 
	set theNSBundle to NSBundle's bundleWithPath:appPosixPath
	set theNSDictionary to theNSBundle's infoDictionary()
	set appName to theNSDictionary's objectForKey:"CFBundleName"
	set shortVersion to theNSDictionary's objectForKey:"CFBundleShortVersionString"
	set buildVersion to theNSDictionary's objectForKey:"CFBundleVersion"
	set appcastLink to theNSDictionary's objectForKey:"SUFeedURL"
	if appcastLink = missing value then error "The application does not have an entry for SUFeedURL in its Info.plist file."
	
	-- choose DSA key file
	using terms from scripting additions
		set privateDSAPathAS to POSIX path of (choose file with prompt "Choose your private DSA key file (dsa_priv.pem):" of type {"pem"})
	end using terms from
	
	-- build local path for .zip
	set zipName to appPosixPath's lastPathComponent()'s stringByDeletingPathExtension()'s stringByAppendingPathExtension:"zip"
	set zipDestPath to appPosixPath's stringByDeletingLastPathComponent()'s stringByAppendingPathComponent:zipName
	
	-- remove any old version
	set theNSFileManager to NSFileManager's defaultManager()
	theNSFileManager's removeItemAtPath:zipDestPath |error|:(missing value)
	
	-- make zip file and sign it
	using terms from scripting additions
		do shell script "ditto -c -k --keepParent " & quoted form of appPosixPathAS & space & quoted form of (zipDestPath as text)
		set theSignature to do shell script "openssl dgst -sha1 -binary < " & (quoted form of (zipDestPath as text)) & " | openssl dgst -dss1 -sign " & (quoted form of privateDSAPathAS) & "| openssl enc -base64"
	end using terms from
	-- get zip size
	set zipNSURL to NSURL's fileURLWithPath:zipDestPath
	set {theResult, theSize} to zipNSURL's getResourceValue:(reference) forKey:(NSURLFileSizeKey) |error|:(missing value)
	set sizeString to NSNumberFormatter's localizedStringFromNumber:theSize numberStyle:NSNumberFormatterNoStyle
	
	-- build appcast link to app .zip 
	set appcastName to appcastLink's lastPathComponent()
	set appcastDir to appcastLink's substringToIndex:((appcastLink's |length|()) - (appcastName's |length|()))
	set appcastZipLink to appcastDir's stringByAppendingPathComponent:(zipName's stringByAddingPercentEncodingWithAllowedCharacters:(NSCharacterSet's URLQueryAllowedCharacterSet()))
	set appcastDestPath to appPosixPath's stringByDeletingLastPathComponent()'s stringByAppendingPathComponent:appcastName
	
	-- make xml
	set rootElement to NSXMLNode's elementWithName:"rss"
	rootElement's setAttributesWithDictionary:{|version|:"2.0", |xmlns:sparkle|:"http://www.andymatuschak.org/xml-namespaces/sparkle", |xmlns:dc|:"http://purl.org/dc/elements/1.1/"}
	set theXMLDocument to NSXMLDocument's alloc()'s initWithRootElement:rootElement
	theXMLDocument's setCharacterEncoding:"UTF-8"
	theXMLDocument's setVersion:"1.0"
	set theChanel to NSXMLNode's elementWithName:"channel"
	rootElement's addChild:theChanel
	theChanel's addChild:(NSXMLNode's elementWithName:"title" stringValue:(appName's stringByAppendingString:" Changelog"))
	theChanel's addChild:(NSXMLNode's elementWithName:"description" stringValue:"Update")
	theChanel's addChild:(NSXMLNode's elementWithName:"link" stringValue:appcastLink)
	theChanel's addChild:(NSXMLNode's elementWithName:"language" stringValue:"en")
	set theItem to NSXMLNode's elementWithName:"item"
	theChanel's addChild:theItem
	theItem's addChild:(NSXMLNode's elementWithName:"title" stringValue:("Version " & shortVersion))
	theItem's addChild:(NSXMLNode's elementWithName:"pubDate" stringValue:(my dateString()))
	set theEnclosure to NSXMLNode's elementWithName:"enclosure"
	theEnclosure's setAttributesWithDictionary:{url:appcastZipLink, |sparkle:version|:buildVersion, |sparkle:shortVersionString|:shortVersion, |length|:sizeString, |type|:"application/octet-stream", |sparkle:dsaSignature|:theSignature}
	theItem's addChild:theEnclosure
	set theDesc to NSXMLNode's elementWithName:"description"
	set cdataNode to NSXMLNode's alloc()'s initWithKind:NSXMLTextKind options:NSXMLNodeIsCDATA
	cdataNode's setStringValue:"
<h2>Update Info</h2>
<p>Details of update.</p>
"
	theDesc's addChild:cdataNode
	theItem's addChild:theDesc
	
	-- write appcast file
	set theData to theXMLDocument's XMLDataWithOptions:NSXMLNodePrettyPrint
	theData's writeToFile:appcastDestPath atomically:true
	
	-- show the files in the Finder
	NSWorkspace's sharedWorkspace()'s activateFileViewerSelectingURLs:{NSURL's fileURLWithPath:appcastDestPath, zipNSURL}
	current application's NSBeep()
	
end open

on dateString()
	set theNSDateFormatter to NSDateFormatter's new()
	theNSDateFormatter's setDateFormat:"EEE, dd MMM yyy HH:mm:ss Z"
	theNSDateFormatter's setTimeZone:(NSTimeZone's timeZoneForSecondsFromGMT:0)
	return theNSDateFormatter's stringFromDate:(NSDate's |date|())
end dateString

I also use macOS 13.1. Since Venture Droplets no longer seem to work in the Finder when dropping apps.

Ventura: AppleScript droplets don’t work when dropping apps on them

Therefore I changed "on open" to "on run" and it runs.

crazy…I missed that. Thank you for closing that loop!

1 Like