From 4fdc5ff30c8f390088812bac6f3efe653d649356 Mon Sep 17 00:00:00 2001 From: Fabio Comuni Date: Fri, 8 Jul 2011 17:12:08 +0200 Subject: [PATCH] First test for friendika "live" update --- include/jquery.htmlstream.js | 157 ++++++++++++++++++ include/remoteupdate.php | 261 ++++++++++++++++++++++++++++++ mod/admin.php | 66 +++++++- view/admin_aside.tpl | 5 + view/admin_remoteupdate.tpl | 98 +++++++++++ view/theme/duepuntozero/style.css | 37 ++++- 6 files changed, 616 insertions(+), 8 deletions(-) create mode 100644 include/jquery.htmlstream.js create mode 100644 include/remoteupdate.php create mode 100644 view/admin_remoteupdate.tpl diff --git a/include/jquery.htmlstream.js b/include/jquery.htmlstream.js new file mode 100644 index 0000000000..c62c538f77 --- /dev/null +++ b/include/jquery.htmlstream.js @@ -0,0 +1,157 @@ +/* jQuery ajax stream plugin +* Version 0.1 +* Copyright (C) 2009 Chris Tarquini +* Licensed under a Creative Commons Attribution-Share Alike 3.0 Unported License (http://creativecommons.org/licenses/by-sa/3.0/) +* Permissions beyond the scope of this license may be available by contacting petros000[at]hotmail.com. +*/ + +(function($) { + +// Save the original AJAX function +var ajax_old = $.ajax; +var get_old = $.get; +var post_old = $.post; +var active = true; +// Add our settings +$.ajaxSetup({stream: false,pollInterval: 500/*, onDataRecieved: function(){}*/ }); +$.enableAjaxStream = function(enable) +{ +if(typeof enable == 'undefined') enable = !active; +if(!enable) +{ +$.ajax = ajax_old; +$.get = get_old; +$.post = post_old; +active = false; +} +else +{ +$.ajax = ajax_stream; +$.get = ajax_get_stream; +$.post = ajax_post_stream; +active = true; +} + +} +var ajax_stream = $.ajax = function(options) +{ +//copied from original ajax function + options = jQuery.extend(true, options, jQuery.extend(true, {}, jQuery.ajaxSettings, options)); +if(options.stream) +{ +var timer = 0; +var offset = 0; +var xmlhttp = null; +var lastlen = 0; +var done = false; +var hook = function(xhr) +{ +xmlhttp = xhr; +checkagain(); +} +var fix = function(){ check('stream'); };// fixes weird bug with random numbers as arg +var checkagain = function(){if(!done) timer = setTimeout(fix,options.pollInterval);} +var check = function(status) +{ +if(typeof status == 'undefined') status = "stream"; +if(xmlhttp.status < 3) return; //only get the latest packet if data has been sent +var text = xmlhttp.responseText; +if(status == 'stream') //if we arent streaming then just flush the buffer +{ +if(text.length <= lastlen) { checkagain(); return;} +lastlength = text.length; +if(offset == text.length) { checkagain(); return;} +} +var pkt = text.substr(offset); +offset = text.length; +if($.isFunction(options.OnDataRecieved)) +{ +options.OnDataRecieved(pkt, status, xmlhttp.responseText, xmlhttp); +} +if(xmlhttp.status != 4) +checkagain(); +} +var complete = function(xhr,s) +{ +clearTimeout(timer);//done..stop polling +done = true; +// send final call +check(s); +} +// If the complete callback is set create a new callback that calls the users and outs +if($.isFunction(options.success)) +{ +var oc = options.success; +options.success = function(xhr,s){ complete(xhr,s); oc(xhr,s);}; + +} +else options.success = complete; +// Set up our hook on the beforeSend +if($.isFunction(options.beforeSend)) +{ +var obs = options.beforeSend; +options.beforeSend = function(xhr){ obs(xhr); hook(xhr);}; +} +else options.beforeSend = hook; + +} +ajax_old(options); +} + +var ajax_get_stream = $.get = function(url,data,callback,type,stream) +{ + if($.isFunction(data)) + { + var orgcb = callback; + callback = data; + if($.isFunction(orgcb)) + { + stream = orgcb; + } + data = null; + } + if($.isFunction(type)) + { + stream = type; + type = undefined; + } + var dostream = $.isFunction(stream); + return jQuery.ajax({ + type: "GET", + url: url, + data: data, + success: callback, + dataType: type, + stream: dostream, + OnDataRecieved: stream + }); + +} + +var ajax_post_stream = $.post = function(url,data,callback,type,stream) +{ + if($.isFunction(data)) + { + var orgcb = callback; + callback = data; + } + if($.isFunction(type)) + { + stream = type; + type = undefined; + } + var dostream = $.isFunction(stream); + return jQuery.ajax({ + type: "POST", + url: url, + data: data, + success: callback, + dataType: type, + stream: dostream, + OnDataRecieved: stream + }); + +} + +})(jQuery); + diff --git a/include/remoteupdate.php b/include/remoteupdate.php new file mode 100644 index 0000000000..aa30da06f8 --- /dev/null +++ b/include/remoteupdate.php @@ -0,0 +1,261 @@ +tags as $i=>$v){ + $i = (float)$i; + if ($i>$tag) $tag=$i; + } + + if ($tag==0.0) return false; + $f = fetch_url("https://raw.github.com/".F9KREPO."/".$tag."/boot.php","r"); + preg_match("|'FRIENDIKA_VERSION', *'([^']*)'|", $f, $m); + $version = $m[1]; + + $lv = explode(".", FRIENDIKA_VERSION); + $rv = explode(".",$version); + foreach($lv as $i=>$v){ + if ((int)$lv[$i] < (int)$rv[$i]) { + return array($tag, $version, "https://github.com/friendika/friendika/zipball/".$tag); + break; + } + } + return false; +} +function canWeWrite(){ + $bd = dirname(dirname(__file__)); + return is_writable( $bd."/boot.php" ); +} + +function out($txt){ echo "§".$txt."§"; ob_end_flush(); flush();} + +function up_count($path){ + + $file_count = 0; + + $dir_handle = opendir($path); + + if (!$dir_handle) return -1; + + while ($file = readdir($dir_handle)) { + + if ($file == '.' || $file == '..') continue; + $file_count++; + + if (is_dir($path . $file)){ + $file_count += up_count($path . $file . DIRECTORY_SEPARATOR); + } + + } + + closedir($dir_handle); + + return $file_count; +} + + + +function up_unzip($file, $folder="/tmp"){ + $folder.="/"; + $zip = zip_open($file); + if ($zip) { + while ($zip_entry = zip_read($zip)) { + $zip_entry_name = zip_entry_name($zip_entry); + if (substr($zip_entry_name,strlen($zip_entry_name)-1,1)=="/"){ + mkdir($folder.$zip_entry_name,0777, true); + } else { + $fp = fopen($folder.$zip_entry_name, "w"); + if (zip_entry_open($zip, $zip_entry, "r")) { + $buf = zip_entry_read($zip_entry, zip_entry_filesize($zip_entry)); + fwrite($fp,"$buf"); + zip_entry_close($zip_entry); + fclose($fp); + } + } + } + zip_close($zip); + } +} + +/** + * Walk recoursively in a folder and call a callback function on every + * dir entry. + * args: + * $dir string base dir to walk + * $callback function callback function + * $sort int 0: ascending, 1: descending + * $cb_argv any extra value passed to callback + * + * callback signature: + * function name($fn, $dir [, $argv]) + * $fn string full dir entry name + * $dir string start dir path + * $argv any user value to callback + * + */ +function up_walktree($dir, $callback=Null, $sort=0, $cb_argv=Null , $startdir=Null){ + if (is_null($callback)) return; + if (is_null($startdir)) $startdir = $dir; + $res = scandir($dir, $sort); + foreach($res as $i=>$v){ + if ($v!="." && $v!=".."){ + $fn = $dir."/".$v; + if ($sort==0) $callback($fn, $startdir, $cb_argv); + if (is_dir($fn)) up_walktree($fn, $callback, $sort, $cb_argv, $startdir); + if ($sort==1) $callback($fn, $startdir, $cb_argv); + } + } + +} + +function up_copy($fn, $dir){ + global $up_countfiles, $up_totalfiles, $up_lastp; + $up_countfiles++; $prc=(int)(((float)$up_countfiles/(float)$up_totalfiles)*100); + + if (strpos($fn, ".gitignore")>-1 || strpos($fn, ".htaccess")>-1) return; + $ddest = dirname(dirname(__file__)); + $fd = str_replace($dir, $ddest, $fn); + + if (is_dir($fn) && !is_dir($fd)) { + $re=mkdir($fd,0777,true); + } + if (!is_dir($fn)){ + $re=copy($fn, $fd); + } + + if ($re===false) { + out("ERROR. Abort."); + killme(); + } + out("copy@Copy@$prc%"); +} + +function up_ftp($fn, $dir, $argv){ + global $up_countfiles, $up_totalfiles, $up_lastp; + $up_countfiles++; $prc=(int)(((float)$up_countfiles/(float)$up_totalfiles)*100); + + if (strpos($fn, ".gitignore")>-1 || strpos($fn, ".htaccess")>-1) return; + + list($ddest, $conn_id) = $argv; + $l = strlen($ddest)-1; + if (substr($ddest,$l,1)=="/") $ddest = substr($ddest,0,$l); + $fd = str_replace($dir, $ddest, $fn); + + if (is_dir($fn)){ + if (ftp_nlist($conn_id, $fd)===false) { + $ret = ftp_mkdir($conn_id, $fd); + } else { + $ret=true; + } + } else { + $ret = ftp_put($conn_id, $fd, $fn, FTP_BINARY); + } + if (!$ret) { + out("ERROR. Abort."); + killme(); + } + out("copy@Copy@$prc%"); +} + +function up_rm($fn, $dir){ + if (is_dir($fn)){ + rmdir($fn); + } else { + unlink($fn); + } +} + +function up_dlfile($url, $file) { + $in = fopen ($url, "r"); + $out = fopen ($file, "w"); + + $fs = filesize($url); + + + if (!$in || !$out) return false; + + $s=0; $count=0; + while (!feof ($in)) { + $line = fgets ($in, 1024); + fwrite( $out, $line); + + $count++; $s += strlen($line); + if ($count==50){ + $count=0; + $sp=$s/1024.0; $ex="Kb"; + if ($sp>1024) { $sp=$sp/1024; $ex="Mb"; } + if ($sp>1024) { $sp=$sp/1024; $ex="Gb"; } + $sp = ((int)($sp*100))/100; + out("dwl@Download@".$sp.$ex); + } + } + fclose($in); + return true; +} + +function doUpdate($remotefile, $ftpdata=false){ + global $up_totalfiles; + + + $localtmpfile = tempnam("/tmp", "fk"); + out("dwl@Download@starting..."); + $rt= up_dlfile($remotefile, $localtmpfile); + if ($rt==false || filesize($localtmpfile)==0){ + out("dwl@Download@ERROR."); + unlink($localtmpfile); + return; + } + out("dwl@Download@Ok."); + + out("unzip@Unzip@"); + $tmpdirname = $localfile."ex"; + mkdir($tmpdirname); + up_unzip($localtmpfile, $tmpdirname); + $basedir = glob($tmpdirname."/*"); $basedir=$basedir[0]; + out ("unzip@Unzip@Ok."); + + $up_totalfiles = up_count($basedir."/"); + + if (canWeWrite()){ + out("copy@Copy@"); + up_walktree($basedir, 'up_copy'); + } + if ($ftpdata!==false && is_array($ftpdata) && $ftpdata['ftphost']!="" ){ + out("ftpcon@Connect to FTP@"); + $conn_id = ftp_connect($ftpdata['ftphost']); + $login_result = ftp_login($conn_id, $ftpdata['ftpuser'], $ftpdata['ftppwd']); + + if ((!$conn_id) || (!$login_result)) { + out("ftpcon@Connect to FTP@FAILED"); + up_clean($tmpdirname, $localtmpfile); + return; + } else { + out("ftpcon@Connect to FTP@Ok."); + } + out("copy@Copy@"); + up_walktree($basedir, 'up_ftp', 0, array( $ftpdata['ftppath'], $conn_id)); + + ftp_close($conn_id); + } + + up_clean($tmpdirname, $localtmpfile); + +} + +function up_clean($tmpdirname, $localtmpfile){ + out("clean@Clean up@"); + unlink($localtmpfile); + up_walktree($tmpdirname, 'up_rm', 1); + rmdir($tmpdirname); + out("clean@Clean up@Ok."); +} diff --git a/mod/admin.php b/mod/admin.php index 203d4873f8..f7dde7bcbe 100644 --- a/mod/admin.php +++ b/mod/admin.php @@ -3,7 +3,7 @@ /** * Friendika admin */ - +require_once("include/remoteupdate.php"); function admin_init(&$a) { if(!is_site_admin()) { @@ -36,11 +36,14 @@ function admin_post(&$a){ } } goaway($a->get_baseurl() . '/admin/plugins/' . $a->argv[2] ); - return; // NOTREACHED + return; // NOTREACHED break; case 'logs': admin_page_logs_post($a); break; + case 'update': + admin_page_remoteupdate_post($a); + break; } } @@ -62,7 +65,8 @@ function admin_content(&$a) { $aside = Array( 'site' => Array($a->get_baseurl()."/admin/site/", t("Site") , "site"), 'users' => Array($a->get_baseurl()."/admin/users/", t("Users") , "users"), - 'plugins'=> Array($a->get_baseurl()."/admin/plugins/", t("Plugins") , "plugins") + 'plugins'=> Array($a->get_baseurl()."/admin/plugins/", t("Plugins") , "plugins"), + 'update' => Array($a->get_baseurl()."/admin/update/", t("Update") , "update") ); /* get plugins admin page */ @@ -106,7 +110,10 @@ function admin_content(&$a) { break; case 'logs': $o = admin_page_logs($a); - break; + break; + case 'update': + $o = admin_page_remoteupdate($a); + break; default: notice( t("Item not found.") ); } @@ -655,3 +662,54 @@ function admin_page_logs(&$a){ )); } +function admin_page_remoteupdate_post(&$a) { + // this function should be called via ajax post + if(!is_site_admin()) { + return login(false); + } + + + if (x($_POST,'remotefile') && $_POST['remotefile']!=""){ + $remotefile = $_POST['remotefile']; + $ftpdata = (x($_POST['ftphost'])?$_POST:false); + doUpdate($remotefile, $ftpdata); + } else { + echo "No remote file to download. Abort!"; + } + + killme(); +} + +function admin_page_remoteupdate(&$a) { + if(!is_site_admin()) { + return login(false); + } + + $canwrite = canWeWrite(); + $canftp = function_exists('ftp_connect'); + + $needupdate = true; + $u = checkUpdate(); + if (!is_array($u)){ + $needupdate = false; + $u = array('','',''); + } + + $tpl = get_markup_template("admin_remoteupdate.tpl"); + return replace_macros($tpl, array( + '$baseurl' => $a->get_baseurl(), + '$submit' => t("Update now"), + '$close' => t("Close"), + '$localversion' => FRIENDIKA_VERSION, + '$remoteversion' => $u[1], + '$needupdate' => $needupdate, + '$canwrite' => $canwrite, + '$canftp' => $canftp, + '$ftphost' => array('ftphost', t("FTP Host"), '',''), + '$ftppath' => array('ftppath', t("FTP Path"), '/',''), + '$ftpuser' => array('ftpuser', t("FTP User"), '',''), + '$ftppwd' => array('ftppwd', t("FTP Password"), '',''), + '$remotefile'=>array('remotefile','', $u['2'],'') + )); + +} diff --git a/view/admin_aside.tpl b/view/admin_aside.tpl index c9ab17aa5d..1cf431bec2 100644 --- a/view/admin_aside.tpl +++ b/view/admin_aside.tpl @@ -30,3 +30,8 @@ + + + diff --git a/view/admin_remoteupdate.tpl b/view/admin_remoteupdate.tpl new file mode 100644 index 0000000000..0c15692c23 --- /dev/null +++ b/view/admin_remoteupdate.tpl @@ -0,0 +1,98 @@ + + + +
+
Your version:
$localversion
+{{ if $needupdate }} +
New version:
$remoteversion
+ +
+ + + {{ if $canwrite }} +
+ {{ else }} +

Your friendika installation is not writable by web server.

+ {{ if $canftp }} +

You can try to update via FTP

+ {{ inc field_input.tpl with $field=$ftphost }}{{ endinc }} + {{ inc field_input.tpl with $field=$ftppath }}{{ endinc }} + {{ inc field_input.tpl with $field=$ftpuser }}{{ endinc }} + {{ inc field_password.tpl with $field=$ftppwd }}{{ endinc }} +
+ {{ endif }} + {{ endif }} +
+{{ else }} +

No updates

+{{ endif }} +
diff --git a/view/theme/duepuntozero/style.css b/view/theme/duepuntozero/style.css index bbaa43b57b..78a0bff5be 100644 --- a/view/theme/duepuntozero/style.css +++ b/view/theme/duepuntozero/style.css @@ -1943,7 +1943,6 @@ a.mail-list-link { #install-dbuser-label, #install-dbpass-label, #install-dbdata-label, -#install-admin-label, #install-tz-desc { float: left; width: 250px; @@ -1955,8 +1954,7 @@ a.mail-list-link { #install-dbhost, #install-dbuser, #install-dbpass, -#install-dbdata, -#install-admin { +#install-dbdata { float: left; width: 200px; margin-left: 20px; @@ -1966,7 +1964,6 @@ a.mail-list-link { #install-dbuser-end, #install-dbpass-end, #install-dbdata-end, -#install-admin-end, #install-tz-end { clear: both; } @@ -2573,6 +2570,38 @@ a.mail-list-link { #adminpage table#users img { width: 16px; height: 16px; } #adminpage table tr:hover { background-color: #bbc7d7; } #adminpage .selectall { text-align: right; } + +/* + * UPDATE + */ +.popup { + width: 100%; height: 100%; + top:0px; left:0px; + position: absolute; + display: none; +} + +.popup .background { + background-color: rgba(0,0,0,128); + opacity: 0.5; + width: 100%; height: 100%; + position: absolute; + top:0px; left:0px; +} +.popup .panel { + top:25%;left:25%;width:50%;height:50%; + padding: 1em; + position: absolute; + border: 4px solid #000000; + background-color: #FFFFFF; +} +.popup .panel .panel_text { display: block; overflow: auto; height: 80%; } +.popup .panel .panel_in { width: 100%; height: 100%; position: relative; } +.popup .panel .panel_actions { width: 100%; bottom: 4px; left: 0px; position: absolute; } +.panel_text .progress { width: 50%; overflow: hidden; height: auto; border: 1px solid #cccccc; margin-bottom: 5px} +.panel_text .progress span {float: right; display: block; width: 25%; background-color: #eeeeee; text-align: right;} + + /** * ICONS */