From 4ea927a7987d7fdcefcacb341f1a60b094ccf8d2 Mon Sep 17 00:00:00 2001 From: Frank Karlitschek Date: Mon, 23 Apr 2012 23:54:49 +0200 Subject: [PATCH 01/10] wrote a new versioning app over the weekend. Basic functionality to far but it works (TM) and has all the needed features (TM) for ownCloud 4. Userinterface integration and small improvements still missing. --- apps/files_versions/appinfo/app.php | 19 ++ apps/files_versions/appinfo/info.xml | 12 ++ apps/files_versions/appinfo/version | 1 + apps/files_versions/css/versions.css | 2 + apps/files_versions/history.php | 60 ++++++ apps/files_versions/js/versions.js | 2 + apps/files_versions/settings.php | 10 + apps/files_versions/templates/history.php | 18 ++ apps/files_versions/templates/settings.php | 7 + apps/files_versions/versions.php | 216 +++++++++++++++++++++ 10 files changed, 347 insertions(+) create mode 100644 apps/files_versions/appinfo/app.php create mode 100644 apps/files_versions/appinfo/info.xml create mode 100644 apps/files_versions/appinfo/version create mode 100644 apps/files_versions/css/versions.css create mode 100644 apps/files_versions/history.php create mode 100644 apps/files_versions/js/versions.js create mode 100644 apps/files_versions/settings.php create mode 100644 apps/files_versions/templates/history.php create mode 100644 apps/files_versions/templates/settings.php create mode 100644 apps/files_versions/versions.php diff --git a/apps/files_versions/appinfo/app.php b/apps/files_versions/appinfo/app.php new file mode 100644 index 00000000000..6e7a803252e --- /dev/null +++ b/apps/files_versions/appinfo/app.php @@ -0,0 +1,19 @@ + 10, + 'id' => 'files_versions', + 'name' => 'Versioning' )); + +OC_APP::registerAdmin('files_versions', 'settings'); + +// Listen to write signals +OC_Hook::connect(OC_Filesystem::CLASSNAME, OC_Filesystem::signal_post_write, "OCA_Versions\Storage", "write_hook"); + + + + +?> diff --git a/apps/files_versions/appinfo/info.xml b/apps/files_versions/appinfo/info.xml new file mode 100644 index 00000000000..9936a2ad8b2 --- /dev/null +++ b/apps/files_versions/appinfo/info.xml @@ -0,0 +1,12 @@ + + + files_versions + Versions + AGPL + Frank Karlitschek + 3 + Versioning of files + + + + diff --git a/apps/files_versions/appinfo/version b/apps/files_versions/appinfo/version new file mode 100644 index 00000000000..afaf360d37f --- /dev/null +++ b/apps/files_versions/appinfo/version @@ -0,0 +1 @@ +1.0.0 \ No newline at end of file diff --git a/apps/files_versions/css/versions.css b/apps/files_versions/css/versions.css new file mode 100644 index 00000000000..139597f9cb0 --- /dev/null +++ b/apps/files_versions/css/versions.css @@ -0,0 +1,2 @@ + + diff --git a/apps/files_versions/history.php b/apps/files_versions/history.php new file mode 100644 index 00000000000..6c7626ca4ed --- /dev/null +++ b/apps/files_versions/history.php @@ -0,0 +1,60 @@ +. + * + */ +require_once('../../lib/base.php'); + +OC_Util::checkLoggedIn(); + +if (isset($_GET['path'])) { + + $path = $_GET['path']; + $path = strip_tags($path); + + // roll back to old version if button clicked + if(isset($_GET['revert'])) { + \OCA_Versions\Storage::rollback($path,$_GET['revert']); + } + + // show the history only if there is something to show + if(OCA_Versions\Storage::isversioned($path)) { + + $count=5; //show the newest revisions + $versions=OCA_Versions\Storage::getversions($path,$count); + + $tmpl = new OC_Template('files_versions', 'history', 'user'); + $tmpl->assign('path', $path); + $tmpl->assign('versions', array_reverse($versions)); + $tmpl->printPage(); + }else{ + $tmpl = new OC_Template('files_versions', 'history', 'user'); + $tmpl->assign('path', $path); + $tmpl->assign('message', 'No old versions available'); + $tmpl->printPage(); + } +}else{ + $tmpl = new OC_Template('files_versions', 'history', 'user'); + $tmpl->assign('message', 'No path specified'); + $tmpl->printPage(); +} + + +?> diff --git a/apps/files_versions/js/versions.js b/apps/files_versions/js/versions.js new file mode 100644 index 00000000000..139597f9cb0 --- /dev/null +++ b/apps/files_versions/js/versions.js @@ -0,0 +1,2 @@ + + diff --git a/apps/files_versions/settings.php b/apps/files_versions/settings.php new file mode 100644 index 00000000000..eb154d3edd3 --- /dev/null +++ b/apps/files_versions/settings.php @@ -0,0 +1,10 @@ +fetchPage(); +?> diff --git a/apps/files_versions/templates/history.php b/apps/files_versions/templates/history.php new file mode 100644 index 00000000000..1b3de9ce77c --- /dev/null +++ b/apps/files_versions/templates/history.php @@ -0,0 +1,18 @@ +File: '.$_['path']).'
'; + echo(''.$_['message']).'
'; + + }else{ + + echo('Versions of '.$_['path']).'
'; + echo('

You can click on the revert button to revert to the specific verson.


'); + foreach ($_['versions'] as $v){ + echo(' '.OC_Util::formatDate($v).' revert

