diff options
25 files changed, 423 insertions, 143 deletions
diff --git a/apps/files/ajax/list.php b/apps/files/ajax/list.php index bae3628402f..b4641343ed4 100644 --- a/apps/files/ajax/list.php +++ b/apps/files/ajax/list.php @@ -2,29 +2,54 @@ OCP\JSON::checkLoggedIn(); \OC::$session->close(); +$l = OC_L10N::get('files'); // Load the files -$dir = isset( $_GET['dir'] ) ? $_GET['dir'] : ''; +$dir = isset($_GET['dir']) ? $_GET['dir'] : ''; $dir = \OC\Files\Filesystem::normalizePath($dir); -$dirInfo = \OC\Files\Filesystem::getFileInfo($dir); -if (!$dirInfo || !$dirInfo->getType() === 'dir') { - header("HTTP/1.0 404 Not Found"); - exit(); -} - -$data = array(); -$baseUrl = OCP\Util::linkTo('files', 'index.php') . '?dir='; - -$permissions = $dirInfo->getPermissions(); - -$sortAttribute = isset( $_GET['sort'] ) ? $_GET['sort'] : 'name'; -$sortDirection = isset( $_GET['sortdirection'] ) ? ($_GET['sortdirection'] === 'desc') : false; -// make filelist -$files = \OCA\Files\Helper::getFiles($dir, $sortAttribute, $sortDirection); - -$data['directory'] = $dir; -$data['files'] = \OCA\Files\Helper::formatFileInfos($files); -$data['permissions'] = $permissions; - -OCP\JSON::success(array('data' => $data)); +try { + $dirInfo = \OC\Files\Filesystem::getFileInfo($dir); + if (!$dirInfo || !$dirInfo->getType() === 'dir') { + header("HTTP/1.0 404 Not Found"); + exit(); + } + + $data = array(); + $baseUrl = OCP\Util::linkTo('files', 'index.php') . '?dir='; + + $permissions = $dirInfo->getPermissions(); + + $sortAttribute = isset($_GET['sort']) ? $_GET['sort'] : 'name'; + $sortDirection = isset($_GET['sortdirection']) ? ($_GET['sortdirection'] === 'desc') : false; + + // make filelist + + $files = \OCA\Files\Helper::getFiles($dir, $sortAttribute, $sortDirection); + $data['directory'] = $dir; + $data['files'] = \OCA\Files\Helper::formatFileInfos($files); + $data['permissions'] = $permissions; + + OCP\JSON::success(array('data' => $data)); +} catch (\OCP\Files\StorageNotAvailableException $e) { + OCP\JSON::error(array( + 'data' => array( + 'exception' => '\OCP\Files\StorageNotAvailableException', + 'message' => $l->t('Storage not available') + ) + )); +} catch (\OCP\Files\StorageInvalidException $e) { + OCP\JSON::error(array( + 'data' => array( + 'exception' => '\OCP\Files\StorageInvalidException', + 'message' => $l->t('Storage invalid') + ) + )); +} catch (\Exception $e) { + OCP\JSON::error(array( + 'data' => array( + 'exception' => '\Exception', + 'message' => $l->t('Unknown error') + ) + )); +} diff --git a/apps/files/js/fileactions.js b/apps/files/js/fileactions.js index fc7c9ccacef..e06d2912274 100644 --- a/apps/files/js/fileactions.js +++ b/apps/files/js/fileactions.js @@ -181,11 +181,12 @@ return; } this.currentFile = parent; + var $tr = parent.closest('tr'); var self = this; var actions = this.getActions(this.getCurrentMimeType(), this.getCurrentType(), this.getCurrentPermissions()); var file = this.getCurrentFile(); var nameLinks; - if (parent.closest('tr').data('renaming')) { + if ($tr.data('renaming')) { return; } @@ -278,7 +279,7 @@ } if (triggerEvent){ - fileList.$fileList.trigger(jQuery.Event("fileActionsReady", {fileList: fileList})); + fileList.$fileList.trigger(jQuery.Event("fileActionsReady", {fileList: fileList, $files: $tr})); } }, getCurrentFile: function () { diff --git a/apps/files/js/filelist.js b/apps/files/js/filelist.js index 0477a657035..94f161943a1 100644 --- a/apps/files/js/filelist.js +++ b/apps/files/js/filelist.js @@ -473,6 +473,7 @@ /** * Appends the next page of files into the table * @param animate true to animate the new elements + * @return array of DOM elements of the newly added files */ _nextPage: function(animate) { var index = this.$fileList.children().length, @@ -483,7 +484,7 @@ isAllSelected = this.isAllSelected(); if (index >= this.files.length) { - return; + return false; } while (count > 0 && index < this.files.length) { @@ -496,12 +497,17 @@ } if (animate) { tr.addClass('appear transparent'); - newTrs.push(tr); } + newTrs.push(tr); index++; count--; } + // trigger event for newly added rows + if (newTrs.length > 0) { + this.$fileList.trigger($.Event('fileActionsReady', {fileList: this, $files: newTrs})); + } + if (animate) { // defer, for animation window.setTimeout(function() { @@ -510,6 +516,7 @@ } }, 0); } + return newTrs; }, /** @@ -518,9 +525,16 @@ */ _onFileActionsUpdated: function() { var self = this; - this.$fileList.find('tr td.filename').each(function() { - self.fileActions.display($(this), true, self); + var $files = this.$fileList.find('tr'); + if (!$files.length) { + return; + } + + $files.each(function() { + self.fileActions.display($(this).find('td.filename'), false, self); }); + this.$fileList.trigger($.Event('fileActionsReady', {fileList: this, $files: $files})); + }, /** @@ -532,7 +546,6 @@ // detach to make adding multiple rows faster this.files = filesArray; - this.$fileList.detach(); this.$fileList.empty(); // clear "Select all" checkbox @@ -541,10 +554,7 @@ this.isEmpty = this.files.length === 0; this._nextPage(); - this.$el.find('thead').after(this.$fileList); - this.updateEmptyContent(); - this.$fileList.trigger($.Event('fileActionsReady', {fileList: this})); this.fileSummary.calculate(filesArray); @@ -836,13 +846,18 @@ * @param {boolean} force set to true to force changing directory */ changeDirectory: function(targetDir, changeUrl, force) { + var self = this; var currentDir = this.getCurrentDirectory(); targetDir = targetDir || '/'; if (!force && currentDir === targetDir) { return; } this._setCurrentDir(targetDir, changeUrl); - this.reload(); + this.reload().then(function(success){ + if (!success) { + self.changeDirectory(currentDir, true); + } + }); }, linkTo: function(dir) { return OC.linkTo('files', 'index.php')+"?dir="+ encodeURIComponent(dir).replace(/%2F/g, '/'); @@ -902,7 +917,6 @@ * @brief Reloads the file list using ajax call */ reload: function() { - var self = this; this._selectedFiles = {}; this._selectionSummary.clear(); this.$el.find('.select-all').prop('checked', false); @@ -916,14 +930,10 @@ dir : this.getCurrentDirectory(), sort: this._sort, sortdirection: this._sortDirection - }, - error: function(result) { - self.reloadCallback(result); - }, - success: function(result) { - self.reloadCallback(result); } }); + var callBack = this.reloadCallback.bind(this); + return this._reloadCall.then(callBack, callBack); }, reloadCallback: function(result) { delete this._reloadCall; @@ -931,17 +941,17 @@ if (!result || result.status === 'error') { OC.Notification.show(result.data.message); - return; + return false; } if (result.status === 404) { // go back home this.changeDirectory('/'); - return; + return false; } // aborted ? if (result.status === 0){ - return; + return true; } // TODO: should rather return upload file size through @@ -953,6 +963,7 @@ } this.setFiles(result.data.files); + return true }, updateStorageStatistics: function(force) { @@ -1282,16 +1293,16 @@ // reinsert row self.files.splice(tr.index(), 1); tr.remove(); - self.add(fileInfo, {updateSummary: false, silent: true}); - self.$fileList.trigger($.Event('fileActionsReady', {fileList: self})); + tr = self.add(fileInfo, {updateSummary: false, silent: true}); + self.$fileList.trigger($.Event('fileActionsReady', {fileList: self, $files: $(tr)})); } }); } else { // add back the old file info when cancelled self.files.splice(tr.index(), 1); tr.remove(); - self.add(oldFileInfo, {updateSummary: false, silent: true}); - self.$fileList.trigger($.Event('fileActionsReady', {fileList: self})); + tr = self.add(oldFileInfo, {updateSummary: false, silent: true}); + self.$fileList.trigger($.Event('fileActionsReady', {fileList: self, $files: $(tr)})); } } catch (error) { input.attr('title', error); diff --git a/apps/files/tests/js/filelistSpec.js b/apps/files/tests/js/filelistSpec.js index a699177767a..713cd5468d8 100644 --- a/apps/files/tests/js/filelistSpec.js +++ b/apps/files/tests/js/filelistSpec.js @@ -797,13 +797,24 @@ describe('OCA.Files.FileList tests', function() { fileList.$fileList.on('fileActionsReady', handler); fileList.setFiles(testFiles); expect(handler.calledOnce).toEqual(true); + expect(handler.getCall(0).args[0].$files.length).toEqual(testFiles.length); }); it('triggers "fileActionsReady" event after single add', function() { var handler = sinon.stub(); + var $tr; fileList.setFiles(testFiles); fileList.$fileList.on('fileActionsReady', handler); - fileList.add({name: 'test.txt'}); + $tr = fileList.add({name: 'test.txt'}); + expect(handler.calledOnce).toEqual(true); + expect(handler.getCall(0).args[0].$files.is($tr)).toEqual(true); + }); + it('triggers "fileActionsReady" event after next page load with the newly appended files', function() { + var handler = sinon.stub(); + fileList.setFiles(generateFiles(0, 64)); + fileList.$fileList.on('fileActionsReady', handler); + fileList._nextPage(); expect(handler.calledOnce).toEqual(true); + expect(handler.getCall(0).args[0].$files.length).toEqual(fileList.pageSize); }); it('does not trigger "fileActionsReady" event after single add with silent argument', function() { var handler = sinon.stub(); @@ -1630,6 +1641,7 @@ describe('OCA.Files.FileList tests', function() { }); it('redisplays actions when new actions have been registered', function() { var actionStub = sinon.stub(); + var readyHandler = sinon.stub(); var clock = sinon.useFakeTimers(); var debounceStub = sinon.stub(_, 'debounce', function(callback) { return function() { @@ -1637,11 +1649,15 @@ describe('OCA.Files.FileList tests', function() { _.defer(callback); }; }); + // need to reinit the list to make the debounce call fileList.destroy(); fileList = new OCA.Files.FileList($('#app-content-files')); fileList.setFiles(testFiles); + + fileList.$fileList.on('fileActionsReady', readyHandler); + fileList.fileActions.register( 'text/plain', 'Test', @@ -1654,9 +1670,13 @@ describe('OCA.Files.FileList tests', function() { ); var $tr = fileList.findFileEl('One.txt'); expect($tr.find('.action-test').length).toEqual(0); + expect(readyHandler.notCalled).toEqual(true); + // update is delayed clock.tick(100); expect($tr.find('.action-test').length).toEqual(1); + expect(readyHandler.calledOnce).toEqual(true); + clock.restore(); debounceStub.restore(); }); diff --git a/apps/files_sharing/css/public.css b/apps/files_sharing/css/public.css index 31c3bca8748..ddd9d10d664 100644 --- a/apps/files_sharing/css/public.css +++ b/apps/files_sharing/css/public.css @@ -88,20 +88,6 @@ thead { max-width: 90%; } -.header-right { - transition: opacity 500ms ease 0s; - -moz-transition: opacity 500ms ease 0s; - -ms-transition: opacity 500ms ease 0s; - -o-transition: opacity 500ms ease 0s; - -webkit-transition: opacity 500ms ease 0s; -} - -.header-right:hover, .header-right.active { - opacity: 1; - -ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=100)"; - filter: alpha(opacity=100); -} - /* within #save */ #remote_address { margin: 0; diff --git a/apps/files_sharing/js/external.js b/apps/files_sharing/js/external.js index 1eb447da896..969a2b184d4 100644 --- a/apps/files_sharing/js/external.js +++ b/apps/files_sharing/js/external.js @@ -58,7 +58,7 @@ $(document).ready(function () { if (params.remote && params.token && params.owner && params.name) { // clear hash, it is unlikely that it contain any extra parameters location.hash = ''; - params.passwordProtected = parseInt(params.passwordProtected, 10) === 1; + params.passwordProtected = parseInt(params.protected, 10) === 1; OCA.Sharing.showAddExternalDialog( params.remote, params.token, diff --git a/apps/files_sharing/js/public.js b/apps/files_sharing/js/public.js index f911f3689eb..a5027da0cfb 100644 --- a/apps/files_sharing/js/public.js +++ b/apps/files_sharing/js/public.js @@ -165,7 +165,6 @@ OCA.Sharing.PublicApp = { $('#save > button').click(function () { $(this).hide(); - $('.header-right').addClass('active'); $('.save-form').css('display', 'inline'); $('#remote_address').focus(); }); diff --git a/apps/files_sharing/js/share.js b/apps/files_sharing/js/share.js index 2efed5310bc..e46be4ada46 100644 --- a/apps/files_sharing/js/share.js +++ b/apps/files_sharing/js/share.js @@ -36,54 +36,34 @@ } return tr; }; - - var oldRenderRow = OCA.Files.FileList.prototype._renderRow; - OCA.Files.FileList.prototype._renderRow = function(fileData) { - var $tr = oldRenderRow.apply(this, arguments); - // if the statuses are loaded already, use them for the icon - // (needed when scrolling to the next page) - var shareStatus = OC.Share.statuses[fileData.id]; - if (fileData.shareOwner || fileData.recipientsDisplayName || shareStatus) { - var permissions = $tr.data('permissions'); - var hasLink = !!(shareStatus && shareStatus.link); - if (permissions & OC.PERMISSION_SHARE) { - OC.Share.markFileAsShared($tr, true, hasLink); - } else { - // if no share action exists because the admin disabled sharing for this user - // we create a share notification action to inform the user about files - // shared with him otherwise we just update the existing share action. - // TODO: make this work like/with OC.Share.markFileAsShared() - var shareNotification = '<a class="action action-share-notification permanent"' + - ' data-action="Share-Notification" href="#" original-title="">' + - ' <img class="svg" src="' + OC.imagePath('core', 'actions/share') + '"></img>'; - $tr.find('.fileactions').append(function() { - var shareBy = t('files_sharing', 'Shared by {owner}', {owner: escapeHTML(fileData.shareOwner)}); - var $result = $(shareNotification + '<span> ' + shareBy + '</span></span>'); - $result.on('click', function() { - return false; - }); - return $result; - }); - } - } - return $tr; - }; } // use delegate to catch the case with multiple file lists - $('#content').delegate('#fileList', 'fileActionsReady',function(ev){ + $('#content').delegate('#fileList', 'fileActionsReady', function(ev){ var fileList = ev.fileList; + var $files = ev.$files; + + function updateIcons($files) { + if (!$files) { + // if none specified, update all + $files = fileList.$fileList.find('tr'); + } + _.each($files, function(file) { + OCA.Sharing.Util.updateFileActionIcon($(file)); + }); + } + if (!OCA.Sharing.sharesLoaded){ - OC.Share.loadIcons('file', fileList); + OC.Share.loadIcons('file', fileList, function() { + // since we don't know which files are affected, just refresh them all + updateIcons(); + }); // assume that we got all shares, so switching directories // will not invalidate that list OCA.Sharing.sharesLoaded = true; } else{ - // this will update the icons for all the currently visible elements - // additionally added elements when scrolling down will be - // updated in the _renderRow override - OC.Share.updateIcons('file', fileList); + updateIcons($files); } }); @@ -141,6 +121,40 @@ }, /** + * Update the file action share icon for the given file + * + * @param $tr file element of the file to update + */ + updateFileActionIcon: function($tr) { + // if the statuses are loaded already, use them for the icon + // (needed when scrolling to the next page) + var shareStatus = OC.Share.statuses[$tr.data('id')]; + if (shareStatus || $tr.attr('data-share-recipients') || $tr.attr('data-share-owner')) { + var permissions = $tr.data('permissions'); + var hasLink = !!(shareStatus && shareStatus.link); + OC.Share.markFileAsShared($tr, true, hasLink); + if ((permissions & OC.PERMISSION_SHARE) === 0) { + // if no share action exists because the admin disabled sharing for this user + // we create a share notification action to inform the user about files + // shared with him otherwise we just update the existing share action. + // TODO: make this work like/with OC.Share.markFileAsShared() + $tr.find('.fileactions .action-share-notification').remove(); + var shareNotification = '<a class="action action-share-notification permanent"' + + ' data-action="Share-Notification" href="#" original-title="">' + + ' <img class="svg" src="' + OC.imagePath('core', 'actions/share') + '"></img>'; + $tr.find('.fileactions').append(function() { + var shareBy = t('files_sharing', 'Shared by {owner}', {owner: escapeHTML($tr.attr('data-share-owner'))}); + var $result = $(shareNotification + '<span> ' + shareBy + '</span></span>'); + $result.on('click', function() { + return false; + }); + return $result; + }); + } + } + }, + + /** * Formats a recipients array to be displayed. * The first four recipients will be shown and the * other ones will be shown as "+x" where "x" is the number of diff --git a/apps/files_sharing/lib/external/manager.php b/apps/files_sharing/lib/external/manager.php index af317cc2027..dda283f4952 100644 --- a/apps/files_sharing/lib/external/manager.php +++ b/apps/files_sharing/lib/external/manager.php @@ -113,7 +113,9 @@ class Manager { * @return Mount */ protected function mountShare($data) { + $data['manager'] = $this; $mountPoint = '/' . $this->userSession->getUser()->getUID() . '/files' . $data['mountpoint']; + $data['mountpoint'] = $mountPoint; $mount = new Mount(self::STORAGE, $mountPoint, $data, $this, $this->storageLoader); $this->mountManager->addMount($mount); return $mount; diff --git a/apps/files_sharing/lib/external/scanner.php b/apps/files_sharing/lib/external/scanner.php index 8921dd1a4c0..4dc5d4be9d8 100644 --- a/apps/files_sharing/lib/external/scanner.php +++ b/apps/files_sharing/lib/external/scanner.php @@ -19,23 +19,7 @@ class Scanner extends \OC\Files\Cache\Scanner { } public function scanAll() { - $remote = $this->storage->getRemote(); - $token = $this->storage->getToken(); - $password = $this->storage->getPassword(); - $url = $remote . '/index.php/apps/files_sharing/shareinfo?t=' . $token; - - $ch = curl_init(); - - curl_setopt($ch, CURLOPT_URL, $url); - curl_setopt($ch, CURLOPT_POST, 1); - curl_setopt($ch, CURLOPT_POSTFIELDS, - http_build_query(array('password' => $password))); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - - $result = curl_exec($ch); - curl_close($ch); - - $data = json_decode($result, true); + $data = $this->storage->getShareInfo(); if ($data['status'] === 'success') { $this->addResult($data['data'], ''); } else { diff --git a/apps/files_sharing/lib/external/storage.php b/apps/files_sharing/lib/external/storage.php index 454196f15ae..3a0de51192e 100644 --- a/apps/files_sharing/lib/external/storage.php +++ b/apps/files_sharing/lib/external/storage.php @@ -10,7 +10,11 @@ namespace OCA\Files_Sharing\External; use OC\Files\Filesystem; use OC\Files\Storage\DAV; +use OC\ForbiddenException; use OCA\Files_Sharing\ISharedStorage; +use OCP\Files\NotFoundException; +use OCP\Files\StorageInvalidException; +use OCP\Files\StorageNotAvailableException; class Storage extends DAV implements ISharedStorage { /** @@ -35,7 +39,13 @@ class Storage extends DAV implements ISharedStorage { private $updateChecked = false; + /** + * @var \OCA\Files_Sharing\External\Manager + */ + private $manager; + public function __construct($options) { + $this->manager = $options['manager']; $this->remote = $options['remote']; $this->remoteUser = $options['owner']; list($protocol, $remote) = explode('://', $this->remote); @@ -108,6 +118,8 @@ class Storage extends DAV implements ISharedStorage { * * @param string $path * @param int $time + * @throws \OCP\Files\StorageNotAvailableException + * @throws \OCP\Files\StorageInvalidException * @return bool */ public function hasUpdated($path, $time) { @@ -117,6 +129,77 @@ class Storage extends DAV implements ISharedStorage { return false; } $this->updateChecked = true; - return parent::hasUpdated('', $time); + try { + return parent::hasUpdated('', $time); + } catch (StorageNotAvailableException $e) { + // see if we can find out why the share is unavailable\ + try { + $this->getShareInfo(); + } catch (NotFoundException $shareException) { + // a 404 can either mean that the share no longer exists or there is no ownCloud on the remote + if ($this->testRemote()) { + // valid ownCloud instance means that the public share no longer exists + // since this is permanent (re-sharing the file will create a new token) + // we remove the invalid storage + $this->manager->removeShare($this->mountPoint); + $this->manager->getMountManager()->removeMount($this->mountPoint); + throw new StorageInvalidException(); + } else { + // ownCloud instance is gone, likely to be a temporary server configuration error + throw $e; + } + } catch(\Exception $shareException) { + // todo, maybe handle 403 better and ask the user for a new password + throw $e; + } + throw $e; + } + } + + /** + * check if the configured remote is a valid ownCloud instance + * + * @return bool + */ + protected function testRemote() { + try { + $result = file_get_contents($this->remote . '/status.php'); + $data = json_decode($result); + return is_object($data) and !empty($data->version); + } catch (\Exception $e) { + return false; + } + } + + public function getShareInfo() { + $remote = $this->getRemote(); + $token = $this->getToken(); + $password = $this->getPassword(); + $url = $remote . '/index.php/apps/files_sharing/shareinfo?t=' . $token; + + $ch = curl_init(); + + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_POST, 1); + curl_setopt($ch, CURLOPT_POSTFIELDS, + http_build_query(array('password' => $password))); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + + $result = curl_exec($ch); + + $status = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + switch ($status) { + case 401: + case 403: + throw new ForbiddenException(); + case 404: + throw new NotFoundException(); + case 500: + throw new \Exception(); + } + + return json_decode($result, true); } } diff --git a/apps/files_sharing/lib/updater.php b/apps/files_sharing/lib/updater.php index e114c3ba0ac..aac4ed196de 100644 --- a/apps/files_sharing/lib/updater.php +++ b/apps/files_sharing/lib/updater.php @@ -109,6 +109,7 @@ class Shared_Updater { static public function renameHook($params) { self::correctFolders($params['newpath']); self::correctFolders(pathinfo($params['oldpath'], PATHINFO_DIRNAME)); + self::renameChildren($params['oldpath'], $params['newpath']); } /** @@ -209,4 +210,26 @@ class Shared_Updater { $findAndRemoveShares->execute(array()); } + /** + * rename mount point from the children if the parent was renamed + * + * @param string $oldPath old path relative to data/user/files + * @param string $newPath new path relative to data/user/files + */ + static private function renameChildren($oldPath, $newPath) { + + $absNewPath = \OC\Files\Filesystem::normalizePath('/' . \OCP\User::getUser() . '/files/' . $newPath); + $absOldPath = \OC\Files\Filesystem::normalizePath('/' . \OCP\User::getUser() . '/files/' . $oldPath); + + $mountManager = \OC\Files\Filesystem::getMountManager(); + $mountedShares = $mountManager->findIn('/' . \OCP\User::getUser() . '/files/' . $oldPath); + foreach ($mountedShares as $mount) { + if ($mount->getStorage()->instanceOfStorage('OCA\Files_Sharing\ISharedStorage')) { + $mountPoint = $mount->getMountPoint(); + $target = str_replace($absOldPath, $absNewPath, $mountPoint); + $mount->moveMount($target); + } + } + } + } diff --git a/apps/files_sharing/templates/public.php b/apps/files_sharing/templates/public.php index c053aaabece..65e620a3425 100644 --- a/apps/files_sharing/templates/public.php +++ b/apps/files_sharing/templates/public.php @@ -17,7 +17,7 @@ <div class="header-right"> <span id="details"> <span id="save" data-protected="<?php p($_['protected'])?>" data-owner="<?php p($_['displayName'])?>" data-name="<?php p($_['filename'])?>"> - <button><?php p($l->t('Save to ownCloud')) ?></button> + <button><?php p($l->t('Add to your ownCloud')) ?></button> <form class="save-form hidden" action="#"> <input type="text" id="remote_address" placeholder="<?php p($l->t('example.com/owncloud')) ?>"/> <input type="submit" value="<?php p($l->t('Save')) ?>"/> diff --git a/apps/files_sharing/tests/externalstorage.php b/apps/files_sharing/tests/externalstorage.php index 60c5787f295..1258148af53 100644 --- a/apps/files_sharing/tests/externalstorage.php +++ b/apps/files_sharing/tests/externalstorage.php @@ -72,6 +72,7 @@ class Test_Files_Sharing_External_Storage extends \PHPUnit_Framework_TestCase { 'mountpoint' => 'remoteshare', 'token' => 'abcdef', 'password' => '', + 'manager' => null ) ); $this->assertEquals($baseUri, $storage->getBaseUri()); diff --git a/apps/files_sharing/tests/js/shareSpec.js b/apps/files_sharing/tests/js/shareSpec.js index 3d4fc754821..600859b4e7d 100644 --- a/apps/files_sharing/tests/js/shareSpec.js +++ b/apps/files_sharing/tests/js/shareSpec.js @@ -80,6 +80,14 @@ describe('OCA.Sharing.Util tests', function() { // TODO: test data-permissions, data-share-owner, etc }); describe('Share action icon', function() { + beforeEach(function() { + OC.Share.statuses = {1: {link: false, path: '/subdir'}}; + OCA.Sharing.sharesLoaded = true; + }); + afterEach(function() { + OC.Share.statuses = {}; + OCA.Sharing.sharesLoaded = false; + }); it('do not shows share text when not shared', function() { var $action, $tr; OC.Share.statuses = {}; diff --git a/apps/files_sharing/tests/updater.php b/apps/files_sharing/tests/updater.php index cdb44068254..5ec53488702 100644 --- a/apps/files_sharing/tests/updater.php +++ b/apps/files_sharing/tests/updater.php @@ -217,4 +217,40 @@ class Test_Files_Sharing_Updater extends Test_Files_Sharing_Base { } + /** + * if a folder gets renamed all children mount points should be renamed too + */ + function testRename() { + + $fileinfo = \OC\Files\Filesystem::getFileInfo($this->folder); + $result = \OCP\Share::shareItem('folder', $fileinfo->getId(), \OCP\Share::SHARE_TYPE_USER, self::TEST_FILES_SHARING_API_USER2, 31); + $this->assertTrue($result); + + $this->loginHelper(self::TEST_FILES_SHARING_API_USER2); + + // make sure that the shared folder exists + $this->assertTrue(\OC\Files\Filesystem::file_exists($this->folder)); + + \OC\Files\Filesystem::mkdir('oldTarget'); + \OC\Files\Filesystem::mkdir('oldTarget/subfolder'); + \OC\Files\Filesystem::mkdir('newTarget'); + + \OC\Files\Filesystem::rename($this->folder, 'oldTarget/subfolder/' . $this->folder); + + // re-login to make sure that the new mount points are initialized + $this->loginHelper(self::TEST_FILES_SHARING_API_USER2); + + \OC\Files\Filesystem::rename('/oldTarget', '/newTarget/oldTarget'); + + // re-login to make sure that the new mount points are initialized + $this->loginHelper(self::TEST_FILES_SHARING_API_USER2); + + $this->assertTrue(\OC\Files\Filesystem::file_exists('/newTarget/oldTarget/subfolder/' . $this->folder)); + + // cleanup + $this->loginHelper(self::TEST_FILES_SHARING_API_USER1); + $result = \OCP\Share::unshare('folder', $fileinfo->getId(), \OCP\Share::SHARE_TYPE_USER, self::TEST_FILES_SHARING_API_USER2); + $this->assertTrue($result); + } + } diff --git a/core/css/mobile.css b/core/css/mobile.css index 2003e0a7f45..bdca29d888a 100644 --- a/core/css/mobile.css +++ b/core/css/mobile.css @@ -82,6 +82,11 @@ background-color: #fff; overflow-x: hidden !important; } +/* allow horizontal scrollbar in settings + otherwise user management is not usable on mobile */ +#body-settings #app-content { + overflow-x: auto !important; +} #app-navigation-toggle { position: fixed; @@ -129,6 +134,15 @@ table.multiselect thead { } +/* prevent overflow in user management controls bar */ +#usersearchform { + display: none; +} +#body-settings #controls { + min-width: 768px !important; +} + + /* fix controls bar jumping when navigation is slid out */ .snapjs-left #app-navigation-toggle, .snapjs-left #controls { diff --git a/core/js/share.js b/core/js/share.js index 5763664c5de..62271a43ceb 100644 --- a/core/js/share.js +++ b/core/js/share.js @@ -29,10 +29,13 @@ OC.Share={ * files "Share" icon to "Shared" according to their share status and * share type. * + * If a callback is specified, the update step is skipped. + * * @param itemType item type * @param fileList file list instance, defaults to OCA.Files.App.fileList + * @param callback function to call after the shares were loaded */ - loadIcons:function(itemType, fileList) { + loadIcons:function(itemType, fileList, callback) { // Load all share icons $.get( OC.filePath('core', 'ajax', 'share.php'), @@ -45,7 +48,11 @@ OC.Share={ $.each(result.data, function(item, data) { OC.Share.statuses[item] = data; }); - OC.Share.updateIcons(itemType, fileList); + if (_.isFunction(callback)) { + callback(OC.Share.statuses); + } else { + OC.Share.updateIcons(itemType, fileList); + } } } ); diff --git a/lib/private/appframework/http/dispatcher.php b/lib/private/appframework/http/dispatcher.php index fa8d3c47a8b..7f2717951a5 100644 --- a/lib/private/appframework/http/dispatcher.php +++ b/lib/private/appframework/http/dispatcher.php @@ -145,7 +145,7 @@ class Dispatcher { ) { $value = false; - } elseif(in_array($type, $types)) { + } elseif($value !== null && in_array($type, $types)) { settype($value, $type); } diff --git a/lib/private/connector/sabre/objecttree.php b/lib/private/connector/sabre/objecttree.php index 54596db3c47..d7a96cfc88e 100644 --- a/lib/private/connector/sabre/objecttree.php +++ b/lib/private/connector/sabre/objecttree.php @@ -11,6 +11,8 @@ namespace OC\Connector\Sabre; use OC\Files\FileInfo; use OC\Files\Filesystem; use OC\Files\Mount\MoveableMount; +use OCP\Files\StorageInvalidException; +use OCP\Files\StorageNotAvailableException; class ObjectTree extends \Sabre\DAV\ObjectTree { @@ -83,7 +85,13 @@ class ObjectTree extends \Sabre\DAV\ObjectTree { } } else { // read from cache - $info = $this->fileView->getFileInfo($path); + try { + $info = $this->fileView->getFileInfo($path); + } catch (StorageNotAvailableException $e) { + throw new \Sabre\DAV\Exception\ServiceUnavailable('Storage not available'); + } catch (StorageInvalidException $e){ + throw new \Sabre\DAV\Exception\NotFound('Storage ' . $path . ' is invalid'); + } } if (!$info) { diff --git a/lib/private/files/mount/manager.php b/lib/private/files/mount/manager.php index 45a9f339fba..e5180cfe173 100644 --- a/lib/private/files/mount/manager.php +++ b/lib/private/files/mount/manager.php @@ -27,6 +27,10 @@ class Manager { * @param string $mountPoint */ public function removeMount($mountPoint) { + $mountPoint = Filesystem::normalizePath($mountPoint); + if (strlen($mountPoint) > 1) { + $mountPoint .= '/'; + } unset($this->mounts[$mountPoint]); } diff --git a/lib/private/files/storage/dav.php b/lib/private/files/storage/dav.php index 8b97f750204..726688fe444 100644 --- a/lib/private/files/storage/dav.php +++ b/lib/private/files/storage/dav.php @@ -8,6 +8,9 @@ namespace OC\Files\Storage; +use OCP\Files\StorageNotAvailableException; +use Sabre\DAV\Exception; + class DAV extends \OC\Files\Storage\Common { protected $password; protected $user; @@ -463,29 +466,36 @@ class DAV extends \OC\Files\Storage\Common { * * @param string $path * @param int $time + * @throws \OCP\Files\StorageNotAvailableException * @return bool */ public function hasUpdated($path, $time) { $this->init(); - $response = $this->client->propfind($this->encodePath($path), array( - '{DAV:}getlastmodified', - '{DAV:}getetag', - '{http://owncloud.org/ns}permissions' - )); - if (isset($response['{DAV:}getetag'])) { - $cachedData = $this->getCache()->get($path); - $etag = trim($response['{DAV:}getetag'], '"'); - if ($cachedData['etag'] !== $etag) { - return true; - } else if (isset($response['{http://owncloud.org/ns}permissions'])) { - $permissions = $this->parsePermissions($response['{http://owncloud.org/ns}permissions']); - return $permissions !== $cachedData['permissions']; + try { + $response = $this->client->propfind($this->encodePath($path), array( + '{DAV:}getlastmodified', + '{DAV:}getetag', + '{http://owncloud.org/ns}permissions' + )); + if (isset($response['{DAV:}getetag'])) { + $cachedData = $this->getCache()->get($path); + $etag = trim($response['{DAV:}getetag'], '"'); + if ($cachedData['etag'] !== $etag) { + return true; + } else if (isset($response['{http://owncloud.org/ns}permissions'])) { + $permissions = $this->parsePermissions($response['{http://owncloud.org/ns}permissions']); + return $permissions !== $cachedData['permissions']; + } else { + return false; + } } else { - return false; + $remoteMtime = strtotime($response['{DAV:}getlastmodified']); + return $remoteMtime > $time; } - } else { - $remoteMtime = strtotime($response['{DAV:}getlastmodified']); - return $remoteMtime > $time; + } catch (Exception\NotFound $e) { + return false; + } catch (Exception $e) { + throw new StorageNotAvailableException(); } } } diff --git a/lib/public/files/storageinvalidexception.php b/lib/public/files/storageinvalidexception.php new file mode 100644 index 00000000000..7419ccc1d11 --- /dev/null +++ b/lib/public/files/storageinvalidexception.php @@ -0,0 +1,22 @@ +<?php +/** + * Copyright (c) 2014 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +/** + * Public interface of ownCloud for apps to use. + * Files/AlreadyExistsException class + */ + +// use OCP namespace for all classes that are considered public. +// This means that they should be used by apps instead of the internal ownCloud classes +namespace OCP\Files; + +/** + * Storage has invalid configuration + */ +class StorageInvalidException extends \Exception { +} diff --git a/lib/public/files/storagenotavailableexception.php b/lib/public/files/storagenotavailableexception.php new file mode 100644 index 00000000000..b526cb4ea0f --- /dev/null +++ b/lib/public/files/storagenotavailableexception.php @@ -0,0 +1,22 @@ +<?php +/** + * Copyright (c) 2014 Robin Appelman <icewind@owncloud.com> + * This file is licensed under the Affero General Public License version 3 or + * later. + * See the COPYING-README file. + */ + +/** + * Public interface of ownCloud for apps to use. + * Files/AlreadyExistsException class + */ + +// use OCP namespace for all classes that are considered public. +// This means that they should be used by apps instead of the internal ownCloud classes +namespace OCP\Files; + +/** + * Storage is temporarily not available + */ +class StorageNotAvailableException extends \Exception { +} diff --git a/public.php b/public.php index 1f858fd073d..2ac082dba57 100644 --- a/public.php +++ b/public.php @@ -17,7 +17,7 @@ try { if (!$pathInfo && !isset($_GET['service'])) { header('HTTP/1.0 404 Not Found'); exit; - } elseif ($_GET['service']) { + } elseif (isset($_GET['service'])) { $service = $_GET['service']; } else { $pathInfo = trim($pathInfo, '/'); |