REBOL [ Title: "Auto-download updated files" Purpose: { This allows applications to automatically download updated files or configuration when needed. } Author: "Gabriele Santilli" EMail: giesse@rebol.it File: %autoupdate.r License: { Copyright (c) 2003-2005, Gabriele Santilli All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * The name of Gabriele Santilli may not be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. } Date: 17-Mar-2005 Version: 1.14.2 ; majorv.minorv.status ; status: 0: unfinished; 1: testing; 2: stable History: [ 12-May-2004 1.1.0 "History start" 12-May-2004 1.2.0 "First version" 12-May-2004 1.3.0 "check-and-update didn't save updated version info after updating" 12-May-2004 1.4.0 "Fixed bug in the parse rules" 12-May-2004 1.5.0 "Temporarily using READ instead of REQUEST-DOWNLOAD" 12-May-2004 1.6.0 "Now creates version-info.txt if it doesn't exist and there are not updates (to speed up next checks)" 12-May-2004 1.7.0 "Using REQUEST-DOWNLOAD again" 13-May-2004 1.8.0 "Added signature checking for remote version info" 13-May-2004 1.9.0 "Added configuration updating" 16-May-2004 1.10.0 "Added more documentation" 2-Jun-2004 1.11.0 "Fixed a bug, it wouldn't return 'NO-UPDATES if the user choose No" 21-Jun-2004 1.12.0 "Now checks the version for the configuration too, and passes it directly to MERGE-CONFIG" 21-Oct-2004 1.13.0 "Now uses the RSA-CHECK-SIGNATURE function" 17-Mar-2005 1.14.2 "Now all downloaded files are set to be executable on platforms that have the executable bit" ] ] ; comment the following line if you are using autodoc.r ;#do [document: func [text] [none]] #do [document { ===Auto-download updated files (autoupdate.r) This script provides functions to automatically download updated files or configuration (see utility.r) when needed. (Strings are localized with locale.r in this file.) It works by downloading a version-info.txt file from an URL; this file contains version information for all files that are part of the application. By checking the content of this file with the local version information, it is determined if new files need to be downloaded. The remote version-info.txt is checked against varsion-info.sig to ensure it is valid and has been created by the application's author. ---The version-info.txt file syntax The version-info is actually a REBOL block. The grammar is: [any [file! integer! any [integer! | binary! | word!]]] so basically it is made up of a series of records, each one representing version information for one file. Each record starts with a file! value, that is the file name, followed by an integer! representing the file current version. This is the number that is checked against the local version to see if the file has been updated. If the local version is less than the remote version, the file is redownloaded. Following, there are three optional values; a binary!, indicating the file's checksum; this is used to determine if the local file is corrupted, or to determine if the file needs to be updated when there is no local version information; an integer!, indicating the size of the file to download, used to present the user with an estimated download size; a word, indicating special properties of the file. This can assume one of the following possible values: :built-in - This file replaces a built-in component. (See below too.) Built-in components' version is not checked against the local version info file like for other kind of files, but rather it is checked against the version number that is built into the program itself. :as-is - The file is stored on the server as is, and will just be downloaded and saved locally as binary. :compressed - The file on the server is compressed. It will be downloaded, then decompressed and saved as binary. Use this for all files, unless they are some small that compression is not worth it. :signed - The file on the server is compressed and signed. It will be downloaded, then its signature will be checked, and if it is valied, it will be decompressed and saved as binary. :config - The file on the server is actually a list of configuration values that should be merged with the current configuration to update it. The file is downloaded, it's signature is checked and if it's valid, it is decompressed, loaded and passed to MERGE-CONFIG. Then SAVE-CONFIG is called with the filename. }] context [ check-vinfo: func [vinfo] [ parse vinfo [any [file! integer! any [integer! | binary! | word!]]] ] check-file: func [built-ins local-vinfo force updated file version size cksum type /local old-version needs-update] [ size: any [size 0] type: any [type 'as-is] old-version: select local-vinfo file needs-update: does [insert insert insert tail updated file type size] if find [built-in config] type [ old-version: either type = 'built-in [ select built-ins file ] [ get-config/accept/default 'version integer! 0 ] if any [ not old-version old-version < version ] [ needs-update ] exit ] either exists? file [ either any [ force not old-version ] [ if cksum <> checksum/secure read/binary file [ needs-update ] ] [ if old-version < version [ needs-update ] ] ] [ needs-update ] ] to-human-size: func [size /local unit] [ size: size / 1024 unit: ["KB" "MB" "GB"] ; hope we'll never need to go past this ;-) while [size > 1024] [ size: size / 1024 unit: next unit ] join (to integer! size * 10) / 10 unit/1 ] write-binary: func [file data /local p] [ if not exists? p: first split-path file [make-dir/deep p] write/binary file data if find get-modes file 'file-modes 'owner-execute [ set-modes file [owner-execute: yes] ] ] #do [document { ---CHECK-AND-UPDATE This function checks for availability of updated files. If updates are available, it asks the user if he/she wants to download them (REQUEST/CONFIRM is used to do so). If so, it downloads them (REQUEST-DOWNLOAD is used for the download). The return value is one of: 'NO-UPDATES (nothing has been updated), 'UPDATES (some files have been updated), 'BUILT-IN (at least one component marked as built-in has been updated). Built in marked components are components that replace anything that is built into the program code (such as the code itself); you will usually check for the return code, and if it is 'BUILT-IN, restart the program. The function's arguments are: :url [url!] - URL for updated files; the URL should actually point to a directory; this is the base URL where all update files should be located. :built-ins [block!] - version info for built-in components; must be a block of pairs file!, integer!, where the file! value is the name of the builtin component, and the integer! is its current version. :public-key [binary!] - the public key to check signatures with. :/force - If this refinement is specified, the local version-info.txt is ignored and the checksum for all files is verified to determine if they need to be updated; this is slower, but can be used to fix corrupted files too. :/with remote-vinfo [block!] - pass remote version info directly, so that the function does not have to download and check it. }] check-and-update: func [ "Checks for updated files and downloads them" url [url!] built-ins [block!] "Version info for built-in components" public-key [binary!] "Public key to verify signed components" /force "Force checksum check on all files (use this to repair corrupted files)" /with remote-vinfo [block!] "Do not download version info, use this instead" /local local-vinfo updated file version size cksum type total data signature k val ] [ k: rsa-load-public-key public-key if not remote-vinfo [ remote-vinfo: read/binary url/version-info.txt signature: read/binary url/version-info.sig either rsa-check-signature k remote-vinfo signature [ remote-vinfo: load to string! remote-vinfo ] [ make error! "Remote version file info signature is not valid" ] ] local-vinfo: any [attempt [load %version-info.txt] [ ]] if not check-vinfo remote-vinfo [make error! "Remote version info file is corrupted."] if not check-vinfo local-vinfo [local-vinfo: [ ]] updated: make block! 32 parse remote-vinfo [ any [ set file file! set version integer! (size: cksum: type: none) any [ set val integer! (size: val) | set val binary! (cksum: val) | set val word! (type: val) ] (check-file built-ins local-vinfo force updated file version size cksum type) ] ] either empty? updated [ if not exists? %version-info.txt [save %version-info.txt remote-vinfo] 'no-updates ] [ total: 0 foreach [file type size] updated [ total: total + size ] either request/confirm substitute #l { Updated files are available (estimated download size: ). Do you wish to download them? } [size: to-human-size total] [ foreach [file type size] updated [ data: request-download url/:file switch type [ built-in [write-binary file data] as-is [write-binary file data] compressed [write-binary file decompress data] signed [ signature: copy/part data 128 remove/part data 128 if rsa-check-signature k data signature [ write-binary file decompress data ] ] config [ signature: copy/part data 128 remove/part data 128 if rsa-check-signature k data signature [ merge-config load decompress data save-config file ] ] ] ] save %version-info.txt remote-vinfo either find updated 'built-in ['built-in] ['updates] ] ['no-updates] ] ] export [check-and-update] ]