'); + } + + } + +?> diff --git a/apps/files_versions/templates/settings.php b/apps/files_versions/templates/settings.php new file mode 100644 index 00000000000..8c8def94429 --- /dev/null +++ b/apps/files_versions/templates/settings.php @@ -0,0 +1,7 @@ +
+
+ Versions
+ + Configuration goes here... +
+
diff --git a/apps/files_versions/versions.php b/apps/files_versions/versions.php new file mode 100644 index 00000000000..156a4f59c73 --- /dev/null +++ b/apps/files_versions/versions.php @@ -0,0 +1,216 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +/** + * Versions + * + * A class to handle the versioning of files. + */ + +namespace OCA_Versions; + +class Storage { + + + // config.php configuration: + // - files_versions + // - files_versionsfolder + // - files_versionsblacklist + // - files_versionsmaxfilesize + // - files_versionsinterval + // - files_versionmaxversions + // + // todo: + // - port to oc_filesystem to enable network transparency + // - check if it works well together with encryption + // - do configuration web interface + // - implement expire all function. And find a place to call it ;-) + // - add transparent compression. first test if it´s worth it. + + const DEFAULTENABLED=true; + const DEFAULTFOLDER='versions'; + const DEFAULTBLACKLIST='avi mp3 mpg mp4'; + const DEFAULTMAXFILESIZE=1048576; // 10MB + const DEFAULTMININTERVAL=300; // 5 min + const DEFAULTMAXVERSIONS=50; + + /** + * init the versioning and create the versions folder. + */ + public static function init() { + if(\OC_Config::getValue('files_versions', Storage::DEFAULTENABLED)=='true') { + // create versions folder + $foldername=\OC_Config::getValue('datadirectory').'/'. \OC_User::getUser() .'/'.\OC_Config::getValue('files_versionsfolder', Storage::DEFAULTFOLDER); + if(!is_dir($foldername)){ + mkdir($foldername); + } + } + } + + + /** + * listen to write event. + */ + public static function write_hook($params) { + if(\OC_Config::getValue('files_versions', Storage::DEFAULTENABLED)=='true') { + $path = $params[\OC_Filesystem::signal_param_path]; + if($path<>'') Storage::store($path); + } + } + + + + /** + * store a new version of a file. + */ + public static function store($filename) { + if(\OC_Config::getValue('files_versions', Storage::DEFAULTENABLED)=='true') { + $versionsfoldername=\OC_Config::getValue('datadirectory').'/'. \OC_User::getUser() .'/'.\OC_Config::getValue('files_versionsfolder', Storage::DEFAULTFOLDER); + $filesfoldername=\OC_Config::getValue('datadirectory').'/'. \OC_User::getUser() .'/files'; + Storage::init(); + + // check if filename is a directory + if(is_dir($filesfoldername.$filename)){ + return false; + } + + // check filetype blacklist + $blacklist=explode(' ',\OC_Config::getValue('files_versionsblacklist', Storage::DEFAULTBLACKLIST)); + foreach($blacklist as $bl) { + $parts=explode('.', $filename); + $ext=end($parts); + if(strtolower($ext)==$bl) { + return false; + } + } + + // check filesize + if(filesize($filesfoldername.$filename)>\OC_Config::getValue('files_versionsmaxfilesize', Storage::DEFAULTMAXFILESIZE)){ + return false; + } + + + // check mininterval + $matches=glob($versionsfoldername.$filename.'.v*'); + sort($matches); + $parts=explode('.v',end($matches)); + if((end($parts)+Storage::DEFAULTMININTERVAL)>time()){ + return false; + } + + + // create all parent folders + $info=pathinfo($filename); + @mkdir($versionsfoldername.$info['dirname'],0700,true); + + + // store a new version of a file + copy($filesfoldername.$filename,$versionsfoldername.$filename.'.v'.time()); + + // expire old revisions + Storage::expire($filename); + } + } + + + /** + * rollback to an old version of a file. + */ + public static function rollback($filename,$revision) { + if(\OC_Config::getValue('files_versions', Storage::DEFAULTENABLED)=='true') { + $versionsfoldername=\OC_Config::getValue('datadirectory').'/'. \OC_User::getUser() .'/'.\OC_Config::getValue('files_versionsfolder', Storage::DEFAULTFOLDER); + $filesfoldername=\OC_Config::getValue('datadirectory').'/'. \OC_User::getUser() .'/files'; + // rollback + @copy($versionsfoldername.$filename.'.v'.$revision,$filesfoldername.$filename); + } + } + + /** + * check if old versions of a file exist. + */ + public static function isversioned($filename) { + if(\OC_Config::getValue('files_versions', Storage::DEFAULTENABLED)=='true') { + $versionsfoldername=\OC_Config::getValue('datadirectory').'/'. \OC_User::getUser() .'/'.\OC_Config::getValue('files_versionsfolder', Storage::DEFAULTFOLDER); + + // check for old versions + $matches=glob($versionsfoldername.$filename.'.v*'); + if(count($matches)>1){ + return true; + }else{ + return false; + } + }else{ + return(false); + } + } + + + + /** + * get a list of old versions of a file. + */ + public static function getversions($filename,$count=0) { + if(\OC_Config::getValue('files_versions', Storage::DEFAULTENABLED)=='true') { + $versionsfoldername=\OC_Config::getValue('datadirectory').'/'. \OC_User::getUser() .'/'.\OC_Config::getValue('files_versionsfolder', Storage::DEFAULTFOLDER); + $versions=array(); + + // fetch for old versions + $matches=glob($versionsfoldername.$filename.'.v*'); + sort($matches); + foreach($matches as $ma) { + $parts=explode('.v',$ma); + $versions[]=(end($parts)); + } + + // only show the newest commits + if($count<>0 and (count($versions)>$count)) { + $versions=array_slice($versions,count($versions)-$count); + } + + return($versions); + + + }else{ + return(array()); + } + } + + + + /** + * expire old versions of a file. + */ + public static function expire($filename) { + if(\OC_Config::getValue('files_versions', Storage::DEFAULTENABLED)=='true') { + + $versionsfoldername=\OC_Config::getValue('datadirectory').'/'. \OC_User::getUser() .'/'.\OC_Config::getValue('files_versionsfolder', Storage::DEFAULTFOLDER); + + // check for old versions + $matches=glob($versionsfoldername.$filename.'.v*'); + if(count($matches)>\OC_Config::getValue('files_versionmaxversions', Storage::DEFAULTMAXVERSIONS)){ + $numbertodelete=count($matches-\OC_Config::getValue('files_versionmaxversions', Storage::DEFAULTMAXVERSIONS)); + + // delete old versions of a file + $deleteitems=array_slice($matches,0,$numbertodelete); + foreach($deleteitems as $de){ + unlink($versionsfoldername.$filename.'.v'.$de); + } + } + } + } + + /** + * expire all old versions. + */ + public static function expireall($filename) { + // todo this should go through all the versions directories and delete all the not needed files and not needed directories. + // useful to be included in a cleanup cronjob. + } + + +} From 0cd9570277152d85a257b01e70d86e39ba8aa51c Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Mon, 23 Apr 2012 21:08:38 +0200 Subject: [PATCH 02/10] Applied patches by Nicolas progweb at free.fr - https://mail.kde.org/pipermail/owncloud/2012-April/002900.html --- apps/contacts/lib/app.php | 1 + apps/contacts/lib/vcard.php | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/apps/contacts/lib/app.php b/apps/contacts/lib/app.php index 475d2c8dc2e..2c2cc331ed7 100644 --- a/apps/contacts/lib/app.php +++ b/apps/contacts/lib/app.php @@ -149,6 +149,7 @@ class OC_Contacts_App { 'WORK' => $l->t('Work'), 'TEXT' => $l->t('Text'), 'VOICE' => $l->t('Voice'), + 'MSG' => $l->t('Message'), 'FAX' => $l->t('Fax'), 'VIDEO' => $l->t('Video'), 'PAGER' => $l->t('Pager'), diff --git a/apps/contacts/lib/vcard.php b/apps/contacts/lib/vcard.php index 90b037f76ee..53c952656b6 100644 --- a/apps/contacts/lib/vcard.php +++ b/apps/contacts/lib/vcard.php @@ -214,7 +214,8 @@ class OC_Contacts_VCard{ $prodid = trim($vcard->getAsString('PRODID')); if(!$prodid) { $appinfo = OC_App::getAppInfo('contacts'); - $prodid = '-//ownCloud//NONSGML '.$appinfo['name'].' '.$appinfo['version'].'//EN'; + $appversion = OC_App::getAppVersion('contacts'); + $prodid = '-//ownCloud//NONSGML '.$appinfo['name'].' '.$appversion.'//EN'; $vcard->setString('PRODID', $prodid); } $now = new DateTime; From dfc94cbe84f03d73b59a1e97a745f7f3837c609b Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Mon, 23 Apr 2012 21:14:10 +0200 Subject: [PATCH 03/10] Contacts: In app import. Cleanup still needed. --- apps/contacts/ajax/importaddressbook.php | 24 ++++ apps/contacts/ajax/uploadimport.php | 57 +++++++++ apps/contacts/css/contacts.css | 3 +- apps/contacts/import.php | 12 +- apps/contacts/js/contacts.js | 115 ++++++++++++++++-- .../templates/part.chooseaddressbook.php | 3 +- .../part.chooseaddressbook.rowfields.php | 2 +- 7 files changed, 201 insertions(+), 15 deletions(-) create mode 100644 apps/contacts/ajax/importaddressbook.php create mode 100644 apps/contacts/ajax/uploadimport.php diff --git a/apps/contacts/ajax/importaddressbook.php b/apps/contacts/ajax/importaddressbook.php new file mode 100644 index 00000000000..5776c801a76 --- /dev/null +++ b/apps/contacts/ajax/importaddressbook.php @@ -0,0 +1,24 @@ + + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +require_once('../../../lib/base.php'); +OC_JSON::checkLoggedIn(); +OC_Util::checkAppEnabled('contacts'); +$upload_max_filesize = OC_Helper::computerFileSize(ini_get('upload_max_filesize')); +$post_max_size = OC_Helper::computerFileSize(ini_get('post_max_size')); +$maxUploadFilesize = min($upload_max_filesize, $post_max_size); + +$freeSpace=OC_Filesystem::free_space('/'); +$freeSpace=max($freeSpace,0); +$maxUploadFilesize = min($maxUploadFilesize ,$freeSpace); + +$tmpl = new OC_Template('contacts', 'part.importaddressbook'); +$tmpl->assign('uploadMaxFilesize', $maxUploadFilesize); +$tmpl->assign('uploadMaxHumanFilesize', OC_Helper::humanFileSize($maxUploadFilesize)); +$tmpl->printpage(); +?> diff --git a/apps/contacts/ajax/uploadimport.php b/apps/contacts/ajax/uploadimport.php new file mode 100644 index 00000000000..08bc436bc58 --- /dev/null +++ b/apps/contacts/ajax/uploadimport.php @@ -0,0 +1,57 @@ + + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE + * License as published by the Free Software Foundation; either + * version 3 of the License, or any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU AFFERO GENERAL PUBLIC LICENSE for more details. + * + * You should have received a copy of the GNU Affero General Public + * License along with this library. If not, see . + * + */ +// Init owncloud +require_once('../../../lib/base.php'); + +// Check if we are a user +OC_JSON::checkLoggedIn(); +OC_JSON::checkAppEnabled('contacts'); +function bailOut($msg) { + OC_JSON::error(array('data' => array('message' => $msg))); + OC_Log::write('contacts','ajax/uploadimport.php: '.$msg, OC_Log::ERROR); + exit(); +} +function debug($msg) { + OC_Log::write('contacts','ajax/uploadimport.php: '.$msg, OC_Log::DEBUG); +} + +// If it is a Drag'n'Drop transfer it's handled here. +$fn = (isset($_SERVER['HTTP_X_FILE_NAME']) ? $_SERVER['HTTP_X_FILE_NAME'] : false); +if($fn) { + /*$dir = OC_App::getStorage('contacts'); + $handle = $dir->touch(''.$fn); + if(!$handle) { + bailOut('Bugger!'); + } else { + bailOut('Yippie!'); + } + debug('Internal path: '.$dir->getInternalPath());*/ + $tmpfile = md5(rand()); + if(OC_Filesystem::file_put_contents('/'.$tmpfile, file_get_contents('php://input'))) { + debug($fn.' uploaded'); + OC_JSON::success(array('data' => array('path'=>'', 'file'=>$tmpfile))); + } else { + bailOut(OC_Contacts_App::$l10n->t('Error uploading contacts to storage.')); + } +} + +?> diff --git a/apps/contacts/css/contacts.css b/apps/contacts/css/contacts.css index b0c68c96e67..04700aa456a 100644 --- a/apps/contacts/css/contacts.css +++ b/apps/contacts/css/contacts.css @@ -33,7 +33,8 @@ dl.form { width: 100%; float: left; clear: right; margin: 0; padding: 0; } .form dd { display: table-cell; clear: right; float: left; margin: 0; padding: 0px; white-space: nowrap; vertical-align: text-bottom; } #address.form dt { min-width: 5em; } #address.form dl { min-width: 10em; } - +.droptarget { margin: 0.5em; padding: 0.5em; border: thin solid #ccc; -moz-border-radius:.3em; -webkit-border-radius:.3em; border-radius:.3em; } +.droppable { margin: 0.5em; padding: 0.5em; border: thin dashed #333; -moz-border-radius:.3em; -webkit-border-radius:.3em; border-radius:.3em; } .loading { background: url('../../../core/img/loading.gif') no-repeat center !important; /*cursor: progress; */ cursor: wait; } .ui-autocomplete-loading { background: url('../../../core/img/loading.gif') right center no-repeat; } .float { float: left; } diff --git a/apps/contacts/import.php b/apps/contacts/import.php index 233ed5be22f..c98e46995ff 100644 --- a/apps/contacts/import.php +++ b/apps/contacts/import.php @@ -99,12 +99,16 @@ if(is_writable('import_tmp/')){ if(count($parts) == 1){ $importready = array($file); } +$imported = 0; +$failed = 0; foreach($importready as $import){ $card = OC_VObject::parse($import); if (!$card) { + $failed += 1; OC_Log::write('contacts','Import: skipping card. Error parsing VCard: '.$import, OC_Log::ERROR); continue; // Ditch cards that can't be parsed by Sabre. } + $imported += 1; OC_Contacts_VCard::add($id, $card); } //done the import @@ -117,4 +121,10 @@ sleep(3); if(is_writable('import_tmp/')){ unlink($progressfile); } -OC_JSON::success(); +if(isset($_POST['istmpfile'])) { + OC_Log::write('contacts','Import: Unlinking '.$_POST['path'] . '/' . $_POST['file'], OC_Log::ERROR); + if(!OC_Filesystem::unlink($_POST['path'] . '/' . $_POST['file'])) { + OC_Log::write('contacts','Import: Error unlinking '.$_POST['path'] . '/' . $_POST['file'], OC_Log::ERROR); + } +} +OC_JSON::success(array('data' => array('imported'=>$imported, 'failed'=>$failed))); diff --git a/apps/contacts/js/contacts.js b/apps/contacts/js/contacts.js index f03ae1e46d6..f471a7f25aa 100644 --- a/apps/contacts/js/contacts.js +++ b/apps/contacts/js/contacts.js @@ -1263,7 +1263,8 @@ Contacts={ for(ptype in this.data.TEL[phone]['parameters'][param]) { var pt = this.data.TEL[phone]['parameters'][param][ptype]; $('#phonelist li:last-child').find('select option').each(function(){ - if ($(this).val().toUpperCase() == pt.toUpperCase()) { + //if ($(this).val().toUpperCase() == pt.toUpperCase()) { + if ($.inArray($(this).val().toUpperCase(), pt.toUpperCase().split(',')) > -1) { $(this).attr('selected', 'selected'); } }); @@ -1285,6 +1286,7 @@ Contacts={ }, }, Addressbooks:{ + droptarget:undefined, overview:function(){ if($('#chooseaddressbook_dialog').dialog('isOpen') == true){ $('#chooseaddressbook_dialog').dialog('moveToTop'); @@ -1317,14 +1319,13 @@ Contacts={ var tr = $(document.createElement('tr')) .load(OC.filePath('contacts', 'ajax', 'addbook.php')); $(object).closest('tr').after(tr).hide(); - /* TODO: Shouldn't there be some kinda error checking here? */ }, editAddressbook:function(object, bookid){ var tr = $(document.createElement('tr')) .load(OC.filePath('contacts', 'ajax', 'editaddressbook.php') + "?bookid="+bookid); $(object).closest('tr').after(tr).hide(); }, - deleteAddressbook:function(bookid){ + deleteAddressbook:function(obj, bookid){ var check = confirm("Do you really want to delete this address book?"); if(check == false){ return false; @@ -1332,9 +1333,10 @@ Contacts={ $.post(OC.filePath('contacts', 'ajax', 'deletebook.php'), { id: bookid}, function(jsondata) { if (jsondata.status == 'success'){ - $('#chooseaddressbook_dialog').dialog('destroy').remove(); + $(obj).closest('tr').remove(); + //$('#chooseaddressbook_dialog').dialog('destroy').remove(); Contacts.UI.Contacts.update(); - Contacts.UI.Addressbooks.overview(); + //Contacts.UI.Addressbooks.overview(); } else { OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error')); //alert('Error: ' + data.message); @@ -1342,8 +1344,99 @@ Contacts={ }); } }, - doImport:function(){ - Contacts.UI.notImplemented(); + loadImportHandlers:function() { + this.droptarget = $('#import_drop_target'); + console.log($('#import_drop_target').html()); + $(this.droptarget).bind('dragover',function(event){ + //console.log('dragover'); + $(event.target).addClass('droppable'); + event.stopPropagation(); + event.preventDefault(); + }); + $(this.droptarget).bind('dragleave',function(event){ + //console.log('dragleave'); + $(event.target).removeClass('droppable'); + //event.stopPropagation(); + //event.preventDefault(); + }); + $(this.droptarget).bind('drop',function(event){ + event.stopPropagation(); + event.preventDefault(); + console.log('drop'); + $(event.target).removeClass('droppable'); + $(event.target).html(t('contacts', 'Uploading...')); + Contacts.UI.loading(event.target, true); + $.fileUpload(event.originalEvent.dataTransfer.files); + }); + + $.fileUpload = function(files){ + console.log(files + ', ' + files.length); + var file = files[0]; + console.log('size: '+file.size+', type: '+file.type); + if(file.size > $('#max_upload').val()){ + OC.dialogs.alert(t('contacts','The file you are trying to upload exceed the maximum size for file uploads on this server.'), t('contacts','Upload too large')); + $(Contacts.UI.Addressbooks.droptarget).html(t('contacts', 'Drop a VCF file to import contacts.')); + Contacts.UI.loading(Contacts.UI.Addressbooks.droptarget, false); + return; + } + if(file.type.indexOf('text') != 0) { + OC.dialogs.alert(t('contacts','You have dropped a file type that cannot be imported: ') + file.type, t('contacts','Wrong file type')); + $(Contacts.UI.Addressbooks.droptarget).html(t('contacts', 'Drop a VCF file to import contacts.')); + Contacts.UI.loading(Contacts.UI.Addressbooks.droptarget, false); + return; + } + var xhr = new XMLHttpRequest(); + + if (!xhr.upload) { + OC.dialogs.alert(t('contacts', 'Your browser doesn\'t support AJAX upload. Please upload the contacts file to ownCloud and import that way.'), t('contacts', 'Error')) + } + fileUpload = xhr.upload, + xhr.onreadystatechange = function() { + if (xhr.readyState == 4){ + response = $.parseJSON(xhr.responseText); + if(response.status == 'success') { + if(xhr.status == 200) { + $(Contacts.UI.Addressbooks.droptarget).html(t('contacts', 'Importing...')); + Contacts.UI.Addressbooks.doImport(response.data.path, response.data.file); + } else { + $(Contacts.UI.Addressbooks.droptarget).html(t('contacts', 'Drop a VCF file to import contacts.')); + Contacts.UI.loading(Contacts.UI.Addressbooks.droptarget, false); + OC.dialogs.alert(xhr.status + ': ' + xhr.responseText, t('contacts', 'Error')); + } + } else { + OC.dialogs.alert(response.data.message, t('contacts', 'Error')); + } + } + }; + xhr.open("POST", 'ajax/uploadimport.php?file='+encodeURIComponent(file.name), true); + xhr.setRequestHeader('Cache-Control', 'no-cache'); + xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest'); + xhr.setRequestHeader('X_FILE_NAME', encodeURIComponent(file.name)); + xhr.setRequestHeader('X-File-Size', file.size); + xhr.setRequestHeader('Content-Type', file.type); + xhr.send(file); + } + }, + importAddressbook:function(object){ + var tr = $(document.createElement('tr')) + .load(OC.filePath('contacts', 'ajax', 'importaddressbook.php')); + $(object).closest('tr').after(tr).hide(); + //this.loadImportHandlers(); + }, + doImport:function(path, file){ + var id = $('#importaddressbook_dialog').find('#book').val(); + console.log('Selected book: ' + id); + $.post(OC.filePath('contacts', '', 'import.php'), { id: id, path: path, file: file, istmpfile: true }, + function(jsondata){ + if(jsondata.status == 'success'){ + Contacts.UI.Addressbooks.droptarget.html(t('contacts', 'Import done. Success/Failure: ')+jsondata.data.imported+'/'+jsondata.data.failed); + $('#chooseaddressbook_dialog').find('#close_button').val(t('contacts', 'OK')); + Contacts.UI.Contacts.update(); + } else { + OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error')); + } + }); + Contacts.UI.loading(Contacts.UI.Addressbooks.droptarget, false); }, submit:function(button, bookid){ var displayname = $("#displayname_"+bookid).val().trim(); @@ -1368,7 +1461,7 @@ Contacts={ } else { OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error')); } - }); + }); }, cancel:function(button, bookid){ $(button).closest('tr').prev().show().next().remove(); @@ -1507,13 +1600,13 @@ $(document).ready(function(){ }); $('#contacts_details_photo_wrapper').bind('dragover',function(event){ console.log('dragover'); - $(event.target).css('background-color','red'); + $(event.target).addClass('droppable'); event.stopPropagation(); event.preventDefault(); }); $('#contacts_details_photo_wrapper').bind('dragleave',function(event){ console.log('dragleave'); - $(event.target).css('background-color','white'); + $(event.target).removeClass('droppable'); //event.stopPropagation(); //event.preventDefault(); }); @@ -1521,7 +1614,7 @@ $(document).ready(function(){ event.stopPropagation(); event.preventDefault(); console.log('drop'); - $(event.target).css('background-color','white') + $(event.target).removeClass('droppable'); $.fileUpload(event.originalEvent.dataTransfer.files); }); diff --git a/apps/contacts/templates/part.chooseaddressbook.php b/apps/contacts/templates/part.chooseaddressbook.php index 90894220ef8..adfc8c15161 100644 --- a/apps/contacts/templates/part.chooseaddressbook.php +++ b/apps/contacts/templates/part.chooseaddressbook.php @@ -1,4 +1,4 @@ -
"> +
"> diff --git a/apps/contacts/templates/part.chooseaddressbook.rowfields.php b/apps/contacts/templates/part.chooseaddressbook.rowfields.php index 8518c1acd13..20b67a4161e 100644 --- a/apps/contacts/templates/part.chooseaddressbook.rowfields.php +++ b/apps/contacts/templates/part.chooseaddressbook.rowfields.php @@ -2,4 +2,4 @@ // FIXME: Make this readable. echo ""; echo ""; - echo ""; + echo ""; From 6848b069c9f33a164c8a5ab180a9a7f40802527b Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Tue, 24 Apr 2012 00:26:33 +0200 Subject: [PATCH 04/10] OC_App::getStorage() failed if app dir didn't exist. --- lib/app.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/app.php b/lib/app.php index 9f7d23872f3..29653775253 100755 --- a/lib/app.php +++ b/lib/app.php @@ -519,6 +519,10 @@ class OC_App{ public static function getStorage($appid){ if(OC_App::isEnabled($appid)){//sanity check if(OC_User::isLoggedIn()){ + $view = new OC_FilesystemView('/'.OC_User::getUser()); + if(!$view->file_exists($appid)) { + $view->mkdir($appid); + } return new OC_FilesystemView('/'.OC_User::getUser().'/'.$appid); }else{ OC_Log::write('core','Can\'t get app storage, app, user not logged in',OC_Log::ERROR); From b8da5c526219fae28434ede6cd53e0010d83c8ee Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Tue, 24 Apr 2012 00:43:08 +0200 Subject: [PATCH 05/10] Contacts: Tried to format NOTE field a bit. Needs to be improved more though. --- apps/contacts/css/contacts.css | 1 + apps/contacts/templates/part.contact.php | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/contacts/css/contacts.css b/apps/contacts/css/contacts.css index 04700aa456a..7338eed4a7f 100644 --- a/apps/contacts/css/contacts.css +++ b/apps/contacts/css/contacts.css @@ -60,6 +60,7 @@ dl.form { width: 100%; float: left; clear: right; margin: 0; padding: 0; } #identityprops { /*position: absolute; top: 2.5em; left: 0px;*/ } /*#contact_photo { max-width: 250px; }*/ #contact_identity { min-width: 30em; } +#note { min-width: 200px; } .contactsection { position: relative; float: left; /*max-width: 40em;*/ padding: 0.5em; height: auto: border: thin solid lightgray;/* -webkit-border-radius: 0.5em; -moz-border-radius: 0.5em; border-radius: 0.5em; background-color: #f8f8f8;*/ } .contactpart legend { width:auto; padding:.3em; border:1px solid #ddd; font-weight:bold; cursor:pointer; background:#f8f8f8; color:#555; text-shadow:#fff 0 1px 0; -moz-box-shadow:0 1px 1px #fff, 0 1px 1px #fff inset; -webkit-box-shadow:0 1px 1px #fff, 0 1px 1px #fff inset; -moz-border-radius:.5em; -webkit-border-radius:.5em; border-radius:.5em; } diff --git a/apps/contacts/templates/part.contact.php b/apps/contacts/templates/part.contact.php index b90fa92c2c5..dec081a9b89 100644 --- a/apps/contacts/templates/part.contact.php +++ b/apps/contacts/templates/part.contact.php @@ -112,7 +112,7 @@ $id = isset($_['id']) ? $_['id'] : '';
- +
From 22f4acadc05c16c19c312517ba494752fe385ccc Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Tue, 24 Apr 2012 01:43:21 +0200 Subject: [PATCH 06/10] Contacts: In app import (for Chrome and Firefox only) --- apps/contacts/ajax/uploadimport.php | 11 ++--------- apps/contacts/import.php | 17 +++++++++++------ apps/contacts/js/contacts.js | 8 +------- 3 files changed, 14 insertions(+), 22 deletions(-) diff --git a/apps/contacts/ajax/uploadimport.php b/apps/contacts/ajax/uploadimport.php index 08bc436bc58..ab680c8823f 100644 --- a/apps/contacts/ajax/uploadimport.php +++ b/apps/contacts/ajax/uploadimport.php @@ -37,16 +37,9 @@ function debug($msg) { // If it is a Drag'n'Drop transfer it's handled here. $fn = (isset($_SERVER['HTTP_X_FILE_NAME']) ? $_SERVER['HTTP_X_FILE_NAME'] : false); if($fn) { - /*$dir = OC_App::getStorage('contacts'); - $handle = $dir->touch(''.$fn); - if(!$handle) { - bailOut('Bugger!'); - } else { - bailOut('Yippie!'); - } - debug('Internal path: '.$dir->getInternalPath());*/ + $view = OC_App::getStorage('contacts'); $tmpfile = md5(rand()); - if(OC_Filesystem::file_put_contents('/'.$tmpfile, file_get_contents('php://input'))) { + if($view->file_put_contents('/'.$tmpfile, file_get_contents('php://input'))) { debug($fn.' uploaded'); OC_JSON::success(array('data' => array('path'=>'', 'file'=>$tmpfile))); } else { diff --git a/apps/contacts/import.php b/apps/contacts/import.php index c98e46995ff..93af7e62814 100644 --- a/apps/contacts/import.php +++ b/apps/contacts/import.php @@ -17,8 +17,14 @@ if(is_writable('import_tmp/')){ fwrite($progressfopen, '10'); fclose($progressfopen); } -$file = OC_Filesystem::file_get_contents($_POST['path'] . '/' . $_POST['file']); -if($_POST['method'] == 'new'){ +$view = $file = null; +if(isset($_POST['fstype']) && $_POST['fstype'] == 'OC_FilesystemView') { + $view = OC_App::getStorage('contacts'); + $file = $view->file_get_contents('/' . $_POST['file']); +} else { + $file = OC_Filesystem::file_get_contents($_POST['path'] . '/' . $_POST['file']); +} +if(isset($_POST['method']) && $_POST['method'] == 'new'){ $id = OC_Contacts_Addressbook::add(OC_User::getUser(), $_POST['addressbookname']); OC_Contacts_Addressbook::setActive($id, 1); }else{ @@ -121,10 +127,9 @@ sleep(3); if(is_writable('import_tmp/')){ unlink($progressfile); } -if(isset($_POST['istmpfile'])) { - OC_Log::write('contacts','Import: Unlinking '.$_POST['path'] . '/' . $_POST['file'], OC_Log::ERROR); - if(!OC_Filesystem::unlink($_POST['path'] . '/' . $_POST['file'])) { - OC_Log::write('contacts','Import: Error unlinking '.$_POST['path'] . '/' . $_POST['file'], OC_Log::ERROR); +if(isset($_POST['fstype']) && $_POST['fstype'] == 'OC_FilesystemView') { + if(!$view->unlink('/' . $_POST['file'])) { + OC_Log::write('contacts','Import: Error unlinking OC_FilesystemView ' . '/' . $_POST['file'], OC_Log::ERROR); } } OC_JSON::success(array('data' => array('imported'=>$imported, 'failed'=>$failed))); diff --git a/apps/contacts/js/contacts.js b/apps/contacts/js/contacts.js index f471a7f25aa..a3e1a1361df 100644 --- a/apps/contacts/js/contacts.js +++ b/apps/contacts/js/contacts.js @@ -1348,16 +1348,12 @@ Contacts={ this.droptarget = $('#import_drop_target'); console.log($('#import_drop_target').html()); $(this.droptarget).bind('dragover',function(event){ - //console.log('dragover'); $(event.target).addClass('droppable'); event.stopPropagation(); event.preventDefault(); }); $(this.droptarget).bind('dragleave',function(event){ - //console.log('dragleave'); $(event.target).removeClass('droppable'); - //event.stopPropagation(); - //event.preventDefault(); }); $(this.droptarget).bind('drop',function(event){ event.stopPropagation(); @@ -1421,12 +1417,11 @@ Contacts={ var tr = $(document.createElement('tr')) .load(OC.filePath('contacts', 'ajax', 'importaddressbook.php')); $(object).closest('tr').after(tr).hide(); - //this.loadImportHandlers(); }, doImport:function(path, file){ var id = $('#importaddressbook_dialog').find('#book').val(); console.log('Selected book: ' + id); - $.post(OC.filePath('contacts', '', 'import.php'), { id: id, path: path, file: file, istmpfile: true }, + $.post(OC.filePath('contacts', '', 'import.php'), { id: id, path: path, file: file, fstype: 'OC_FilesystemView' }, function(jsondata){ if(jsondata.status == 'success'){ Contacts.UI.Addressbooks.droptarget.html(t('contacts', 'Import done. Success/Failure: ')+jsondata.data.imported+'/'+jsondata.data.failed); @@ -1478,7 +1473,6 @@ Contacts={ } else{ OC.dialogs.alert(jsondata.data.message, t('contacts', 'Error')); - //alert(jsondata.data.message); } }); setTimeout(Contacts.UI.Contacts.lazyupdate, 500); From 988f98fc6f2942a09ffbd901bb24651acac75c8c Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Tue, 24 Apr 2012 11:35:56 +0200 Subject: [PATCH 07/10] Contacts: Check that UIDs really are unique on import. Better solution would be to suggest to merge contacts, but that will have to wait to OC5 ;-) --- apps/contacts/lib/vcard.php | 38 ++++++++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/apps/contacts/lib/vcard.php b/apps/contacts/lib/vcard.php index 53c952656b6..96fc8cf7121 100644 --- a/apps/contacts/lib/vcard.php +++ b/apps/contacts/lib/vcard.php @@ -141,10 +141,38 @@ class OC_Contacts_VCard{ } /** - * @brief Tries to update imported VCards to adhere to rfc2426 (VERSION: 3.0) + * @brief Checks if a contact with the same UID already exist in the address book. + * @param $aid Address book ID. + * @param $uid UID (passed by reference). + * @returns true if the UID has been changed. + */ + protected static function trueUID($aid, &$uid) { + $stmt = OC_DB::prepare( 'SELECT * FROM *PREFIX*contacts_cards WHERE addressbookid = ? AND uri = ?' ); + $uri = $uid.'.vcf'; + $result = $stmt->execute(array($aid,$uri)); + if($result->numRows() > 0){ + while(true) { + $tmpuid = substr(md5(rand().time()),0,10); + $uri = $tmpuid.'.vcf'; + $result = $stmt->execute(array($aid,$uri)); + if($result->numRows() > 0){ + continue; + } else { + $uid = $tmpuid; + return true; + } + } + } else { + return false; + } + } + + /** + * @brief Tries to update imported VCards to adhere to rfc2426 (VERSION: 3.0) and add mandatory fields if missing. + * @param aid Address book id. * @param vcard An OC_VObject of type VCARD (passed by reference). */ - protected static function updateValuesFromAdd(&$vcard) { // any suggestions for a better method name? ;-) + protected static function updateValuesFromAdd($aid, &$vcard) { // any suggestions for a better method name? ;-) $stringprops = array('N', 'FN', 'ORG', 'NICK', 'ADR', 'NOTE'); $typeprops = array('ADR', 'TEL', 'EMAIL'); $upgrade = false; @@ -207,8 +235,12 @@ class OC_Contacts_VCard{ } if(!$uid) { $vcard->setUID(); + $uid = $vcard->getAsString('UID'); OC_Log::write('contacts','OC_Contacts_VCard::updateValuesFromAdd. Added missing \'UID\' field: '.$uid,OC_Log::DEBUG); } + if(self::trueUID($aid, $uid)) { + $vcard->setString('UID', $uid); + } $vcard->setString('VERSION','3.0'); // Add product ID is missing. $prodid = trim($vcard->getAsString('PRODID')); @@ -237,7 +269,7 @@ class OC_Contacts_VCard{ OC_Contacts_App::loadCategoriesFromVCard($card); - self::updateValuesFromAdd($card); + self::updateValuesFromAdd($aid, $card); $fn = $card->getAsString('FN'); if (empty($fn)) { From b824425186abc3ea7218252c26d44f8aee73b95d Mon Sep 17 00:00:00 2001 From: Thomas Tanghus Date: Tue, 24 Apr 2012 11:38:11 +0200 Subject: [PATCH 08/10] Added loading indicator. --- apps/contacts/js/contacts.js | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/contacts/js/contacts.js b/apps/contacts/js/contacts.js index a3e1a1361df..e00e9257e65 100644 --- a/apps/contacts/js/contacts.js +++ b/apps/contacts/js/contacts.js @@ -1393,6 +1393,7 @@ Contacts={ if(response.status == 'success') { if(xhr.status == 200) { $(Contacts.UI.Addressbooks.droptarget).html(t('contacts', 'Importing...')); + Contacts.UI.loading(Contacts.UI.Addressbooks.droptarget, true); Contacts.UI.Addressbooks.doImport(response.data.path, response.data.file); } else { $(Contacts.UI.Addressbooks.droptarget).html(t('contacts', 'Drop a VCF file to import contacts.')); From 69a2173fdbc31eda0110997e28fea2037e5e1558 Mon Sep 17 00:00:00 2001 From: Frank Karlitschek Date: Tue, 24 Apr 2012 14:00:44 +0200 Subject: [PATCH 09/10] non working app. moved to the apps-playground repository --- apps/files_versioning/ajax/gethead.php | 12 - apps/files_versioning/ajax/sethead.php | 14 - apps/files_versioning/appinfo/app.php | 20 - apps/files_versioning/appinfo/info.xml | 12 - apps/files_versioning/appinfo/version | 1 - apps/files_versioning/css/settings.css | 3 - apps/files_versioning/js/settings.js | 25 - apps/files_versioning/lib_granite.php | 12 - apps/files_versioning/settings.php | 34 - apps/files_versioning/templates/settings.php | 12 - apps/files_versioning/versionstorage.php | 386 ----------- apps/files_versioning/versionwrapper.php | 686 ------------------- 12 files changed, 1217 deletions(-) delete mode 100644 apps/files_versioning/ajax/gethead.php delete mode 100644 apps/files_versioning/ajax/sethead.php delete mode 100644 apps/files_versioning/appinfo/app.php delete mode 100644 apps/files_versioning/appinfo/info.xml delete mode 100644 apps/files_versioning/appinfo/version delete mode 100644 apps/files_versioning/css/settings.css delete mode 100644 apps/files_versioning/js/settings.js delete mode 100644 apps/files_versioning/lib_granite.php delete mode 100644 apps/files_versioning/settings.php delete mode 100644 apps/files_versioning/templates/settings.php delete mode 100644 apps/files_versioning/versionstorage.php delete mode 100644 apps/files_versioning/versionwrapper.php diff --git a/apps/files_versioning/ajax/gethead.php b/apps/files_versioning/ajax/gethead.php deleted file mode 100644 index cc93b7a1d17..00000000000 --- a/apps/files_versioning/ajax/gethead.php +++ /dev/null @@ -1,12 +0,0 @@ - $head)); diff --git a/apps/files_versioning/ajax/sethead.php b/apps/files_versioning/ajax/sethead.php deleted file mode 100644 index d1b2df9b00f..00000000000 --- a/apps/files_versioning/ajax/sethead.php +++ /dev/null @@ -1,14 +0,0 @@ - 10, - 'id' => 'files_versioning', - 'name' => 'Versioning and Backup' )); - -// Include stylesheets for the settings page -OC_Util::addStyle( 'files_versioning', 'settings' ); -OC_Util::addScript('files_versioning','settings'); - -// Register a settings section in the Admin > Personal page -OC_APP::registerPersonal('files_versioning','settings'); diff --git a/apps/files_versioning/appinfo/info.xml b/apps/files_versioning/appinfo/info.xml deleted file mode 100644 index b9f56f674a0..00000000000 --- a/apps/files_versioning/appinfo/info.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - files_versioning - Versioning and Backup - GPLv2 - Craig Roberts - 3 - Versions files using Git repositories, providing a simple backup facility. Currently in *beta* and explicitly without warranty of any kind. - - - - diff --git a/apps/files_versioning/appinfo/version b/apps/files_versioning/appinfo/version deleted file mode 100644 index afaf360d37f..00000000000 --- a/apps/files_versioning/appinfo/version +++ /dev/null @@ -1 +0,0 @@ -1.0.0 \ No newline at end of file diff --git a/apps/files_versioning/css/settings.css b/apps/files_versioning/css/settings.css deleted file mode 100644 index afe2cd5508f..00000000000 --- a/apps/files_versioning/css/settings.css +++ /dev/null @@ -1,3 +0,0 @@ -#file_versioning_commit_chzn { - width: 15em; -} diff --git a/apps/files_versioning/js/settings.js b/apps/files_versioning/js/settings.js deleted file mode 100644 index 8dd13bac033..00000000000 --- a/apps/files_versioning/js/settings.js +++ /dev/null @@ -1,25 +0,0 @@ -$(document).ready(function(){ - $('#file_versioning_head').chosen(); - - $.getJSON(OC.filePath('files_versioning', 'ajax', 'gethead.php'), function(jsondata, status) { - - if (jsondata.head == 'HEAD') { - // Most recent commit, do nothing - } else { - $("#file_versioning_head").val(jsondata.head); - // Trigger the chosen update call - // See http://harvesthq.github.com/chosen/ - $("#file_versioning_head").trigger("liszt:updated"); - } - }); - - $('#file_versioning_head').change(function() { - - var data = $(this).serialize(); - $.post( OC.filePath('files_versioning', 'ajax', 'sethead.php'), data, function(data){ - if(data == 'error'){ - console.log('Saving new HEAD failed'); - } - }); - }); -}); diff --git a/apps/files_versioning/lib_granite.php b/apps/files_versioning/lib_granite.php deleted file mode 100644 index 571e5cea637..00000000000 --- a/apps/files_versioning/lib_granite.php +++ /dev/null @@ -1,12 +0,0 @@ -head(); -for ($i = 0; $i < 50; $i++) { - $commits[] = $commit; - $parents = $commit->parents(); - if (count($parents) > 0) { - $parent = $parents[0]; - } else { - break; - } - - $commit = $repository->factory('commit', $parent); -} - -$tmpl = new OC_Template( 'files_versioning', 'settings'); -$tmpl->assign('commits', $commits); -return $tmpl->fetchPage(); diff --git a/apps/files_versioning/templates/settings.php b/apps/files_versioning/templates/settings.php deleted file mode 100644 index 17f4cc7f77f..00000000000 --- a/apps/files_versioning/templates/settings.php +++ /dev/null @@ -1,12 +0,0 @@ -
- Versioning and Backup
-

Please note: Backing up large files (around 16MB+) will cause your backup history to grow very large, very quickly.

- - -
diff --git a/apps/files_versioning/versionstorage.php b/apps/files_versioning/versionstorage.php deleted file mode 100644 index d083e623df9..00000000000 --- a/apps/files_versioning/versionstorage.php +++ /dev/null @@ -1,386 +0,0 @@ -. - */ - -// Include Granite -require_once('lib_granite.php'); - -// Create a top-level 'Backup' directory if it does not already exist -$user = OC_User::getUser(); -if (OC_Filesystem::$loaded and !OC_Filesystem::is_dir('/Backup')) { - OC_Filesystem::mkdir('/Backup'); - OC_Preferences::setValue(OC_User::getUser(), 'files_versioning', 'head', 'HEAD'); -} - -// Generate the repository path (currently using 'full' repositories, as opposed to bare ones) -$repo_path = DIRECTORY_SEPARATOR - . OC_User::getUser() - . DIRECTORY_SEPARATOR - . 'files' - . DIRECTORY_SEPARATOR - . 'Backup'; - -// Mount the 'Backup' folder using the versioned storage provider below -OC_Filesystem::mount('OC_Filestorage_Versioned', array('repo'=>$repo_path), $repo_path . DIRECTORY_SEPARATOR); - -class OC_Filestorage_Versioned extends OC_Filestorage { - - /** - * Holds an instance of Granite\Git\Repository - */ - protected $repo; - - /** - * Constructs a new OC_Filestorage_Versioned instance, expects an associative - * array with a `repo` key set to the path of the repository's `.git` folder - * - * @param array $parameters An array containing the key `repo` pointing to the - * repository path. - */ - public function __construct($parameters) { - // Get the full path to the repository folder - $path = OC_Config::getValue('datadirectory', OC::$SERVERROOT.'/data') - . $parameters['repo'] - . DIRECTORY_SEPARATOR - . '.git' - . DIRECTORY_SEPARATOR; - - try { - // Attempt to load the repository - $this->repo = new Granite\Git\Repository($path); - } catch (InvalidArgumentException $e) { - // $path is not a valid Git repository, we must create one - Granite\Git\Repository::init($path); - - // Load the newly-initialised repository - $this->repo = new Granite\Git\Repository($path); - - /** - * Create an initial commit with a README file - * FIXME: This functionality should be transferred to the Granite library - */ - $blob = new Granite\Git\Blob($this->repo->path()); - $blob->content('Your Backup directory is now ready for use.'); - - // Create a new tree to hold the README file - $tree = $this->repo->factory('tree'); - // Create a tree node to represent the README blob - $tree_node = new Granite\Git\Tree\Node('README', '100644', $blob->sha()); - $tree->nodes(array($tree_node->name() => $tree_node)); - - // Create an initial commit - $commit = new Granite\Git\Commit($this->repo->path()); - $user_string = OC_User::getUser() . ' ' . time() . ' +0000'; - $commit->author($user_string); - $commit->committer($user_string); - $commit->message('Initial commit'); - $commit->tree($tree); - - // Write it all to disk - $blob->write(); - $tree->write(); - $commit->write(); - - // Update the HEAD for the 'master' branch - $this->repo->head('master', $commit->sha()); - } - - // Update the class pointer to the HEAD - $head = OC_Preferences::getValue(OC_User::getUser(), 'files_versioning', 'head', 'HEAD'); - - // Load the most recent commit if the preference is not set - if ($head == 'HEAD') { - $this->head = $this->repo->head()->sha(); - } else { - $this->head = $head; - } - } - - public function mkdir($path) { - if (mkdir("versioned:/{$this->repo->path()}$path#{$this->head}")) { - $this->head = $this->repo->head()->sha(); - OC_Preferences::setValue(OC_User::getUser(), 'files_versioning', 'head', $head); - return true; - } - - return false; - } - - public function rmdir($path) { - - } - - /** - * Returns a directory handle to the requested path, or FALSE on failure - * - * @param string $path The directory path to open - * - * @return boolean|resource A directory handle, or FALSE on failure - */ - public function opendir($path) { - return opendir("versioned:/{$this->repo->path()}$path#{$this->head}"); - } - - /** - * Returns TRUE if $path is a directory, or FALSE if not - * - * @param string $path The path to check - * - * @return boolean - */ - public function is_dir($path) { - return $this->filetype($path) == 'dir'; - } - - /** - * Returns TRUE if $path is a file, or FALSE if not - * - * @param string $path The path to check - * - * @return boolean - */ - public function is_file($path) { - return $this->filetype($path) == 'file'; - } - - public function stat($path) - { - return stat("versioned:/{$this->repo->path()}$path#{$this->head}"); - } - - /** - * Returns the strings 'dir' or 'file', depending on the type of $path - * - * @param string $path The path to check - * - * @return string Returns 'dir' if a directory, 'file' otherwise - */ - public function filetype($path) { - if ($path == "" || $path == "/") { - return 'dir'; - } else { - if (substr($path, -1) == '/') { - $path = substr($path, 0, -1); - } - - $node = $this->tree_search($this->repo, $this->repo->factory('commit', $this->head)->tree(), $path); - - // Does it exist, or is it new? - if ($node == null) { - // New file - return 'file'; - } else { - // Is it a tree? - try { - $this->repo->factory('tree', $node); - return 'dir'; - } catch (InvalidArgumentException $e) { - // Nope, must be a blob - return 'file'; - } - } - } - } - - public function filesize($path) { - return filesize("versioned:/{$this->repo->path()}$path#{$this->head}"); - } - - /** - * Returns a boolean value representing whether $path is readable - * - * @param string $path The path to check - *( - * @return boolean Whether or not the path is readable - */ - public function is_readable($path) { - return true; - } - - /** - * Returns a boolean value representing whether $path is writable - * - * @param string $path The path to check - *( - * @return boolean Whether or not the path is writable - */ - public function is_writable($path) { - - $head = OC_Preferences::getValue(OC_User::getUser(), 'files_versioning', 'head', 'HEAD'); - if ($head !== 'HEAD' && $head !== $this->repo->head()->sha()) { - // Cannot modify previous commits - return false; - } - return true; - } - - /** - * Returns a boolean value representing whether $path exists - * - * @param string $path The path to check - *( - * @return boolean Whether or not the path exists - */ - public function file_exists($path) { - return file_exists("versioned:/{$this->repo->path()}$path#{$this->head}"); - } - - /** - * Returns an integer value representing the inode change time - * (NOT IMPLEMENTED) - * - * @param string $path The path to check - *( - * @return int Timestamp of the last inode change - */ - public function filectime($path) { - return -1; - } - - /** - * Returns an integer value representing the file modification time - * - * @param string $path The path to check - *( - * @return int Timestamp of the last file modification - */ - public function filemtime($path) { - return filemtime("versioned:/{$this->repo->path()}$path#{$this->head}"); - } - - public function file_get_contents($path) { - return file_get_contents("versioned:/{$this->repo->path()}$path#{$this->head}"); - } - - public function file_put_contents($path, $data) { - $success = file_put_contents("versioned:/{$this->repo->path()}$path#{$this->head}", $data); - if ($success !== false) { - // Update the HEAD in the preferences - OC_Preferences::setValue(OC_User::getUser(), 'files_versioning', 'head', $this->repo->head()->sha()); - return $success; - } - - return false; - } - - public function unlink($path) { - - } - - public function rename($path1, $path2) { - - } - - public function copy($path1, $path2) { - - } - - public function fopen($path, $mode) { - return fopen("versioned:/{$this->repo->path()}$path#{$this->head}", $mode); - } - - public function getMimeType($path) { - if ($this->filetype($path) == 'dir') { - return 'httpd/unix-directory'; - } elseif ($this->filesize($path) == 0) { - // File's empty, returning text/plain allows opening in the web editor - return 'text/plain'; - } else { - $finfo = new finfo(FILEINFO_MIME_TYPE); - /** - * We need to represent the repository path, the file path, and the - * revision, which can be simply achieved with a convention of using - * `.git` in the repository directory (bare or not) and the '#part' - * segment of a URL to specify the revision. For example - * - * versioned://var/www/myrepo.git/docs/README.md#HEAD ('bare' repo) - * versioned://var/www/myrepo/.git/docs/README.md#HEAD ('full' repo) - * versioned://var/www/myrepo/.git/docs/README.md#6a8f...8a54 ('full' repo and SHA-1 commit ID) - */ - $mime = $finfo->buffer(file_get_contents("versioned:/{$this->repo->path()}$path#{$this->head}")); - return $mime; - } - } - - /** - * Generates a hash based on the file contents - * - * @param string $type The hashing algorithm to use (e.g. 'md5', 'sha256', etc.) - * @param string $path The file to be hashed - * @param boolean $raw Outputs binary data if true, lowercase hex digits otherwise - * - * @return string Hashed string representing the file contents - */ - public function hash($type, $path, $raw) { - return hash($type, file_get_contents($path), $raw); - } - - public function free_space($path) { - } - - public function search($query) { - - } - - public function touch($path, $mtime=null) { - - } - - - public function getLocalFile($path) { - } - - /** - * Recursively searches a tree for a path, returning FALSE if is not found - * or an SHA-1 id if it is found. - * - * @param string $repo The repository containing the tree object - * @param string $tree The tree object to search - * @param string $path The path to search for (relative to the tree) - * @param int $depth The depth of the current search (for recursion) - * - * @return string|boolean The SHA-1 id of the sub-tree - */ - private function tree_search($repo, $tree, $path, $depth = 0) - { - $paths = array_values(explode(DIRECTORY_SEPARATOR, $path)); - - $current_path = $paths[$depth]; - - $nodes = $tree->nodes(); - foreach ($nodes as $node) { - if ($node->name() == $current_path) { - - if (count($paths)-1 == $depth) { - // Stop, found it - return $node->sha(); - } - - // Recurse if necessary - if ($node->isDirectory()) { - $tree = $this->repo->factory('tree', $node->sha()); - return $this->tree_search($repo, $tree, $path, $depth + 1); - } - } - } - - return false; - } - -} diff --git a/apps/files_versioning/versionwrapper.php b/apps/files_versioning/versionwrapper.php deleted file mode 100644 index b83a4fd3b22..00000000000 --- a/apps/files_versioning/versionwrapper.php +++ /dev/null @@ -1,686 +0,0 @@ -tree); - return true; - } - - /** - * Open directory handle - */ - public function dir_opendir($path, $options) { - // Parse the URL into a repository directory, file path and commit ID - list($this->repo, $repo_file, $this->commit) = $this->parse_url($path); - - if ($repo_file == '' || $repo_file == '/') { - // Set the tree property for the future `readdir()` etc. calls - $this->tree = array_values($this->commit->tree()->nodes()); - return true; - } elseif ($this->tree_search($this->repo, $this->commit->tree(), $repo_file) !== false) { - // Something exists at this path, is it a directory though? - try { - $tree = $this->repo->factory( - 'tree', - $this->tree_search($this->repo, $this->commit->tree(), $repo_file) - ); - $this->tree = array_values($tree->nodes()); - return true; - } catch (InvalidArgumentException $e) { - // Trying to call `opendir()` on a file, return false below - } - } - - // Unable to find the directory, return false - return false; - } - - /** - * Read entry from directory handle - */ - public function dir_readdir() { - return isset($this->tree[$this->dir_position]) - ? $this->tree[$this->dir_position++]->name() - : false; - } - - /** - * Rewind directory handle - */ - public function dir_rewinddir() { - $this->dir_position = 0; - } - - /** - * Create a directory - * Git doesn't track empty directories, so a ".empty" file is added instead - */ - public function mkdir($path, $mode, $options) { - // Parse the URL into a repository directory, file path and commit ID - list($this->repo, $repo_file, $this->commit) = $this->parse_url($path); - - // Create an empty file for Git - $empty = new Granite\Git\Blob($this->repo->path()); - $empty->content(''); - $empty->write(); - - if (dirname($repo_file) == '.') { - // Adding a new directory to the root tree - $tree = $this->repo->head()->tree(); - } else { - $tree = $this->repo->factory('tree', $this->tree_search( - $this->repo, $this->repo->head()->tree(), dirname($repo_file) - ) - ); - } - - // Create our new tree, with our empty file - $dir = $this->repo->factory('tree'); - $nodes = array(); - $nodes[self::EMPTYFILE] = new Granite\Git\Tree\Node(self::EMPTYFILE, '100644', $empty->sha()); - $dir->nodes($nodes); - $dir->write(); - - // Add our new tree to its parent - $nodes = $tree->nodes(); - $nodes[basename($repo_file)] = new Granite\Git\Tree\Node(basename($repo_file), '040000', $dir->sha()); - $tree->nodes($nodes); - $tree->write(); - - // We need to recursively update each parent tree, since they are all - // hashed and the changes will cascade back up the chain - - // So, we're currently at the bottom-most directory - $current_dir = dirname($repo_file); - $previous_tree = $tree; - - if ($current_dir !== '.') { - do { - // Determine the parent directory - $previous_dir = $current_dir; - $current_dir = dirname($current_dir); - - $current_tree = $current_dir !== '.' - ? $this->repo->factory( - 'tree', $this->tree_search( - $this->repo, - $this->repo->head()->tree(), - $current_dir - ) - ) - : $this->repo->head()->tree(); - - $current_nodes = $current_tree->nodes(); - $current_nodes[basename($previous_dir)] = new Granite\Git\Tree\Node( - basename($previous_dir), '040000', $previous_tree->sha() - ); - $current_tree->nodes($current_nodes); - $current_tree->write(); - - $previous_tree = $current_tree; - } while ($current_dir !== '.'); - - $tree = $previous_tree; - } - - // Create a new commit to represent this write - $commit = $this->repo->factory('commit'); - $username = OC_User::getUser(); - $user_string = $username . ' ' . time() . ' +0000'; - $commit->author($user_string); - $commit->committer($user_string); - $commit->message("$username created the `$repo_file` directory, " . date('d F Y H:i', time()) . '.'); - $commit->parents(array($this->repo->head()->sha())); - $commit->tree($tree); - - // Write it to disk - $commit->write(); - - // Update the HEAD for the 'master' branch - $this->repo->head('master', $commit->sha()); - - return true; - } - - /** - * Renames a file or directory - */ - public function rename($path_from, $path_to) { - - } - - /** - * Removes a directory - */ - public function rmdir($path, $options) { - - } - - /** - * Retrieve the underlaying resource (NOT IMPLEMENTED) - */ - public function stream_cast($cast_as) { - return false; - } - - /** - * Close a resource - */ - public function stream_close() { - unset($this->blob); - return true; - } - - /** - * Tests for end-of-file on a file pointer - */ - public function stream_eof() { - return !($this->file_position < strlen($this->blob)); - } - - /** - * Flushes the output (NOT IMPLEMENTED) - */ - public function stream_flush() { - return false; - } - - /** - * Advisory file locking (NOT IMPLEMENTED) - */ - public function stream_lock($operation) { - return false; - } - - /** - * Change stream options (NOT IMPLEMENTED) - * Called in response to `chgrp()`, `chown()`, `chmod()` and `touch()` - */ - public function stream_metadata($path, $option, $var) { - return false; - } - - /** - * Opens file or URL - */ - public function stream_open($path, $mode, $options, &$opened_path) { - // Store the path, so we can use it later in `stream_write()` if necessary - $this->path = $path; - // Parse the URL into a repository directory, file path and commit ID - list($this->repo, $repo_file, $this->commit) = $this->parse_url($path); - - $file = $this->tree_search($this->repo, $this->commit->tree(), $repo_file); - if ($file !== false) { - try { - $this->blob = $this->repo->factory('blob', $file)->content(); - return true; - } catch (InvalidArgumentException $e) { - // Trying to open a directory, return false below - } - } elseif ($mode !== 'r') { - // All other modes allow opening for reading and writing, clearly - // some 'write' files may not exist yet... - return true; - } - - // File could not be found or is not actually a file - return false; - } - - /** - * Read from stream - */ - public function stream_read($count) { - // Fetch the remaining set of bytes - $bytes = substr($this->blob, $this->file_position, $count); - - // If EOF or empty string, return false - if ($bytes == '' || $bytes == false) { - return false; - } - - // If $count does not extend past EOF, add $count to stream offset - if ($this->file_position + $count < strlen($this->blob)) { - $this->file_position += $count; - } else { - // Otherwise return all remaining bytes - $this->file_position = strlen($this->blob); - } - - return $bytes; - } - - /** - * Seeks to specific location in a stream - */ - public function stream_seek($offset, $whence = SEEK_SET) { - $new_offset = false; - - switch ($whence) - { - case SEEK_SET: - $new_offset = $offset; - break; - case SEEK_CUR: - $new_offset = $this->file_position += $offset; - break; - case SEEK_END: - $new_offset = strlen($this->blob) + $offset; - break; - } - - $this->file_position = $offset; - - return ($new_offset !== false); - } - - /** - * Change stream options (NOT IMPLEMENTED) - */ - public function stream_set_option($option, $arg1, $arg2) { - return false; - } - - /** - * Retrieve information about a file resource (NOT IMPLEMENTED) - */ - public function stream_stat() { - - } - - /** - * Retrieve the current position of a stream - */ - public function stream_tell() { - return $this->file_position; - } - - /** - * Truncate stream - */ - public function stream_truncate($new_size) { - - } - - /** - * Write to stream - * FIXME: Could use heavy refactoring - */ - public function stream_write($data) { - /** - * FIXME: This also needs to be added to Granite, in the form of `add()`, - * `rm()` and `commit()` calls - */ - - // Parse the URL into a repository directory, file path and commit ID - list($this->repo, $repo_file, $this->commit) = $this->parse_url($this->path); - - $node = $this->tree_search($this->repo, $this->commit->tree(), $repo_file); - - if ($node !== false) { - // File already exists, attempting modification of existing tree - try { - $this->repo->factory('blob', $node); - - // Create our new blob with the provided $data - $blob = $this->repo->factory('blob'); - $blob->content($data); - $blob->write(); - - // We know the tree exists, so strip the filename from the path and - // find it... - - if (dirname($repo_file) == '.' || dirname($repo_file) == '') { - // Root directory - $tree = $this->repo->head()->tree(); - } else { - // Sub-directory - $tree = $this->repo->factory('tree', $this->tree_search( - $this->repo, - $this->repo->head()->tree(), - dirname($repo_file) - ) - ); - } - - // Replace the old blob with our newly modified one - $tree_nodes = $tree->nodes(); - $tree_nodes[basename($repo_file)] = new Granite\Git\Tree\Node( - basename($repo_file), '100644', $blob->sha() - ); - $tree->nodes($tree_nodes); - $tree->write(); - - // We need to recursively update each parent tree, since they are all - // hashed and the changes will cascade back up the chain - - // So, we're currently at the bottom-most directory - $current_dir = dirname($repo_file); - $previous_tree = $tree; - - if ($current_dir !== '.') { - do { - // Determine the parent directory - $previous_dir = $current_dir; - $current_dir = dirname($current_dir); - - $current_tree = $current_dir !== '.' - ? $this->repo->factory( - 'tree', $this->tree_search( - $this->repo, - $this->repo->head()->tree(), - $current_dir - ) - ) - : $this->repo->head()->tree(); - - $current_nodes = $current_tree->nodes(); - $current_nodes[basename($previous_dir)] = new Granite\Git\Tree\Node( - basename($previous_dir), '040000', $previous_tree->sha() - ); - $current_tree->nodes($current_nodes); - $current_tree->write(); - - $previous_tree = $current_tree; - } while ($current_dir !== '.'); - } - - // Create a new commit to represent this write - $commit = $this->repo->factory('commit'); - $username = OC_User::getUser(); - $user_string = $username . ' ' . time() . ' +0000'; - $commit->author($user_string); - $commit->committer($user_string); - $commit->message("$username modified the `$repo_file` file, " . date('d F Y H:i', time()) . '.'); - $commit->parents(array($this->repo->head()->sha())); - $commit->tree($previous_tree); - - // Write it to disk - $commit->write(); - - // Update the HEAD for the 'master' branch - $this->repo->head('master', $commit->sha()); - - // If we made it this far, write was successful - update the stream - // position and return the number of bytes written - $this->file_position += strlen($data); - return strlen($data); - - } catch (InvalidArgumentException $e) { - // Attempting to write to a directory or other error, fail - return 0; - } - } else { - // File does not exist, needs to be created - - // Create our new blob with the provided $data - $blob = $this->repo->factory('blob'); - $blob->content($data); - $blob->write(); - - if (dirname($repo_file) == '.') { - // Trying to add a new file to the root tree, nice and easy - $tree = $this->repo->head()->tree(); - $tree_nodes = $tree->nodes(); - $tree_nodes[basename($repo_file)] = new Granite\Git\Tree\Node( - basename($repo_file), '100644', $blob->sha() - ); - $tree->nodes($tree_nodes); - $tree->write(); - } else { - // Trying to add a new file to a subdirectory, try and find it - $tree = $this->repo->factory('tree', $this->tree_search( - $this->repo, $this->repo->head()->tree(), dirname($repo_file) - ) - ); - - // Add the blob to the tree - $nodes = $tree->nodes(); - $nodes[basename($repo_file)] = new Granite\Git\Tree\Node( - basename($repo_file), '100644', $blob->sha() - ); - $tree->nodes($nodes); - $tree->write(); - - // We need to recursively update each parent tree, since they are all - // hashed and the changes will cascade back up the chain - - // So, we're currently at the bottom-most directory - $current_dir = dirname($repo_file); - $previous_tree = $tree; - - if ($current_dir !== '.') { - do { - // Determine the parent directory - $previous_dir = $current_dir; - $current_dir = dirname($current_dir); - - $current_tree = $current_dir !== '.' - ? $this->repo->factory( - 'tree', $this->tree_search( - $this->repo, - $this->repo->head()->tree(), - $current_dir - ) - ) - : $this->repo->head()->tree(); - - $current_nodes = $current_tree->nodes(); - $current_nodes[basename($previous_dir)] = new Granite\Git\Tree\Node( - basename($previous_dir), '040000', $previous_tree->sha() - ); - $current_tree->nodes($current_nodes); - $current_tree->write(); - - $previous_tree = $current_tree; - } while ($current_dir !== '.'); - - $tree = $previous_tree; - } - } - - // Create a new commit to represent this write - $commit = $this->repo->factory('commit'); - $username = OC_User::getUser(); - $user_string = $username . ' ' . time() . ' +0000'; - $commit->author($user_string); - $commit->committer($user_string); - $commit->message("$username created the `$repo_file` file, " . date('d F Y H:i', time()) . '.'); - $commit->parents(array($this->repo->head()->sha())); - $commit->tree($tree); // Top-level tree (NOT the newly modified tree) - - // Write it to disk - $commit->write(); - - // Update the HEAD for the 'master' branch - $this->repo->head('master', $commit->sha()); - - // If we made it this far, write was successful - update the stream - // position and return the number of bytes written - $this->file_position += strlen($data); - return strlen($data); - } - - // Write failed - return 0; - } - - /** - * Delete a file - */ - public function unlink($path) { - - } - - /** - * Retrieve information about a file - */ - public function url_stat($path, $flags) { - // Parse the URL into a repository directory, file path and commit ID - list($this->repo, $repo_file, $this->commit) = $this->parse_url($path); - - $node = $this->tree_search($this->repo, $this->commit->tree(), $repo_file); - - if ($node == false && $this->commit->sha() == $this->repo->head()->sha()) { - // A new file - no information available - $size = 0; - $mtime = -1; - } else { - - // Is it a directory? - try { - $this->repo->factory('tree', $node); - $size = 4096; // FIXME - } catch (InvalidArgumentException $e) { - // Must be a file - $size = strlen(file_get_contents($path)); - } - - // Parse the timestamp from the commit message - preg_match('/[0-9]{10}+/', $this->commit->committer(), $matches); - $mtime = $matches[0]; - } - - $stat["dev"] = ""; - $stat["ino"] = ""; - $stat["mode"] = ""; - $stat["nlink"] = ""; - $stat["uid"] = ""; - $stat["gid"] = ""; - $stat["rdev"] = ""; - $stat["size"] = $size; - $stat["atime"] = $mtime; - $stat["mtime"] = $mtime; - $stat["ctime"] = $mtime; - $stat["blksize"] = ""; - $stat["blocks"] = ""; - - return $stat; - } - - /** - * Debug function for development purposes - */ - private function debug($message, $level = OC_Log::DEBUG) - { - if ($this->debug) { - OC_Log::write('files_versioning', $message, $level); - } - } - - /** - * Parses a URL of the form: - * `versioned://path/to/git/repository/.git/path/to/file#SHA-1-commit-id` - * FIXME: Will throw an InvalidArgumentException if $path is invaid - * - * @param string $path The path to parse - * - * @return array An array containing an instance of Granite\Git\Repository, - * the file path, and an instance of Granite\Git\Commit - * @throws InvalidArgumentException If the repository cannot be loaded - */ - private function parse_url($path) - { - preg_match('/\/([A-Za-z0-9\/]+\.git\/)([A-Za-z0-9\/\.\/]*)(#([A-Fa-f0-9]+))*/', $path, $matches); - - // Load up the repo - $repo = new \Granite\Git\Repository($matches[1]); - // Parse the filename (stripping any trailing slashes) - $repo_file = $matches[2]; - if (substr($repo_file, -1) == '/') { - $repo_file = substr($repo_file, 0, -1); - } - - // Default to HEAD if no commit is provided - $repo_commit = isset($matches[4]) - ? $matches[4] - : $repo->head()->sha(); - - // Load the relevant commit - $commit = $repo->factory('commit', $repo_commit); - - return array($repo, $repo_file, $commit); - } - - /** - * Recursively searches a tree for a path, returning FALSE if is not found - * or an SHA-1 id if it is found. - * - * @param string $repo The repository containing the tree object - * @param string $tree The tree object to search - * @param string $path The path to search for (relative to the tree) - * @param int $depth The depth of the current search (for recursion) - * - * @return string|boolean The SHA-1 id of the sub-tree - */ - private function tree_search($repo, $tree, $path, $depth = 0) - { - $paths = array_values(explode(DIRECTORY_SEPARATOR, $path)); - - $current_path = $paths[$depth]; - - $nodes = $tree->nodes(); - foreach ($nodes as $node) { - if ($node->name() == $current_path) { - - if (count($paths)-1 == $depth) { - // Stop, found it - return $node->sha(); - } - - // Recurse if necessary - if ($node->isDirectory()) { - $tree = $this->repo->factory('tree', $node->sha()); - return $this->tree_search($repo, $tree, $path, $depth + 1); - } - } - } - - return false; - } - -} From 9b134b063683aca678e19912ebc3928321751714 Mon Sep 17 00:00:00 2001 From: Frank Karlitschek Date: Tue, 24 Apr 2012 15:41:54 +0200 Subject: [PATCH 10/10] =?UTF-8?q?move=20granite=20also=20to=20apps-playgro?= =?UTF-8?q?und=20because=20it=C2=B4s=20not=20stable=20yet=20and=20is=20no?= =?UTF-8?q?=20longer=20used?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- 3rdparty/granite/git/blob.php | 162 ------------- 3rdparty/granite/git/commit.php | 232 ------------------- 3rdparty/granite/git/object/index.php | 210 ----------------- 3rdparty/granite/git/object/loose.php | 81 ------- 3rdparty/granite/git/object/packed.php | 304 ------------------------- 3rdparty/granite/git/object/raw.php | 153 ------------- 3rdparty/granite/git/repository.php | 293 ------------------------ 3rdparty/granite/git/tag.php | 38 ---- 3rdparty/granite/git/tree.php | 198 ---------------- 3rdparty/granite/git/tree/node.php | 126 ---------- 10 files changed, 1797 deletions(-) delete mode 100644 3rdparty/granite/git/blob.php delete mode 100644 3rdparty/granite/git/commit.php delete mode 100644 3rdparty/granite/git/object/index.php delete mode 100644 3rdparty/granite/git/object/loose.php delete mode 100644 3rdparty/granite/git/object/packed.php delete mode 100644 3rdparty/granite/git/object/raw.php delete mode 100644 3rdparty/granite/git/repository.php delete mode 100644 3rdparty/granite/git/tag.php delete mode 100644 3rdparty/granite/git/tree.php delete mode 100644 3rdparty/granite/git/tree/node.php diff --git a/3rdparty/granite/git/blob.php b/3rdparty/granite/git/blob.php deleted file mode 100644 index 781a697d560..00000000000 --- a/3rdparty/granite/git/blob.php +++ /dev/null @@ -1,162 +0,0 @@ - - * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License - * @link http://craig0990.github.com/Granite/ - */ - -namespace Granite\Git; -use \Granite\Git\Object\Raw as Raw; -use \InvalidArgumentException as InvalidArgumentException; -use \finfo as finfo; -/** - * **Granite\Git\Blob** represents the raw content of an object in a Git repository, - * typically a **file**. This class provides methods related to the handling of - * blob content, mimetypes, sizes and write support. - * - * @category Git - * @package Granite - * @author Craig Roberts - * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License - * @link http://craig0990.github.com/Granite/ - */ -class Blob -{ - - /** - * Stores the SHA-1 id of the object requested; accessed through the `sha()` - * method where it is recalculated based on the blob content. - */ - private $sha = null; - /** - * The raw binary string of the file contents. - */ - private $content = ""; - /** - * The path to the repository location. - */ - private $path; - - /** - * Fetches a raw Git object and parses the result. Throws an - * InvalidArgumentException if the object is not of the correct type, - * or cannot be found. - * - * @param string $path The path to the repository root. - * @param string $sha The SHA-1 id of the requested object, or `null` if - * creating a new blob object. - * - * @throws InvalidArgumentException If the SHA-1 id provided is not a blob. - */ - public function __construct($path, $sha = NULL) - { - $this->path = $path; - if ($sha !== NULL) { - $this->sha = $sha; - $object = Raw::factory($path, $sha); - - if ($object->type() !== Raw::OBJ_BLOB) { - throw new InvalidArgumentException( - "The object $sha is not a blob, type is {$object->type()}" - ); - } - - $this->content = $object->content(); - unset($object); - } - } - - /** - * Sets or returns the raw file content, depending whether the parameter is - * provided. - * - * @param string $content The object content to set, or `null` if requesting the - * current content. - * - * @return string The raw binary string of the file contents. - */ - public function content($content = NULL) - { - if ($content == NULL) { - return $this->content; - } - $this->content = $content; - } - - /** - * Returns the size of the file content in bytes, equivalent to - * `strlen($blob->content())`. - * - * @return int The size of the object in bytes. - */ - public function size() - { - return strlen($this->content); - } - - /** - * Updates and returns the SHA-1 id of the object, based on it's contents. - * - * @return int The SHA-1 id of the object. - */ - public function sha() - { - $sha = hash_init('sha1'); - $header = 'blob ' . strlen($this->content) . "\0"; - hash_update($sha, $header); - hash_update($sha, $this->content); - $this->sha = hash_final($sha); - return $this->sha; - } - - /** - * Returns the mimetype of the object, using `finfo()` to determine the mimetype - * of the string. - * - * @return string The object mimetype. - * @see http://php.net/manual/en/function.finfo-open.php - */ - public function mimetype() - { - $finfo = new finfo(FILEINFO_MIME); - return $finfo->buffer($this->content); - } - - /** - * Encode and compress the object content, saving it to a 'loose' file. - * - * @return boolean True on success, false on failure. - */ - public function write() - { - $sha = $this->sha(TRUE); - $path = $this->path - . 'objects' - . DIRECTORY_SEPARATOR - . substr($sha, 0, 2) - . DIRECTORY_SEPARATOR - . substr($sha, 2); - // FIXME: currently writes loose objects only - if (file_exists($path)) { - return FALSE; - } - - if (!is_dir(dirname($path))) { - mkdir(dirname($path), 0777, TRUE); - } - - $loose = fopen($path, 'wb'); - $data = 'blob ' . strlen($this->content) . "\0" . $this->content; - $write = fwrite($loose, gzcompress($data)); - fclose($loose); - - return ($write !== FALSE); - } - -} diff --git a/3rdparty/granite/git/commit.php b/3rdparty/granite/git/commit.php deleted file mode 100644 index 51077e89f3c..00000000000 --- a/3rdparty/granite/git/commit.php +++ /dev/null @@ -1,232 +0,0 @@ - - * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License - * @link http://craig0990.github.com/Granite/ - */ - -namespace Granite\Git; -use \Granite\Git\Object\Raw as Raw; -use \InvalidArgumentException as InvalidArgumentException; - -/** - * Commit represents a full commit object - * - * @category Git - * @package Granite - * @author Craig Roberts - * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License - * @link http://craig0990.github.com/Granite/ - */ -class Commit -{ - - /** - * The path to the repository root - */ - private $path; - /** - * The SHA-1 id of the requested commit - */ - private $sha; - /** - * The size of the commit in bytes - */ - private $size; - /** - * The commit message - */ - private $message; - /** - * The full committer string - */ - private $committer; - /** - * The full author string - */ - private $author; - /** - * The SHA-1 ids of the parent commits - */ - private $parents = array(); - - /** - * Fetches a raw Git object and parses the result. Throws an - * InvalidArgumentException if the object is not of the correct type, - * or cannot be found. - * - * @param string $path The path to the repository root - * @param string $sha The SHA-1 id of the requested object - * - * @throws InvalidArgumentException - */ - public function __construct($path, $sha = NULL) - { - $this->path = $path; - if ($sha !== NULL) { - $this->sha = $sha; - $object = Raw::factory($path, $sha); - $this->size = $object->size(); - - if ($object->type() !== Raw::OBJ_COMMIT) { - throw new InvalidArgumentException( - "The object $sha is not a commit, type is " . $object->type() - ); - } - - // Parse headers and commit message (delimited with "\n\n") - list($headers, $this->message) = explode("\n\n", $object->content(), 2); - $headers = explode("\n", $headers); - - foreach ($headers as $header) { - list($header, $value) = explode(' ', $header, 2); - if ($header == 'parent') { - $this->parents[] = $value; - } else { - $this->$header = $value; - } - } - - $this->tree = new Tree($this->path, $this->tree); - } - } - - /** - * Returns the message stored in the commit - * - * @return string The commit message - */ - public function message($message = NULL) - { - if ($message !== NULL) { - $this->message = $message; - return $this; - } - return $this->message; - } - - /** - * Returns the commiter string - * - * @return string The committer string - */ - public function committer($committer = NULL) - { - if ($committer !== NULL) { - $this->committer = $committer; - return $this; - } - return $this->committer; - } - - /** - * Returns the author string - * - * @return string The author string - */ - public function author($author = NULL) - { - if ($author !== NULL) { - $this->author = $author; - return $this; - } - return $this->author; - } - - /** - * Returns the parents of the commit, or an empty array if none - * - * @return array The parents of the commit - */ - public function parents($parents = NULL) - { - if ($parents !== NULL) { - $this->parents = $parents; - return $this; - } - return $this->parents; - } - - /** - * Returns a tree object associated with the commit - * - * @return Tree - */ - public function tree(Tree $tree = NULL) - { - if ($tree !== NULL) { - $this->tree = $tree; - return $this; - } - return $this->tree; - } - - /** - * Returns the size of the commit in bytes (Git header + data) - * - * @return int - */ - public function size() - { - return $this->size; - } - - /** - * Returns the size of the commit in bytes (Git header + data) - * - * @return int - */ - public function sha() - { - $this->sha = hash('sha1', $this->_raw()); - return $this->sha; - } - - public function write() - { - $sha = $this->sha(); - $path = $this->path - . 'objects' - . DIRECTORY_SEPARATOR - . substr($sha, 0, 2) - . DIRECTORY_SEPARATOR - . substr($sha, 2); - // FIXME: currently writes loose objects only - if (file_exists($path)) { - return FALSE; - } - - if (!is_dir(dirname($path))) { - mkdir(dirname($path), 0777, TRUE); - } - - $loose = fopen($path, 'wb'); - $data = $this->_raw(); - $write = fwrite($loose, gzcompress($data)); - fclose($loose); - - return ($write !== FALSE); - } - - public function _raw() - { - $data = 'tree ' . $this->tree->sha() . "\n"; - foreach ($this->parents as $parent) - { - $data .= "parent $parent\n"; - } - $data .= 'author ' . $this->author . "\n"; - $data .= 'committer ' . $this->committer . "\n\n"; - $data .= $this->message; - - $data = 'commit ' . strlen($data) . "\0" . $data; - return $data; - } - -} diff --git a/3rdparty/granite/git/object/index.php b/3rdparty/granite/git/object/index.php deleted file mode 100644 index 239706d4efd..00000000000 --- a/3rdparty/granite/git/object/index.php +++ /dev/null @@ -1,210 +0,0 @@ - - * @license http://www.opensource.org/licenses/mit-license.php MIT License - * @link http://craig0990.github.com/Granite/ - */ - -namespace Granite\Git\Object; -use \UnexpectedValueException as UnexpectedValueException; - -/** - * Index represents a packfile index - * - * @category Git - * @package Granite - * @author Craig Roberts - * @license http://www.opensource.org/licenses/mit-license.php MIT License - * @link http://craig0990.github.com/Granite/ - */ -class Index -{ - const INDEX_MAGIC = "\377tOc"; - - /** - * The full path to the packfile index - */ - private $path; - /** - * The offset at which the fanout begins, version 2+ indexes have a 2-byte header - */ - private $offset = 8; - /** - * The size of the SHA-1 entries, version 1 stores 4-byte offsets alongside to - * total 24 bytes, version 2+ stores offsets separately - */ - private $size = 20; - /** - * The version of the index file format, versions 1 and 2 are in use and - * currently supported - */ - private $version; - - /** - * Fetches a raw Git object and parses the result - * - * @param string $path The path to the repository root - * @param string $packname The name of the packfile index to read - */ - public function __construct($path, $packname) - { - $this->path = $path - . 'objects' - . DIRECTORY_SEPARATOR - . 'pack' - . DIRECTORY_SEPARATOR - . 'pack-' . $packname . '.idx'; - - $this->version = $this->_readVersion(); - if ($this->version !== 1 && $this->version !== 2) { - throw new UnexpectedValueException( - "Unsupported index version (version $version)" - ); - } - - if ($this->version == 1) { - $this->offset = 0; // Version 1 index has no header/version - $this->size = 24; // Offsets + SHA-1 ids are stored together - } - } - - /** - * Returns the offset of the object stored in the index - * - * @param string $sha The SHA-1 id of the object being requested - * - * @return int The offset of the object in the packfile - */ - public function find($sha) - { - $index = fopen($this->path, 'rb'); - $offset = false; // Offset for object in packfile not found by default - - // Read the fanout to skip to the start char in the sorted SHA-1 list - list($start, $after) = $this->_readFanout($index, $sha); - - if ($start == $after) { - fclose($index); - return false; // Object is apparently located in a 0-length section - } - - // Seek $offset + 255 4-byte fanout entries and read 256th entry - fseek($index, $this->offset + 4 * 255); - $totalObjects = $this->_uint32($index); - - // Look up the SHA-1 id of the object - // TODO: Binary search - fseek($index, $this->offset + 1024 + $this->size * $start); - for ($i = $start; $i < $after; $i++) { - if ($this->version == 1) { - $offset = $this->_uint32($index); - } - - $name = fread($index, 20); - if ($name == pack('H40', $sha)) { - break; // Found it - } - } - - if ($i == $after) { - fclose($index); - return false; // Scanned entire section, couldn't find it - } - - if ($this->version == 2) { - // Jump to the offset location and read it - fseek($index, 1032 + 24 * $totalObjects + 4 * $i); - $offset = $this->_uint32($index); - if ($offset & 0x80000000) { - // Offset is a 64-bit integer; packfile is larger than 2GB - fclose($index); - throw new UnexpectedValueException( - "Packfile larger than 2GB, currently unsupported" - ); - } - } - - fclose($index); - return $offset; - } - - /** - * Converts a binary string into a 32-bit unsigned integer - * - * @param handle $file Binary string to convert - * - * @return int Integer value - */ - private function _uint32($file) - { - $val = unpack('Nx', fread($file, 4)); - return $val['x']; - } - - /** - * Reads the fanout for a particular SHA-1 id - * - * Largely modified from Glip, with some reference to Grit - largely because I - * can't see how to re-implement this in PHP - * - * @param handle $file File handle to the index file - * @param string $sha The SHA-1 id to search for - * @param int $offset The offset at which the fanout begins - * - * @return array Array containing integer 'start' and - * 'past-the-end' locations - */ - private function _readFanout($file, $sha) - { - $sha = pack('H40', $sha); - fseek($file, $this->offset); - if ($sha{0} == "\00") { - /** - * First character is 0, read first fanout entry to provide - * 'past-the-end' location (since first fanout entry provides start - * point for '1'-prefixed SHA-1 ids) - */ - $start = 0; - fseek($file, $this->offset); // Jump to start of fanout, $offset bytes in - $after = $this->_uint32($file); - } else { - /** - * Take ASCII value of first character, minus one to get the fanout - * position of the offset (minus one because the fanout does not - * contain an entry for "\00"), multiplied by four bytes per entry - */ - fseek($file, $this->offset + (ord($sha{0}) - 1) * 4); - $start = $this->_uint32($file); - $after = $this->_uint32($file); - } - - return array($start, $after); - } - - /** - * Returns the version number of the index file, or 1 if there is no version - * information - * - * @return int - */ - private function _readVersion() - { - $file = fopen($this->path, 'rb'); - $magic = fread($file, 4); - $version = $this->_uint32($file); - - if ($magic !== self::INDEX_MAGIC) { - $version = 1; - } - - fclose($file); - return $version; - } - -} diff --git a/3rdparty/granite/git/object/loose.php b/3rdparty/granite/git/object/loose.php deleted file mode 100644 index 32f894845b9..00000000000 --- a/3rdparty/granite/git/object/loose.php +++ /dev/null @@ -1,81 +0,0 @@ - - * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License - * @link http://craig0990.github.com/Granite/ - */ - -namespace Granite\Git\Object; -use \UnexpectedValueException as UnexpectedValueException; - -/** - * Loose represents a loose object in the Git repository - * - * @category Git - * @package Granite - * @author Craig Roberts - * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License - * @link http://craig0990.github.com/Granite/ - */ -class Loose extends Raw -{ - - /** - * Reads an object from a loose object file based on the SHA-1 id - * - * @param string $path The path to the repository root - * @param string $sha The SHA-1 id of the requested object - * - * @throws UnexpectedValueException If the type is not 'commit', 'tree', - * 'tag' or 'blob' - */ - public function __construct($path, $sha) - { - $this->sha = $sha; - - $loose_path = $path - . 'objects/' - . substr($sha, 0, 2) - . '/' - . substr($sha, 2); - - if (!file_exists($loose_path)) { - throw new InvalidArgumentException("Cannot open loose object file for $sha"); - } - - $raw = gzuncompress(file_get_contents($loose_path)); - $data = explode("\0", $raw, 2); - - $header = $data[0]; - $this->content = $data[1]; - - list($this->type, $this->size) = explode(' ', $header); - - switch ($this->type) { - case 'commit': - $this->type = Raw::OBJ_COMMIT; - break; - case 'tree': - $this->type = Raw::OBJ_TREE; - break; - case 'blob': - $this->type = Raw::OBJ_BLOB; - break; - case 'tag': - $this->type = Raw::OBJ_TAG; - break; - default: - throw new UnexpectedValueException( - "Unexpected type '{$this->type}'" - ); - break; - } - } - -} diff --git a/3rdparty/granite/git/object/packed.php b/3rdparty/granite/git/object/packed.php deleted file mode 100644 index 7e8d663b32e..00000000000 --- a/3rdparty/granite/git/object/packed.php +++ /dev/null @@ -1,304 +0,0 @@ - - * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License - * @link http://craig0990.github.com/Granite/ - */ - -namespace Granite\Git\Object; -use \UnexpectedValueException as UnexpectedValueException; - -/** - * Packed represents a packed object in the Git repository - * - * @category Git - * @package Granite - * @author Craig Roberts - * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License - * @link http://craig0990.github.com/Granite/ - */ -class Packed extends Raw -{ - - /** - * The name of the packfile being read - */ - private $_packfile; - - /** - * Added to the object size to make a 'best-guess' effort at how much compressed - * data to read - should be reimplemented, ideally with streams. - */ - const OBJ_PADDING = 512; - - /** - * Reads the object data from the compressed data at $offset in $packfile - * - * @param string $packfile The path to the packfile - * @param int $offset The offset of the object data - */ - public function __construct($packfile, $offset) - { - $this->_packfile = $packfile; - - list($this->type, $this->size, $this->content) - = $this->_readPackedObject($offset); - } - - /** - * Reads the object data at $this->_offset - * - * @param int $offset Offset of the object header - * - * @return array Containing the type, size and object data - */ - private function _readPackedObject($offset) - { - $file = fopen($this->_packfile, 'rb'); - fseek($file, $offset); - // Read the type and uncompressed size from the object header - list($type, $size) = $this->_readHeader($file, $offset); - $object_offset = ftell($file); - - if ($type == self::OBJ_OFS_DELTA || $type == self::OBJ_REF_DELTA) { - return $this->_unpackDeltified( - $file, $offset, $object_offset, $type, $size - ); - } - - $content = gzuncompress(fread($file, $size + self::OBJ_PADDING), $size); - - return array($type, $size, $content); - } - - /** - * Reads a packed object header, returning the type and the size. For more - * detailed information, refer to the @see tag. - * - * From the @see tag: "Each byte is really 7 bits of data, with the first bit - * being used to say if that hunk is the last one or not before the data starts. - * If the first bit is a 1, you will read another byte, otherwise the data starts - * next. The first 3 bits in the first byte specifies the type of data..." - * - * @param handle $file File handle to read - * @param int $offset Offset of the object header - * - * @return array Containing the type and the size - * @see http://book.git-scm.com/7_the_packfile.html - */ - private function _readHeader($file, $offset) - { - // Read the object header byte-by-byte - fseek($file, $offset); - $byte = ord(fgetc($file)); - /** - * Bit-shift right by four, then ignore the first bit with a bitwise AND - * This gives us the object type in binary: - * 001 commit self::OBJ_COMMIT - * 010 tree self::OBJ_TREE - * 011 blob self::OBJ_BLOB - * 100 tag self::OBJ_TAG - * 110 offset delta self::OBJ_OFS_DELTA - * 111 ref delta self::OBJ_REF_DELTA - * - * (000 is undefined, 101 is not currently in use) - * See http://book.git-scm.com/7_the_packfile.html for details - */ - $type = ($byte >> 4) & 0x07; - - // Read the last four bits of the first byte, used to find the size - $size = $byte & 0x0F; - - /** - * $shift initially set to four, since we use the last four bits of the first - * byte - * - * $byte & 0x80 checks the initial bit is set to 1 (i.e. keep reading data) - * - * Finally, $shift is incremented by seven for each consecutive byte (because - * we ignore the initial bit) - */ - for ($shift = 4; $byte & 0x80; $shift += 7) { - $byte = ord(fgetc($file)); - /** - * The size is ANDed against 0x7F to strip the initial bit, then - * bitshifted by left $shift (4 or 7, depending on whether it's the - * initial byte) and ORed against the existing binary $size. This - * continuously increments the $size variable. - */ - $size |= (($byte & 0x7F) << $shift); - } - - return array($type, $size); - } - - /** - * Unpacks a deltified object located at $offset in $file - * - * @param handle $file File handle to read - * @param int $offset Offset of the object data - * @param int $object_offset Offset of the object data, past the header - * @param int $type The object type, either OBJ_REF_DELTA - or OBJ_OFS_DELTA - * @param int $size The expected size of the uncompressed data - * - * @return array Containing the type, size and object data - */ - private function _unpackDeltified($file, $offset, $object_offset, $type, $size) - { - fseek($file, $object_offset); - - if ($type == self::OBJ_REF_DELTA) { - - $base_sha = bin2hex(fread($file, 20)); - - $path = substr($this->_packfile, 0, strpos($this->_packfile, '.git')+5); - $base = Raw::factory($path, $base_sha); - $type = $base->type(); - $base = $base->content(); - - $delta = gzuncompress( - fread($file, $size + self::OBJ_PADDING), $size - ); - - $content = $this->_applyDelta($base, $delta); - - } elseif ($type == self::OBJ_OFS_DELTA) { - - // 20 = maximum varint size according to Glip - $data = fread($file, $size + self::OBJ_PADDING + 20); - - list($base_offset, $length) = $this->_bigEndianNumber($data); - - $delta = gzuncompress(substr($data, $length), $size); - unset($data); - - $base_offset = $offset - $base_offset; - list($type, $size, $base) = $this->_readPackedObject($base_offset); - - $content = $this->_applyDelta($base, $delta); - - } else { - throw new UnexpectedValueException( - "Unknown type $type for deltified object" - ); - } - - return array($type, strlen($content), $content); - } - - /** - * Applies the $delta byte-sequence to $base and returns the - * resultant binary string. - * - * This code is modified from Grit (see below), the Ruby - * implementation used for GitHub under an MIT license. - * - * @param string $base The base string for the delta to be applied to - * @param string $delta The delta string to apply - * - * @return string The patched binary string - * @see - * https://github.com/mojombo/grit/blob/master/lib/grit/git-ruby/internal/pack.rb - */ - private function _applyDelta($base, $delta) - { - $pos = 0; - $src_size = $this->_varint($delta, $pos); - $dst_size = $this->_varint($delta, $pos); - - if ($src_size !== strlen($base)) { - throw new UnexpectedValueException( - 'Expected base delta size ' . strlen($base) . ' does not match the expected ' - . "value $src_size" - ); - } - - $dest = ""; - while ($pos < strlen($delta)) { - $byte = ord($delta{$pos++}); - - if ($byte & 0x80) { - /* copy a part of $base */ - $offset = 0; - if ($byte & 0x01) $offset = ord($delta{$pos++}); - if ($byte & 0x02) $offset |= ord($delta{$pos++}) << 8; - if ($byte & 0x04) $offset |= ord($delta{$pos++}) << 16; - if ($byte & 0x08) $offset |= ord($delta{$pos++}) << 24; - $length = 0; - if ($byte & 0x10) $length = ord($delta{$pos++}); - if ($byte & 0x20) $length |= ord($delta{$pos++}) << 8; - if ($byte & 0x40) $length |= ord($delta{$pos++}) << 16; - if ($length == 0) $length = 0x10000; - $dest .= substr($base, $offset, $length); - } else { - /* take the next $byte bytes as they are */ - $dest .= substr($delta, $pos, $byte); - $pos += $byte; - } - } - - if (strlen($dest) !== $dst_size) { - throw new UnexpectedValueException( - "Deltified string expected to be $dst_size bytes, but actually " - . strlen($dest) . ' bytes' - ); - } - - return $dest; - } - - /** - * Parse a Git varint (variable-length integer). Used in the `_applyDelta()` - * method to read the delta header. - * - * @param string $string The string to parse - * @param int &$pos The position in the string to read from - * - * @return int The integer value - */ - private function _varint($string, &$pos = 0) - { - $varint = 0; - $bitmask = 0x80; - for ($i = 0; $bitmask & 0x80; $i += 7) { - $bitmask = ord($string{$pos++}); - $varint |= (($bitmask & 0x7F) << $i); - } - return $varint; - } - - /** - * Decodes a big endian modified base 128 number (refer to @see tag); this only - * appears to be used in one place, the offset delta in packfiles. The offset - * is the number of bytes to seek back from the start of the delta object to find - * the base object. - * - * This code has been implemented using the C code given in the @see tag below. - * - * @param string &$data The data to read from and decode the number - * - * @return Array Containing the base offset (number of bytes to seek back) and - * the length to use when reading the delta - * @see http://git.rsbx.net/Documents/Git_Data_Formats.txt - */ - private function _bigEndianNumber(&$data) - { - $i = 0; - $byte = ord($data{$i++}); - $number = $byte & 0x7F; - while ($byte & 0x80) { - $byte = ord($data{$i++}); - $number = (($number + 1) << 7) | ($byte & 0x7F); - } - - return array($number, $i); - } - -} diff --git a/3rdparty/granite/git/object/raw.php b/3rdparty/granite/git/object/raw.php deleted file mode 100644 index 56f363c37b2..00000000000 --- a/3rdparty/granite/git/object/raw.php +++ /dev/null @@ -1,153 +0,0 @@ - - * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License - * @link http://craig0990.github.com/Granite/ - */ - -namespace Granite\Git\Object; -use \InvalidArgumentException as InvalidArgumentException; - -/** - * Raw represents a raw Git object, using Index to locate - * packed objects. - * - * @category Git - * @package Granite - * @author Craig Roberts - * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License - * @link http://craig0990.github.com/Granite/ - */ -class Raw -{ - /** - * Integer values for Git objects - * @see http://book.git-scm.com/7_the_packfile.html - */ - const OBJ_COMMIT = 1; - const OBJ_TREE = 2; - const OBJ_BLOB = 3; - const OBJ_TAG = 4; - const OBJ_OFS_DELTA = 6; - const OBJ_REF_DELTA = 7; - - /** - * The SHA-1 id of the requested object - */ - protected $sha; - /** - * The type of the requested object (see class constants) - */ - protected $type; - /** - * The binary string content of the requested object - */ - protected $content; - - /** - * Returns an instance of a raw Git object - * - * @param string $path The path to the repository root - * @param string $sha The SHA-1 id of the requested object - * - * @return Packed|Loose - */ - public static function factory($path, $sha) - { - $loose_path = $path - . 'objects/' - . substr($sha, 0, 2) - . '/' - . substr($sha, 2); - if (file_exists($loose_path)) { - return new Loose($path, $sha); - } else { - return self::_findPackedObject($path, $sha); - } - } - - /** - * Returns the raw content of the Git object requested - * - * @return string Raw object content - */ - public function content() - { - return $this->content; - } - - /** - * Returns the size of the Git object - * - * @return int The size of the object in bytes - */ - public function size() - { - return strlen($this->content); - } - - /** - * Returns the type of the object as either commit, tag, blob or tree - * - * @return string The object type - */ - public function type() - { - return $this->type; - } - - /** - * Searches a packfile for the SHA id and reads the object from the packfile - * - * @param string $path The path to the repository - * @param string $sha The SHA-1 id of the object being requested - * - * @throws \InvalidArgumentException - * @return array An array containing the type, size and object data - */ - private static function _findPackedObject($path, $sha) - { - $packfiles = glob( - $path - . 'objects' - . DIRECTORY_SEPARATOR - . 'pack' - . DIRECTORY_SEPARATOR - . 'pack-*.pack' - ); - - $offset = false; - foreach ($packfiles as $packfile) { - $packname = substr(basename($packfile, '.pack'), 5); - $idx = new Index($path, $packname); - $offset = $idx->find($sha); - - if ($offset !== false) { - break; // Found it - } - } - - if ($offset == false) { - throw new InvalidArgumentException("Could not find packed object $sha"); - } - - $packname = $path - . 'objects' - . DIRECTORY_SEPARATOR - . 'pack' - . DIRECTORY_SEPARATOR - . 'pack-' . $packname . '.pack'; - $object = new Packed($packname, $offset); - - return $object; - } - -} - -?> diff --git a/3rdparty/granite/git/repository.php b/3rdparty/granite/git/repository.php deleted file mode 100644 index 30b58a39f5a..00000000000 --- a/3rdparty/granite/git/repository.php +++ /dev/null @@ -1,293 +0,0 @@ - - * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License - * @link http://craig0990.github.com/Granite/ - */ - -namespace Granite\Git; -use \InvalidArgumentException as InvalidArgumentException; -use \UnexpectedValueException as UnexpectedValueException; - -/** - * Repository represents a Git repository, providing a variety of methods for - * fetching objects from SHA-1 ids or the tip of a branch with `head()` - * - * @category Git - * @package Granite - * @author Craig Roberts - * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License - * @link http://craig0990.github.com/Granite/ - */ -class Repository -{ - - /** - * The path to the repository root - */ - private $_path; - /** - * The indexed version of a commit, ready to write with `commit()` - */ - private $idx_commit; - /** - * The indexed version of a tree, modified to with `add()` and `remove()` - */ - private $idx_tree; - - /** - * Sets the repository path - * - * @param string $path The path to the repository root (i.e. /repo/.git/) - */ - public function __construct($path) - { - if (!is_dir($path)) { - throw new InvalidArgumentException("Unable to find directory $path"); - } elseif (!is_readable($path)) { - throw new InvalidArgumentException("Unable to read directory $path"); - } elseif (!is_dir($path . DIRECTORY_SEPARATOR . 'objects') - || !is_dir($path . DIRECTORY_SEPARATOR . 'refs') - ) { - throw new UnexpectedValueException( - "Invalid directory, could not find 'objects' or 'refs' in $path" - ); - } - - $this->_path = $path; - $this->idx_commit = $this->factory('commit'); - $this->idx_tree = $this->factory('tree'); - } - - /** - * Returns an object from the Repository of the given type, with the given - * SHA-1 id, or false if it cannot be found - * - * @param string $type The type (blob, commit, tag or tree) of object being - * requested - * @param string $sha The SHA-1 id of the object (or the name of a tag) - * - * @return Blob|Commit|Tag|Tree - */ - public function factory($type, $sha = null) - { - if (!in_array($type, array('blob', 'commit', 'tag', 'tree'))) { - throw new InvalidArgumentException("Invalid type: $type"); - } - - if ($type == 'tag') { - $sha = $this->_ref('tags' . DIRECTORY_SEPARATOR . $sha); - } - $type = 'Granite\\Git\\' . ucwords($type); - - return new $type($this->_path, $sha); - } - - /** - * Returns a Commit object representing the HEAD commit - * - * @param string $branch The branch name to lookup, defaults to 'master' - * - * @return Commit An object representing the HEAD commit - */ - public function head($branch = 'master', $value = NULL) - { - if ($value == NULL) - return $this->factory( - 'commit', $this->_ref('heads' . DIRECTORY_SEPARATOR . $branch) - ); - - file_put_contents( - $this->_path . DIRECTORY_SEPARATOR - . 'refs' . DIRECTORY_SEPARATOR - . 'heads' . DIRECTORY_SEPARATOR . 'master', - $value - ); - } - - /** - * Returns a string representing the repository's location, which may or may - * not be initialised - * - * @return string A string representing the repository's location - */ - public function path() - { - return $this->_path; - } - - /** - * Returns an array of the local branches under `refs/heads` - * - * @return array - */ - public function tags() - { - return $this->_refs('tags'); - } - - /** - * Returns an array of the local tags under `refs/tags` - * - * @return array - */ - public function branches() - { - return $this->_refs('heads'); - } - - private function _refs($type) - { - $dir = $this->_path . 'refs' . DIRECTORY_SEPARATOR . $type; - $refs = glob($dir . DIRECTORY_SEPARATOR . '*'); - foreach ($refs as &$ref) { - $ref = basename($ref); - } - return $refs; - } - - /** - * Initialises a Git repository - * - * @return boolean Returns true on success, false on error - */ - public static function init($path) - { - $path .= '/'; - if (!is_dir($path)) { - mkdir($path); - } elseif (is_dir($path . 'objects')) { - return false; - } - - mkdir($path . 'objects'); - mkdir($path . 'objects/info'); - mkdir($path . 'objects/pack'); - mkdir($path . 'refs'); - mkdir($path . 'refs/heads'); - mkdir($path . 'refs/tags'); - - file_put_contents($path . 'HEAD', 'ref: refs/heads/master'); - - return true; - } - - /** - * Writes the indexed commit to disk, with blobs added/removed via `add()` and - * `rm()` - * - * @param string $message The commit message - * @param string $author The author name - * - * @return boolean True on success, or false on failure - */ - public function commit($message, $author) - { - $user_string = $username . ' ' . time() . ' +0000'; - - try { - $parents = array($this->repo->head()->sha()); - } catch (InvalidArgumentException $e) { - $parents = array(); - } - - $this->idx_commit->message($message); - $this->idx_commit->author($user_string); - $this->idx_commit->committer($user_string); - $this->idx_commit->tree($this->idx_tree); - $commit->parents($parents); - - $this->idx_tree->write(); - $this->idx_commit->write(); - - $this->repo->head('master', $this->idx_commit->sha()); - - $this->idx_commit = $this->factory('commit'); - $this->idx_tree = $this->factory('tree'); - } - - /** - * Adds a file to the indexed commit, to be written to disk with `commit()` - * - * @param string $filename The filename to save it under - * @param Granite\Git\Blob $blob The raw blob object to add to the tree - */ - public function add($filename, Granite\Git\Blob $blob) - { - $blob->write(); - $nodes = $this->idx_tree->nodes(); - $nodes[$filename] = new Granite\Git\Tree\Node($filename, '100644', $blob->sha()); - $this->idx_tree->nodes($nodes); - } - - /** - * Removes a file from the indexed commit - */ - public function rm($filename) - { - $nodes = $this->idx_tree->nodes(); - unset($nodes[$filename]); - $this->idx_tree->nodes($nodes); - } - - /** - * Returns an SHA-1 id of the ref resource - * - * @param string $ref The ref name to lookup - * - * @return string An SHA-1 id of the ref resource - */ - private function _ref($ref) - { - // All refs are stored in `.git/refs` - $file = $this->_path . 'refs' . DIRECTORY_SEPARATOR . $ref; - - if (file_exists($file)) { - return trim(file_get_contents($file)); - } - - $sha = $this->_packedRef($ref); - - if ($sha == false) { - throw new InvalidArgumentException("The ref $ref could not be found"); - } - - return $sha; - } - - /** - * Returns an SHA-1 id of the ref resource, or false if it cannot be found - * - * @param string $ref The ref name to lookup - * - * @return string An SHA-1 id of the ref resource - */ - private function _packedRef($ref) - { - $sha = false; - if (file_exists($this->_path . 'packed-refs')) { - $file = fopen($this->_path . 'packed-refs', 'r'); - - while (($line = fgets($file)) !== false) { - $info = explode(' ', $line); - if (count($info) == 2 - && trim($info[1]) == 'refs' . DIRECTORY_SEPARATOR . $ref - ) { - $sha = trim($info[0]); - break; - } - } - - fclose($file); - } - - return $sha; - } - -} diff --git a/3rdparty/granite/git/tag.php b/3rdparty/granite/git/tag.php deleted file mode 100644 index e26ddaffa6d..00000000000 --- a/3rdparty/granite/git/tag.php +++ /dev/null @@ -1,38 +0,0 @@ - - * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License - * @link http://craig0990.github.com/Granite/ - */ - -namespace Granite\Git; - -/** - * Tag represents a full tag object - * - * @category Git - * @package Granite - * @author Craig Roberts - * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License - * @link http://craig0990.github.com/Granite/ - */ -class Tag -{ - - public function __construct($path, $sha) - { - $this->sha = $sha; - } - - public function sha() - { - return $this->sha; - } - -} diff --git a/3rdparty/granite/git/tree.php b/3rdparty/granite/git/tree.php deleted file mode 100644 index 2de72274532..00000000000 --- a/3rdparty/granite/git/tree.php +++ /dev/null @@ -1,198 +0,0 @@ - - * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License - * @link http://craig0990.github.com/Granite/ - */ - -namespace Granite\Git; -use \Granite\Git\Tree\Node as Node; - -/** - * Tree represents a full tree object, with nodes pointing to other tree objects - * and file blobs - * - * @category Git - * @package Granite - * @author Craig Roberts - * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License - * @link http://craig0990.github.com/Granite/ - */ -class Tree -{ - - /** - * The SHA-1 id of the requested tree - */ - private $sha; - /** - * The nodes/entries for the requested tree - */ - private $nodes = array(); - /** - * The path to the repository - */ - private $path; - - /** - * Reads a tree object by fetching the raw object - * - * @param string $path The path to the repository root - * @param string $sha The SHA-1 id of the requested object - */ - public function __construct($path, $sha = NULL, $dbg = FALSE) - { - $this->path = $path; - if ($sha !== NULL) { - $object = Object\Raw::factory($path, $sha); - $this->sha = $sha; - - if ($object->type() !== Object\Raw::OBJ_TREE) { - throw new \InvalidArgumentException( - "The object $sha is not a tree, type is " . $object->type() - ); - } - - $content = $object->content(); - file_put_contents('/tmp/tree_from_real_repo'.time(), $content); - $nodes = array(); - - for ($i = 0; $i < strlen($content); $i = $data_start + 21) { - $data_start = strpos($content, "\0", $i); - $info = substr($content, $i, $data_start-$i); - list($mode, $name) = explode(' ', $info, 2); - // Read the object SHA-1 id - $sha = bin2hex(substr($content, $data_start + 1, 20)); - - $this->nodes[$name] = new Node($name, $mode, $sha); - } - } - } - - /** - * Returns an array of Tree and Granite\Git\Blob objects, - * representing subdirectories and files - * - * @return array Array of Tree and Granite\Git\Blob objects - */ - public function nodes($nodes = null) - { - if ($nodes == null) { - return $this->nodes; - } - $this->nodes = $nodes; - } - - /** - * Adds a blob or a tree to the list of nodes - * - * @param string $name The basename (filename) of the blob or tree - * @param string $mode The mode of the blob or tree (see above) - * @param string $sha The SHA-1 id of the blob or tree to add - */ - public function add($name, $mode, $sha) - { - $this->nodes[$name] = new Node($name, $mode, $sha); - uasort($this->nodes, array($this, '_sort')); - } - - public function write() - { - $sha = $this->sha(); - $path = $this->path - . 'objects' - . DIRECTORY_SEPARATOR - . substr($sha, 0, 2) - . DIRECTORY_SEPARATOR - . substr($sha, 2); - // FIXME: currently writes loose objects only - if (file_exists($path)) { - return FALSE; - } - - if (!is_dir(dirname($path))) { - mkdir(dirname($path), 0777, TRUE); - } - - $loose = fopen($path, 'wb'); - $data = $this->_raw(); - $data = 'tree ' . strlen($data) . "\0" . $data; - $write = fwrite($loose, gzcompress($data)); - fclose($loose); - - return ($write !== FALSE); - } - - /** - * Returns the SHA-1 id of the Tree - * - * @return string SHA-1 id of the Tree - */ - public function sha() - { - $data = $this->_raw(); - $raw = 'tree ' . strlen($data) . "\0" . $data; - $this->sha = hash('sha1', $raw); - return $this->sha; - } - - /** - * Generates the raw object content to be saved to disk - */ - public function _raw() - { - uasort($this->nodes, array($this, '_sort')); - $data = ''; - foreach ($this->nodes as $node) - { - $data .= base_convert($node->mode(), 10, 8) . ' ' . $node->name() . "\0"; - $data .= pack('H40', $node->sha()); - } - file_put_contents('/tmp/tree_made'.time(), $data); - return $data; - } - - /** - * Sorts the node entries in a tree, general sort method adapted from original - * Git C code (see @see tag below). - * - * @return 1, 0 or -1 if the first entry is greater than, the same as, or less - * than the second, respectively. - * @see https://github.com/gitster/git/blob/master/read-cache.c Around line 352, - * the `base_name_compare` function - */ - public function _sort(&$a, &$b) - { - $length = strlen($a->name()) < strlen($b->name()) ? strlen($a->name()) : strlen($b->name()); - - $cmp = strncmp($a->name(), $b->name(), $length); - if ($cmp) { - return $cmp; - } - - $suffix1 = $a->name(); - $suffix1 = (strlen($suffix1) > $length) ? $suffix1{$length} : FALSE; - $suffix2 = $b->name(); - $suffix2 = (strlen($suffix2) > $length) ? $suffix2{$length} : FALSE; - if (!$suffix1 && $a->isDirectory()) { - $suffix1 = '/'; - } - if (!$suffix2 && $b->isDirectory()) { - $suffix2 = '/'; - } - if ($suffix1 < $suffix2) { - return -1; - } elseif ($suffix1 > $suffix2) { - return 1; - } - - return 0; - } - -} diff --git a/3rdparty/granite/git/tree/node.php b/3rdparty/granite/git/tree/node.php deleted file mode 100644 index f99eb1ae281..00000000000 --- a/3rdparty/granite/git/tree/node.php +++ /dev/null @@ -1,126 +0,0 @@ - - * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License - * @link http://craig0990.github.com/Granite/ - */ - -namespace Granite\Git\Tree; - -/** - * Node represents an entry in a Tree - * - * @category Git - * @package Granite - * @author Craig Roberts - * @license http://www.opensource.org/licenses/mit-license.php MIT Expat License - * @link http://craig0990.github.com/Granite/ - */ -class Node -{ - - /** - * Name of the file, directory or submodule - */ - private $_name; - /** - * Mode of the object, in octal - */ - private $_mode; - /** - * SHA-1 id of the tree - */ - private $_sha; - /** - * Boolean value for whether the entry represents a directory - */ - private $_is_dir; - /** - * Boolean value for whether the entry represents a submodule - */ - private $_is_submodule; - - /** - * Sets up a Node class with properties corresponding to the $mode parameter - * - * @param string $name The name of the object (file, directory or submodule name) - * @param int $mode The mode of the object, retrieved from the repository - * @param string $sha The SHA-1 id of the object - */ - public function __construct($name, $mode, $sha) - { - $this->_name = $name; - $this->_mode = intval($mode, 8); - $this->_sha = $sha; - - $this->_is_dir = (bool) ($this->_mode & 0x4000); - $this->_is_submodule = ($this->_mode == 0xE000); - } - - /** - * Returns a boolean value indicating whether the node is a directory - * - * @return boolean - */ - public function isDirectory() - { - return $this->_is_dir; - } - - /** - * Returns a boolean value indicating whether the node is a submodule - * - * @return boolean - */ - public function isSubmodule() - { - return $this->_is_submodule; - } - - /** - * Returns the object name - * - * @return string - */ - public function name() - { - return $this->_name; - } - - /** - * Returns the object's SHA-1 id - * - * @return string - */ - public function sha() - { - return $this->_sha; - } - - /** - * Returns the octal value of the file mode - * - * @return int - */ - public function mode() - { - return $this->_mode; - } - - public function type() - { - if ($this->isDirectory()) { - return 'tree'; - } elseif ($this->isSubmodule()) { - return 'commit'; - } else { - return 'blob'; - } - } -}
t('New Address Book') ?> + t('Import from VCF') ?>
t("CardDav Link") . "\" class=\"action\">t("Download") . "\" class=\"action\">t("Edit") . "\" class=\"action\" onclick=\"Contacts.UI.Addressbooks.editAddressbook(this, " . $_['addressbook']["id"] . ");\">t("Delete") . "\" class=\"action\">t("CardDav Link") . "\" class=\"action\">t("Download") . "\" class=\"action\">t("Edit") . "\" class=\"action\" onclick=\"Contacts.UI.Addressbooks.editAddressbook(this, " . $_['addressbook']["id"] . ");\">t("Delete") . "\" class=\"action\">