diff --git a/lib/autoloader.php b/lib/autoloader.php
index 2ce363850817d653f1a37504c032ef0a83b35fb6..54f01d9be94f0f5de8333daf321be5d53982a183 100644
--- a/lib/autoloader.php
+++ b/lib/autoloader.php
@@ -89,12 +89,11 @@ class Autoloader {
 		} elseif (strpos($class, 'OCA\\') === 0) {
 			list(, $app, $rest) = explode('\\', $class, 3);
 			$app = strtolower($app);
-			foreach (\OC::$APPSROOTS as $appDir) {
-				if (stream_resolve_include_path($appDir['path'] . '/' . $app)) {
-					$paths[] = $appDir['path'] . '/' . $app . '/' . strtolower(str_replace('\\', '/', $rest) . '.php');
-					// If not found in the root of the app directory, insert '/lib' after app id and try again.
-					$paths[] = $appDir['path'] . '/' . $app . '/lib/' . strtolower(str_replace('\\', '/', $rest) . '.php');
-				}
+			$appPath = \OC_App::getAppPath($app);
+			if ($appPath && stream_resolve_include_path($appPath)) {
+				$paths[] = $appPath . '/' . strtolower(str_replace('\\', '/', $rest) . '.php');
+				// If not found in the root of the app directory, insert '/lib' after app id and try again.
+				$paths[] = $appPath . '/lib/' . strtolower(str_replace('\\', '/', $rest) . '.php');
 			}
 		} elseif (strpos($class, 'Test_') === 0) {
 			$paths[] = 'tests/lib/' . strtolower(str_replace('_', '/', substr($class, 5)) . '.php');
diff --git a/lib/private/app.php b/lib/private/app.php
index 2650ad98bc6a8c80130a40211c5ad010977c6b28..a62623905ffac617fee1f27ff4d67fc99c84e309 100644
--- a/lib/private/app.php
+++ b/lib/private/app.php
@@ -3,8 +3,13 @@
  * ownCloud
  *
  * @author Frank Karlitschek
+ * @copyright 2012 Frank Karlitschek <frank@owncloud.org>
+ *
  * @author Jakob Sack
- * @copyright 2012 Frank Karlitschek frank@owncloud.org
+ * @copyright 2012 Jakob Sack <mail@jakobsack.de>
+ *
+ * @author Georg Ehrke
+ * @copyright 2014 Georg Ehrke <georg@ownCloud.com>
  *
  * This library is free software; you can redistribute it and/or
  * modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
@@ -221,64 +226,53 @@ class OC_App {
 	public static function enable($app, $groups = null) {
 		self::$enabledAppsCache = array(); // flush
 		if (!OC_Installer::isInstalled($app)) {
-			// check if app is a shipped app or not. OCS apps have an integer as id, shipped apps use a string
-			if (!is_numeric($app)) {
-				$app = OC_Installer::installShippedApp($app);
-			} else {
-				$appdata = OC_OCSClient::getApplication($app);
-				$download = OC_OCSClient::getApplicationDownload($app, 1);
-				if (isset($download['downloadlink']) and $download['downloadlink'] != '') {
-					// Replace spaces in download link without encoding entire URL
-					$download['downloadlink'] = str_replace(' ', '%20', $download['downloadlink']);
-					$info = array('source' => 'http', 'href' => $download['downloadlink'], 'appdata' => $appdata);
-					$app = OC_Installer::installApp($info);
-				}
-			}
+			$app = self::installApp($app);
 		}
-		$l = OC_L10N::get('core');
-		if ($app !== false) {
-			// check if the app is compatible with this version of ownCloud
-			$info = OC_App::getAppInfo($app);
-			$version = OC_Util::getVersion();
-			if(!self::isAppCompatible($version, $info)) {
-				throw new \Exception(
-					$l->t("App \"%s\" can't be installed because it is not compatible with this version of ownCloud.",
-						array($info['name'])
-					)
-				);
-			} else {
-				if (!is_null($groups)) {
-					OC_Appconfig::setValue($app, 'enabled', json_encode($groups));
-				}else{
-					OC_Appconfig::setValue($app, 'enabled', 'yes');
-				}
-				if (isset($appdata['id'])) {
-					OC_Appconfig::setValue($app, 'ocsid', $appdata['id']);
-				}
-				\OC_Hook::emit('OC_App', 'post_enable', array('app' => $app));
-			}
-		} else {
-			throw new \Exception($l->t("No app name specified"));
+
+		if (!is_null($groups)) {
+			OC_Appconfig::setValue($app, 'enabled', json_encode($groups));
+		}else{
+			OC_Appconfig::setValue($app, 'enabled', 'yes');
 		}
 	}
 
 	/**
-	 * disables an app
-	 * @param string $app app
-	 * @return boolean|null
-	 *
+	 * @param string $app
+	 * @return int
+	 */
+	public static function downloadApp($app) {
+		$appdata=OC_OCSClient::getApplication($app);
+		$download=OC_OCSClient::getApplicationDownload($app, 1);
+		if(isset($download['downloadlink']) and $download['downloadlink']!='') {
+			// Replace spaces in download link without encoding entire URL
+			$download['downloadlink'] = str_replace(' ', '%20', $download['downloadlink']);
+			$info = array('source'=>'http', 'href'=>$download['downloadlink'], 'appdata'=>$appdata);
+			$app=OC_Installer::installApp($info);
+		}
+		return $app;
+	}
+
+	/**
+	 * @param string $app
+	 * @return bool
+	 */
+	public static function removeApp($app) {
+		if (self::isShipped($app)) {
+			return false;
+		}
+
+		return OC_Installer::removeApp($app);
+	}
+
+	/**
 	 * This function set an app as disabled in appconfig.
+	 * @param string $app app
 	 */
 	public static function disable($app) {
 		self::$enabledAppsCache = array(); // flush
 		// check if app is a shipped app or not. if not delete
 		\OC_Hook::emit('OC_App', 'pre_disable', array('app' => $app));
-		OC_Appconfig::setValue($app, 'enabled', 'no');
-
-		// check if app is a shipped app or not. if not delete
-		if (!OC_App::isShipped($app)) {
-			OC_Installer::removeApp($app);
-		}
+		OC_Appconfig::setValue($app, 'enabled', 'no' );
 	}
 
 	/**
@@ -461,17 +455,46 @@ class OC_App {
 	}
 
 
-	protected static function findAppInDirectories($appid) {
+	/**
+	 * search for an app in all app-directories
+	 * @param $appId
+	 * @return mixed (bool|string)
+	 */
+	protected static function findAppInDirectories($appId) {
 		static $app_dir = array();
-		if (isset($app_dir[$appid])) {
-			return $app_dir[$appid];
+
+		if (isset($app_dir[$appId])) {
+			return $app_dir[$appId];
 		}
-		foreach (OC::$APPSROOTS as $dir) {
-			if (file_exists($dir['path'] . '/' . $appid)) {
-				return $app_dir[$appid] = $dir;
+
+		$possibleApps = array();
+		foreach(OC::$APPSROOTS as $dir) {
+			if(file_exists($dir['path'] . '/' . $appId)) {
+				$possibleApps[] = $dir;
 			}
 		}
-		return false;
+
+		if (empty($possibleApps)) {
+			return false;
+		} elseif(count($possibleApps) === 1) {
+			$dir = array_shift($possibleApps);
+			$app_dir[$appId] = $dir;
+			return $dir;
+		} else {
+			$versionToLoad = array();
+			foreach($possibleApps as $possibleApp) {
+				$version = self::getAppVersionByPath($possibleApp['path']);
+				if (empty($versionToLoad) || version_compare($version, $versionToLoad['version'], '>')) {
+					$versionToLoad = array(
+						'dir' => $possibleApp,
+						'version' => $version,
+					);
+				}
+			}
+			$app_dir[$appId] = $versionToLoad['dir'];
+			return $versionToLoad['dir'];
+			//TODO - write test
+		}
 	}
 
 	/**
@@ -487,6 +510,17 @@ class OC_App {
 		return false;
 	}
 
+
+	/**
+	 * check if an app's directory is writable
+	 * @param $appid
+	 * @return bool
+	 */
+	public static function isAppDirWritable($appid) {
+		$path = self::getAppPath($appid);
+		return ($path !== false) ? is_writable($path) : false;
+	}
+
 	/**
 	 * Get the path for the given app on the access
 	 * If the app is defined in multiple directories, the first one is taken. (false if not found)
@@ -506,15 +540,27 @@ class OC_App {
 	 * @return string
 	 */
 	public static function getAppVersion($appid) {
-		$file = self::getAppPath($appid) . '/appinfo/version';
-		if (is_file($file) && $version = trim(file_get_contents($file))) {
-			return $version;
-		} else {
-			$appData = self::getAppInfo($appid);
+		$file = self::getAppPath($appid);
+		return ($file !== false) ? self::getAppVersionByPath($file) : '0';
+	}
+
+	/**
+	 * get app's version based on it's path
+	 * @param string $path
+	 * @return string
+	 */
+	public static function getAppVersionByPath($path) {
+		$versionFile = $path . '/appinfo/version';
+		$infoFile = $path . '/appinfo/info.xml';
+		if(is_file($versionFile)) {
+			return trim(file_get_contents($versionFile));
+		}else{
+			$appData = self::getAppInfo($infoFile, true);
 			return isset($appData['version']) ? $appData['version'] : '';
 		}
 	}
 
+
 	/**
 	 * Read all app metadata from the info.xml file
 	 * @param string $appid id of the app or the path of the info.xml file
@@ -619,7 +665,6 @@ class OC_App {
 		}
 	}
 
-
 	/**
 	 * get the forms for either settings, admin or personal
 	 */
@@ -745,18 +790,20 @@ class OC_App {
 
 				$info['active'] = $active;
 
-				if (isset($info['shipped']) and ($info['shipped'] == 'true')) {
+				if(isset($info['shipped']) and ($info['shipped'] == 'true')) {
 					$info['internal'] = true;
 					$info['internallabel'] = 'Internal App';
 					$info['internalclass'] = '';
-					$info['update'] = false;
+					$info['removable'] = false;
 				} else {
 					$info['internal'] = false;
 					$info['internallabel'] = '3rd Party';
 					$info['internalclass'] = 'externalapp';
-					$info['update'] = OC_Installer::isUpdateAvailable($app);
+					$info['removable'] = true;
 				}
 
+				$info['update'] = OC_Installer::isUpdateAvailable($app);
+
 				$info['preview'] = OC_Helper::imagePath('settings', 'trans.png');
 				$info['version'] = OC_App::getAppVersion($app);
 				$appList[] = $info;
@@ -841,6 +888,7 @@ class OC_App {
 				$app1[$i]['ocs_id'] = $app['id'];
 				$app1[$i]['internal'] = $app1[$i]['active'] = 0;
 				$app1[$i]['update'] = false;
+				$app1[$i]['removable'] = false;
 				if ($app['label'] == 'recommended') {
 					$app1[$i]['internallabel'] = 'Recommended';
 					$app1[$i]['internalclass'] = 'recommendedapp';
@@ -851,17 +899,29 @@ class OC_App {
 
 
 				// rating img
-				if ($app['score'] >= 0 and $app['score'] < 5) $img = OC_Helper::imagePath("core", "rating/s1.png");
-				elseif ($app['score'] >= 5 and $app['score'] < 15) $img = OC_Helper::imagePath("core", "rating/s2.png");
-				elseif ($app['score'] >= 15 and $app['score'] < 25) $img = OC_Helper::imagePath("core", "rating/s3.png");
-				elseif ($app['score'] >= 25 and $app['score'] < 35) $img = OC_Helper::imagePath("core", "rating/s4.png");
-				elseif ($app['score'] >= 35 and $app['score'] < 45) $img = OC_Helper::imagePath("core", "rating/s5.png");
-				elseif ($app['score'] >= 45 and $app['score'] < 55) $img = OC_Helper::imagePath("core", "rating/s6.png");
-				elseif ($app['score'] >= 55 and $app['score'] < 65) $img = OC_Helper::imagePath("core", "rating/s7.png");
-				elseif ($app['score'] >= 65 and $app['score'] < 75) $img = OC_Helper::imagePath("core", "rating/s8.png");
-				elseif ($app['score'] >= 75 and $app['score'] < 85) $img = OC_Helper::imagePath("core", "rating/s9.png");
-				elseif ($app['score'] >= 85 and $app['score'] < 95) $img = OC_Helper::imagePath("core", "rating/s10.png");
-				elseif ($app['score'] >= 95 and $app['score'] < 100) $img = OC_Helper::imagePath("core", "rating/s11.png");
+				if ($app['score'] < 5) {
+					$img = OC_Helper::imagePath( "core", "rating/s1.png" );
+				} elseif ($app['score'] < 15) {
+					$img = OC_Helper::imagePath( "core", "rating/s2.png" );
+				} elseif($app['score'] < 25) {
+					$img = OC_Helper::imagePath( "core", "rating/s3.png" );
+				} elseif($app['score'] < 35) {
+					$img = OC_Helper::imagePath( "core", "rating/s4.png" );
+				} elseif($app['score'] < 45) {
+					$img = OC_Helper::imagePath( "core", "rating/s5.png" );
+				} elseif($app['score'] < 55) {
+					$img = OC_Helper::imagePath( "core", "rating/s6.png" );
+				} elseif($app['score'] < 65) {
+					$img = OC_Helper::imagePath( "core", "rating/s7.png" );
+				} elseif($app['score'] < 75) {
+					$img = OC_Helper::imagePath( "core", "rating/s8.png" );
+				} elseif($app['score'] < 85) {
+					$img = OC_Helper::imagePath( "core", "rating/s9.png" );
+				} elseif($app['score'] < 95) {
+					$img = OC_Helper::imagePath( "core", "rating/s10.png" );
+				} elseif($app['score'] < 100) {
+					$img = OC_Helper::imagePath( "core", "rating/s11.png" );
+				}
 
 				$app1[$i]['score'] = '<img src="' . $img . '"> Score: ' . $app['score'] . '%';
 				$i++;
@@ -1043,10 +1103,58 @@ class OC_App {
 		return $versions;
 	}
 
+
+	/**
+	 * @param mixed $app
+	 * @return bool
+	 * @throws Exception if app is not compatible with this version of ownCloud
+	 * @throws Exception if no app-name was specified
+	 */
+	public static function installApp($app) {
+		$l = OC_L10N::get('core');
+		$appdata=OC_OCSClient::getApplication($app);
+
+		// check if app is a shipped app or not. OCS apps have an integer as id, shipped apps use a string
+		if(!is_numeric($app)) {
+			$shippedVersion=self::getAppVersion($app);
+			if($appdata && version_compare($shippedVersion, $appdata['version'], '<')) {
+				$app = self::downloadApp($app);
+			} else {
+				$app = OC_Installer::installShippedApp($app);
+			}
+		}else{
+			$app = self::downloadApp($app);
+		}
+
+		if($app!==false) {
+			// check if the app is compatible with this version of ownCloud
+			$info = self::getAppInfo($app);
+			$version=OC_Util::getVersion();
+			if(!self::isAppCompatible($version, $info)) {
+				throw new \Exception(
+					$l->t('App \"%s\" can\'t be installed because it is not compatible with this version of ownCloud.',
+						array($info['name'])
+					)
+				);
+			}else{
+				OC_Appconfig::setValue( $app, 'enabled', 'yes' );
+				if(isset($appdata['id'])) {
+					OC_Appconfig::setValue( $app, 'ocsid', $appdata['id'] );
+				}
+				\OC_Hook::emit('OC_App', 'post_enable', array('app' => $app));
+			}
+		}else{
+			throw new \Exception($l->t("No app name specified"));
+		}
+
+		return $app;
+	}
+
 	/**
 	 * update the database for the app and call the update script
 	 *
 	 * @param string $appid
+	 * @return bool
 	 */
 	public static function updateApp($appid) {
 		if (file_exists(self::getAppPath($appid) . '/appinfo/preupdate.php')) {
@@ -1057,7 +1165,7 @@ class OC_App {
 			OC_DB::updateDbFromStructure(self::getAppPath($appid) . '/appinfo/database.xml');
 		}
 		if (!self::isEnabled($appid)) {
-			return;
+			return false;
 		}
 		if (file_exists(self::getAppPath($appid) . '/appinfo/update.php')) {
 			self::loadApp($appid);
@@ -1074,6 +1182,8 @@ class OC_App {
 		}
 
 		self::setAppTypes($appid);
+
+		return true;
 	}
 
 	/**
diff --git a/lib/private/installer.php b/lib/private/installer.php
index 3bddfa6a3b73b671a97185242056704e09b49902..466aa4a8f899d1e5f6bc8616f9e6d198479ec41d 100644
--- a/lib/private/installer.php
+++ b/lib/private/installer.php
@@ -5,6 +5,9 @@
  * @author Robin Appelman
  * @copyright 2012 Frank Karlitschek frank@owncloud.org
  *
+ * @author Georg Ehrke
+ * @copytight 2014 Georg Ehrke georg@ownCloud.com
+ *
  * 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
@@ -24,6 +27,7 @@
  * This class provides the functionality needed to install, update and remove plugins/apps
  */
 class OC_Installer{
+
 	/**
 	 *
 	 * This function installs an app. All information needed are passed in the
@@ -60,6 +64,169 @@ class OC_Installer{
 	public static function installApp( $data = array()) {
 		$l = \OC_L10N::get('lib');
 
+		list($extractDir, $path) = self::downloadApp($data);
+		$info = self::checkAppsIntegrity($data, $extractDir, $path);
+
+		$basedir=OC_App::getInstallPath().'/'.$info['id'];
+		//check if the destination directory already exists
+		if(is_dir($basedir)) {
+			OC_Helper::rmdirr($extractDir);
+			if($data['source']=='http') {
+				unlink($path);
+			}
+			throw new \Exception($l->t("App directory already exists"));
+		}
+
+		if(!empty($data['pretent'])) {
+			return false;
+		}
+
+		//copy the app to the correct place
+		if(@!mkdir($basedir)) {
+			OC_Helper::rmdirr($extractDir);
+			if($data['source']=='http') {
+				unlink($path);
+			}
+			throw new \Exception($l->t("Can't create app folder. Please fix permissions. %s", array($basedir)));
+		}
+
+		$extractDir .= '/' . $info['id'];
+		OC_Helper::copyr($extractDir, $basedir);
+
+		//remove temporary files
+		OC_Helper::rmdirr($extractDir);
+
+		//install the database
+		if(is_file($basedir.'/appinfo/database.xml')) {
+			if (OC_Appconfig::getValue($info['id'], 'installed_version') === null) {
+				OC_DB::createDbFromStructure($basedir.'/appinfo/database.xml');
+			} else {
+				OC_DB::updateDbFromStructure($basedir.'/appinfo/database.xml');
+			}
+		}
+
+		//run appinfo/install.php
+		if((!isset($data['noinstall']) or $data['noinstall']==false) and file_exists($basedir.'/appinfo/install.php')) {
+			include $basedir.'/appinfo/install.php';
+		}
+
+		//set the installed version
+		OC_Appconfig::setValue($info['id'], 'installed_version', OC_App::getAppVersion($info['id']));
+		OC_Appconfig::setValue($info['id'], 'enabled', 'no');
+
+		//set remote/public handelers
+		foreach($info['remote'] as $name=>$path) {
+			OCP\CONFIG::setAppValue('core', 'remote_'.$name, $info['id'].'/'.$path);
+		}
+		foreach($info['public'] as $name=>$path) {
+			OCP\CONFIG::setAppValue('core', 'public_'.$name, $info['id'].'/'.$path);
+		}
+
+		OC_App::setAppTypes($info['id']);
+
+		return $info['id'];
+	}
+
+	/**
+	 * @brief checks whether or not an app is installed
+	 * @param string $app app
+	 * @returns bool
+	 *
+	 * Checks whether or not an app is installed, i.e. registered in apps table.
+	 */
+	public static function isInstalled( $app ) {
+		return (OC_Appconfig::getValue($app, "installed_version") !== null);
+	}
+
+	/**
+	 * @brief Update an application
+	 * @param array $info
+	 * @param bool $isShipped
+	 *
+	 * This function installs an app. All information needed are passed in the
+	 * associative array $data.
+	 * The following keys are required:
+	 *   - source: string, can be "path" or "http"
+	 *
+	 * One of the following keys is required:
+	 *   - path: path to the file containing the app
+	 *   - href: link to the downloadable file containing the app
+	 *
+	 * The following keys are optional:
+	 *   - pretend: boolean, if set true the system won't do anything
+	 *   - noupgrade: boolean, if true appinfo/upgrade.php won't be loaded
+	 *
+	 * This function works as follows
+	 *   -# fetching the file
+	 *   -# removing the old files
+	 *   -# unzipping new file
+	 *   -# including appinfo/upgrade.php
+	 *   -# setting the installed version
+	 *
+	 * upgrade.php can determine the current installed version of the app using
+	 * "OC_Appconfig::getValue($appid, 'installed_version')"
+	 */
+	public static function updateApp( $info=array(), $isShipped=false) {
+		list($extractDir, $path) = self::downloadApp($info);
+		$info = self::checkAppsIntegrity($info, $extractDir, $path, $isShipped);
+
+		$currentDir = OC_App::getAppPath($info['id']);
+		$basedir  = OC_App::getInstallPath();
+		$basedir .= '/';
+		$basedir .= $info['id'];
+
+		if($currentDir !== false && is_writable($currentDir)) {
+			$basedir = $currentDir;
+		}
+		if(is_dir($basedir)) {
+			OC_Helper::rmdirr($basedir);
+		}
+
+		$appInExtractDir = $extractDir;
+		if (substr($extractDir, -1) !== '/') {
+			$appInExtractDir .= '/';
+		}
+
+		$appInExtractDir .= $info['id'];
+		OC_Helper::copyr($appInExtractDir, $basedir);
+		OC_Helper::rmdirr($extractDir);
+
+		return OC_App::updateApp($info['id']);
+	}
+
+	/**
+	 * update an app by it's id
+	 * @param integer $ocsid
+	 * @param bool $isShipped
+	 * @return bool
+	 * @throws Exception
+	 */
+	public static function updateAppByOCSId($ocsid, $isShipped=false) {
+		$appdata = OC_OCSClient::getApplication($ocsid);
+		$download = OC_OCSClient::getApplicationDownload($ocsid, 1);
+
+		if (isset($download['downloadlink']) && trim($download['downloadlink']) !== '') {
+			$download['downloadlink'] = str_replace(' ', '%20', $download['downloadlink']);
+			$info = array(
+				'source' => 'http',
+				'href' => $download['downloadlink'],
+				'appdata' => $appdata
+			);
+		} else {
+			throw new \Exception('Could not fetch app info!');
+		}
+
+		return self::updateApp($info);
+	}
+
+	/**
+	 * @param array $data
+	 * @return array
+	 * @throws Exception
+	 */
+	public static function downloadApp($data = array()) {
+		$l = \OC_L10N::get('lib');
+
 		if(!isset($data['source'])) {
 			throw new \Exception($l->t("No source specified when installing app"));
 		}
@@ -104,6 +271,22 @@ class OC_Installer{
 			throw new \Exception($l->t("Failed to open archive when installing app"));
 		}
 
+		return array(
+			$extractDir,
+			$path
+		);
+	}
+
+	/**
+	 * check an app's integrity
+	 * @param array $data
+	 * @param string $extractDir
+	 * @param bool $isShipped
+	 * @return array
+	 * @throws \Exception
+	 */
+	public static function checkAppsIntegrity($data = array(), $extractDir, $path, $isShipped=false) {
+		$l = \OC_L10N::get('lib');
 		//load the info.xml file of the app
 		if(!is_file($extractDir.'/appinfo/info.xml')) {
 			//try to find it in a subdir
@@ -127,7 +310,7 @@ class OC_Installer{
 		}
 		$info=OC_App::getAppInfo($extractDir.'/appinfo/info.xml', true);
 		// check the code for not allowed calls
-		if(!OC_Installer::checkCode($info['id'], $extractDir)) {
+		if(!$isShipped && !OC_Installer::checkCode($info['id'], $extractDir)) {
 			OC_Helper::rmdirr($extractDir);
 			throw new \Exception($l->t("App can't be installed because of not allowed code in the App"));
 		}
@@ -139,7 +322,7 @@ class OC_Installer{
 		}
 
 		// check if shipped tag is set which is only allowed for apps that are shipped with ownCloud
-		if(isset($info['shipped']) and ($info['shipped']=='true')) {
+		if(!$isShipped && isset($info['shipped']) && ($info['shipped']=='true')) {
 			OC_Helper::rmdirr($extractDir);
 			throw new \Exception($l->t("App can't be installed because it contains the <shipped>true</shipped> tag which is not allowed for non shipped apps"));
 		}
@@ -152,125 +335,37 @@ class OC_Installer{
 			$version = trim($info['version']);
 		}
 
-		if($version<>trim($data['appdata']['version'])) {
+		if(isset($data['appdata']['version']) && $version<>trim($data['appdata']['version'])) {
 			OC_Helper::rmdirr($extractDir);
 			throw new \Exception($l->t("App can't be installed because the version in info.xml/version is not the same as the version reported from the app store"));
 		}
 
-		$basedir=OC_App::getInstallPath().'/'.$info['id'];
-		//check if the destination directory already exists
-		if(is_dir($basedir)) {
-			OC_Helper::rmdirr($extractDir);
-			if($data['source']=='http') {
-				unlink($path);
-			}
-			throw new \Exception($l->t("App directory already exists"));
-		}
-
-		if(isset($data['pretent']) and $data['pretent']==true) {
-			return false;
-		}
-
-		//copy the app to the correct place
-		if(@!mkdir($basedir)) {
-			OC_Helper::rmdirr($extractDir);
-			if($data['source']=='http') {
-				unlink($path);
-			}
-			throw new \Exception($l->t("Can't create app folder. Please fix permissions. %s", array($basedir)));
-		}
-		OC_Helper::copyr($extractDir, $basedir);
-
-		//remove temporary files
-		OC_Helper::rmdirr($extractDir);
-
-		//install the database
-		if(is_file($basedir.'/appinfo/database.xml')) {
-			if (OC_Appconfig::getValue($info['id'], 'installed_version') === null) {
-				OC_DB::createDbFromStructure($basedir.'/appinfo/database.xml');
-			} else {
-				OC_DB::updateDbFromStructure($basedir.'/appinfo/database.xml');
-			}
-		}
-
-		//run appinfo/install.php
-		if((!isset($data['noinstall']) or $data['noinstall']==false) and file_exists($basedir.'/appinfo/install.php')) {
-			include $basedir.'/appinfo/install.php';
-		}
-
-		//set the installed version
-		OC_Appconfig::setValue($info['id'], 'installed_version', OC_App::getAppVersion($info['id']));
-		OC_Appconfig::setValue($info['id'], 'enabled', 'no');
-
-		//set remote/public handelers
-		foreach($info['remote'] as $name=>$path) {
-			OCP\CONFIG::setAppValue('core', 'remote_'.$name, $info['id'].'/'.$path);
-		}
-		foreach($info['public'] as $name=>$path) {
-			OCP\CONFIG::setAppValue('core', 'public_'.$name, $info['id'].'/'.$path);
-		}
-
-		OC_App::setAppTypes($info['id']);
-
-		return $info['id'];
-	}
-
-	/**
-	 * checks whether or not an app is installed
-	 * @param string $app app
-	 * @return bool
-	 *
-	 * Checks whether or not an app is installed, i.e. registered in apps table.
-	 */
-	public static function isInstalled( $app ) {
-
-		if( null == OC_Appconfig::getValue( $app, "installed_version" )) {
-			return false;
-		}
-
-		return true;
-	}
-
-	/**
-	 * Update an application
-	 *
-	 * This function installs an app. All information needed are passed in the
-	 * associative array $data.
-	 * The following keys are required:
-	 *   - source: string, can be "path" or "http"
-	 *
-	 * One of the following keys is required:
-	 *   - path: path to the file containing the app
-	 *   - href: link to the downloadable file containing the app
-	 *
-	 * The following keys are optional:
-	 *   - pretend: boolean, if set true the system won't do anything
-	 *   - noupgrade: boolean, if true appinfo/upgrade.php won't be loaded
-	 *
-	 * This function works as follows
-	 *   -# fetching the file
-	 *   -# removing the old files
-	 *   -# unzipping new file
-	 *   -# including appinfo/upgrade.php
-	 *   -# setting the installed version
-	 *
-	 * upgrade.php can determine the current installed version of the app using
-	 * "OC_Appconfig::getValue($appid, 'installed_version')"
-	 */
-	public static function updateApp( $app ) {
-		$ocsid=OC_Appconfig::getValue( $app, 'ocsid');
-		OC_App::disable($app);
-		OC_App::enable($ocsid);
-		return(true);
+		return $info;
 	}
 
 	/**
 	 * Check if an update for the app is available
+	 * @param string $app
 	 * @return string|false false or the version number of the update
 	 *
 	 * The function will check if an update for a version is available
 	 */
 	public static function isUpdateAvailable( $app ) {
+		static $isInstanceReadyForUpdates = null;
+
+		if ($isInstanceReadyForUpdates === null) {
+			$installPath = OC_App::getInstallPath();
+			if ($installPath === false || $installPath === null) {
+				$isInstanceReadyForUpdates = false;
+			} else {
+				$isInstanceReadyForUpdates = true;
+			}
+		}
+
+		if ($isInstanceReadyForUpdates === false) {
+			return false;
+		}
+
 		$ocsid=OC_Appconfig::getValue( $app, 'ocsid', '');
 
 		if($ocsid<>'') {
@@ -299,19 +394,25 @@ class OC_Installer{
 	 * The function will check if the app is already downloaded in the apps repository
 	 */
 	public static function isDownloaded( $name ) {
-
-		$downloaded=false;
 		foreach(OC::$APPSROOTS as $dir) {
-			if(is_dir($dir['path'].'/'.$name)) $downloaded=true;
+			$dirToTest  = $dir['path'];
+			$dirToTest .= '/';
+			$dirToTest .= $name;
+			$dirToTest .= '/';
+
+			if (is_dir($dirToTest)) {
+				return true;
+			}
 		}
-		return($downloaded);
+
+		return false;
 	}
 
 	/**
 	 * Removes an app
 	 * @param string $name name of the application to remove
 	 * @param array $options options
-	 * @return boolean|null
+	 * @return boolean
 	 *
 	 * This function removes an app. $options is an associative array. The
 	 * following keys are optional:ja
@@ -353,9 +454,11 @@ class OC_Installer{
 			$appdir=OC_App::getInstallPath().'/'.$name;
 			OC_Helper::rmdirr($appdir);
 
+			return true;
 		}else{
 			OC_Log::write('core', 'can\'t remove app '.$name.'. It is not installed.', OC_Log::ERROR);
 
+			return false;
 		}
 
 	}
@@ -407,6 +510,9 @@ class OC_Installer{
 			return false;
 		}
 		OC_Appconfig::setValue($app, 'installed_version', OC_App::getAppVersion($app));
+		if (array_key_exists('ocsid', $info)) {
+			OC_Appconfig::setValue($app, 'ocsid', $info['ocsid']);
+		}
 
 		//set remote/public handelers
 		foreach($info['remote'] as $name=>$path) {
@@ -421,14 +527,12 @@ class OC_Installer{
 		return $info['id'];
 	}
 
-
 	/**
 	 * check the code of an app with some static code checks
 	 * @param string $folder the folder of the app to check
 	 * @return boolean true for app is o.k. and false for app is not o.k.
 	 */
 	public static function checkCode($appname, $folder) {
-
 		$blacklist=array(
 			'exec(',
 			'eval(',
diff --git a/settings/ajax/disableapp.php b/settings/ajax/disableapp.php
index 466a719157d6fd726d185df6d69d57c975f6c6e9..c1e5bc8eac76680cce533ab50d40207fd8964d54 100644
--- a/settings/ajax/disableapp.php
+++ b/settings/ajax/disableapp.php
@@ -1,7 +1,14 @@
 <?php
-OC_JSON::checkAdminUser();
+OCP\JSON::checkAdminUser();
 OCP\JSON::callCheck();
 
-OC_App::disable(OC_App::cleanAppId($_POST['appid']));
+if (!array_key_exists('appid', $_POST)) {
+	OC_JSON::error();
+	exit;
+}
 
+$appId = $_POST['appid'];
+$appId = OC_App::cleanAppId($appId);
+
+OC_App::disable($appId);
 OC_JSON::success();
diff --git a/settings/ajax/installapp.php b/settings/ajax/installapp.php
new file mode 100644
index 0000000000000000000000000000000000000000..47f40f2b0cd723324406db533c8dcfc6adc1ad19
--- /dev/null
+++ b/settings/ajax/installapp.php
@@ -0,0 +1,19 @@
+<?php
+OCP\JSON::checkAdminUser();
+OCP\JSON::callCheck();
+
+if (!array_key_exists('appid', $_POST)) {
+	OC_JSON::error();
+	exit;
+}
+
+$appId = $_POST['appid'];
+$appId = OC_App::cleanAppId($appId);
+
+$result = OC_App::installApp($appId);
+if($result !== false) {
+	OC_JSON::success(array('data' => array('appid' => $appId)));
+} else {
+	$l = OC_L10N::get('settings');
+	OC_JSON::error(array("data" => array( "message" => $l->t("Couldn't remove app.") )));
+}
diff --git a/settings/ajax/uninstallapp.php b/settings/ajax/uninstallapp.php
new file mode 100644
index 0000000000000000000000000000000000000000..5c6371dc16f82b93178e8db7e44b4dab3414fd95
--- /dev/null
+++ b/settings/ajax/uninstallapp.php
@@ -0,0 +1,19 @@
+<?php
+OCP\JSON::checkAdminUser();
+OCP\JSON::callCheck();
+
+if (!array_key_exists('appid', $_POST)) {
+	OC_JSON::error();
+	exit;
+}
+
+$appId = $_POST['appid'];
+$appId = OC_App::cleanAppId($appId);
+
+$result = OC_App::removeApp($appId);
+if($result !== false) {
+	OC_JSON::success(array('data' => array('appid' => $appId)));
+} else {
+	$l = OC_L10N::get('settings');
+	OC_JSON::error(array("data" => array( "message" => $l->t("Couldn't remove app.") )));
+}
diff --git a/settings/ajax/updateapp.php b/settings/ajax/updateapp.php
index 91c342d5d07a9bf7a9e5a67dbc7758bea85764d5..78f6775fe952deff7e9229b0c8b6ce21153a14ad 100644
--- a/settings/ajax/updateapp.php
+++ b/settings/ajax/updateapp.php
@@ -1,15 +1,42 @@
 <?php
-
-OC_JSON::checkAdminUser();
+/**
+ * Copyright (c) 2013 Georg Ehrke georg@ownCloud.com
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+OCP\JSON::checkAdminUser();
 OCP\JSON::callCheck();
 
-$appid = $_POST['appid'];
-$appid = OC_App::cleanAppId($appid);
+if (!array_key_exists('appid', $_POST)) {
+	OCP\JSON::error(array(
+		'message' => 'No AppId given!'
+	));
+	exit;
+}
+
+$appId = $_POST['appid'];
+
+if (!is_numeric($appId)) {
+	$appId = OC_Appconfig::getValue($appId, 'ocsid', null);
+	$isShipped = OC_App::isShipped($appId);
+
+	if ($appId === null) {
+		OCP\JSON::error(array(
+			'message' => 'No OCS-ID found for app!'
+		));
+		exit;
+	}
+} else {
+	$isShipped = false;
+}
+
+$appId = OC_App::cleanAppId($appId);
 
-$result = OC_Installer::updateApp($appid);
+$result = OC_Installer::updateAppByOCSId($appId, $isShipped);
 if($result !== false) {
-	OC_JSON::success(array('data' => array('appid' => $appid)));
+	OC_JSON::success(array('data' => array('appid' => $appId)));
 } else {
-	$l = OC_L10N::get('settings');	
+	$l = OC_L10N::get('settings');
 	OC_JSON::error(array("data" => array( "message" => $l->t("Couldn't update app.") )));
 }
diff --git a/settings/js/apps.js b/settings/js/apps.js
index ed75e06c0660bc6818769c3184f855aa1a1714d7..4cd7520caa7a2962fdede7e9991dab496874eda5 100644
--- a/settings/js/apps.js
+++ b/settings/js/apps.js
@@ -84,6 +84,14 @@ OC.Settings.Apps = OC.Settings.Apps || {
 			page.find('input.update').hide();
 		}
 
+		if (app.removable !== false) {
+			page.find('input.uninstall').show();
+			page.find('input.uninstall').data('appid', app.id);
+			page.find('input.uninstall').attr('value', t('settings', 'Uninstall App'));
+		} else {
+			page.find('input.uninstall').hide();
+		}
+
 		page.find('input.enable').show();
 		page.find('input.enable').val((app.active) ? t('settings', 'Disable') : t('settings', 'Enable'));
 		page.find('input.enable').data('appid', app.id);
@@ -221,6 +229,18 @@ OC.Settings.Apps = OC.Settings.Apps || {
 			}
 		},'json');
 	},
+	uninstallApp:function(appid, element) {
+		element.val(t('settings','Uninstalling ....'));
+		$.post(OC.filePath('settings','ajax','uninstallapp.php'),{appid:appid},function(result) {
+			if(!result || result.status !== 'success') {
+				OC.Settings.Apps.showErrorMessage(t('settings','Error while uninstalling app'),t('settings','Error'));
+				element.val(t('settings','Uninstall'));
+			} else {
+				OC.Settings.Apps.removeNavigation(appid);
+				appitem.removeClass('active');
+			}
+		},'json');
+	},
 
 	insertApp:function(appdata) {
 		var applist = $('#app-navigation ul li');
@@ -351,6 +371,13 @@ $(document).ready(function(){
 			OC.Settings.Apps.updateApp(appid, element);
 		}
 	});
+	$('#app-content input.uninstall').click(function(){
+		var element = $(this);
+		var appid=$(this).data('appid');
+		if(appid) {
+			OC.Settings.Apps.uninstallApp(appid, element);
+		}
+	});
 
 	$('#group_select').change(function() {
 		var element = $('#app-content input.enable');
diff --git a/settings/routes.php b/settings/routes.php
index 9acfc2852bdbbe4d7d3ee2bafd27287cb398b95e..1c8ad1b3fe8801fad32d5ea40bd138bf5748c1ef 100644
--- a/settings/routes.php
+++ b/settings/routes.php
@@ -71,6 +71,8 @@ $this->create('settings_ajax_disableapp', '/settings/ajax/disableapp.php')
 	->actionInclude('settings/ajax/disableapp.php');
 $this->create('settings_ajax_updateapp', '/settings/ajax/updateapp.php')
 	->actionInclude('settings/ajax/updateapp.php');
+$this->create('settings_ajax_uninstallapp', '/settings/ajax/uninstallapp.php')
+	->actionInclude('settings/ajax/uninstallapp.php');
 $this->create('settings_ajax_navigationdetect', '/settings/ajax/navigationdetect.php')
 	->actionInclude('settings/ajax/navigationdetect.php');
 $this->create('apps_custom', '/settings/js/apps-custom.js')
diff --git a/settings/templates/apps.php b/settings/templates/apps.php
index 776c322046262e4e94aaa38d66a8dcff907fb54d..b35eda4350cbb55d69e84ea566d1ba93d3824213 100644
--- a/settings/templates/apps.php
+++ b/settings/templates/apps.php
@@ -53,6 +53,7 @@
 		print_unescaped($l->t('<span class="licence"></span>-licensed by <span class="author"></span>'));?></p>
 	<input class="enable hidden" type="submit" />
 	<input class="update hidden" type="submit" value="<?php p($l->t('Update')); ?>" />
+	<input class="uninstall hidden" type="submit" value="<?php p($l->t('Uninstall')); ?>"/>
 	<br />
 	<input class="hidden" type="checkbox" id="groups_enable"/>
 	<label class="hidden" for="groups_enable"><?php p($l->t('Enable only for specific groups')); ?></label>
diff --git a/tests/data/testapp.zip b/tests/data/testapp.zip
new file mode 100644
index 0000000000000000000000000000000000000000..e76c0d187248193e90ad487fbd172af065695aa3
Binary files /dev/null and b/tests/data/testapp.zip differ
diff --git a/tests/data/testapp2.zip b/tests/data/testapp2.zip
new file mode 100644
index 0000000000000000000000000000000000000000..f46832f7a757b2199f293ab05d16c368f55b3e8e
Binary files /dev/null and b/tests/data/testapp2.zip differ
diff --git a/tests/lib/installer.php b/tests/lib/installer.php
new file mode 100644
index 0000000000000000000000000000000000000000..5e267245200eb6204b4439491eab2e212a17722f
--- /dev/null
+++ b/tests/lib/installer.php
@@ -0,0 +1,77 @@
+<?php
+/**
+ * Copyright (c) 2014 Georg Ehrke <georg@owncloud.com>
+ * This file is licensed under the Affero General Public License version 3 or
+ * later.
+ * See the COPYING-README file.
+ */
+
+class Test_Installer extends PHPUnit_Framework_TestCase {
+
+	private static $appid = 'testapp';
+	private $appstore;
+
+	public function setUp() {
+		$this->appstore = OC_Config::getValue('appstoreenabled', true);
+		OC_Config::setValue('appstoreenabled', true);
+		OC_Installer::removeApp(self::$appid);
+	}
+
+	public function tearDown() {
+		OC_Installer::removeApp(self::$appid);
+		OC_Config::setValue('appstoreenabled', $this->appstore);
+	}
+
+	public function testInstallApp() {
+		$pathOfTestApp  = __DIR__;
+		$pathOfTestApp .= '/../data/';
+		$pathOfTestApp .= 'testapp.zip';
+
+		$tmp = OC_Helper::tmpFile('.zip');
+		OC_Helper::copyr($pathOfTestApp, $tmp);
+
+		$data = array(
+			'path' => $tmp,
+			'source' => 'path',
+		);
+
+		OC_Installer::installApp($data);
+		$isInstalled = OC_Installer::isInstalled(self::$appid);
+
+		$this->assertTrue($isInstalled);
+	}
+
+	public function testUpdateApp() {
+		$pathOfOldTestApp  = __DIR__;
+		$pathOfOldTestApp .= '/../data/';
+		$pathOfOldTestApp .= 'testapp.zip';
+
+		$oldTmp = OC_Helper::tmpFile('.zip');
+		OC_Helper::copyr($pathOfOldTestApp, $oldTmp);
+
+		$oldData = array(
+			'path' => $oldTmp,
+			'source' => 'path',
+		);
+
+		$pathOfNewTestApp  = __DIR__;
+		$pathOfNewTestApp .= '/../data/';
+		$pathOfNewTestApp .= 'testapp2.zip';
+
+		$newTmp = OC_Helper::tmpFile('.zip');
+		OC_Helper::copyr($pathOfNewTestApp, $newTmp);
+
+		$newData = array(
+			'path' => $newTmp,
+			'source' => 'path',
+		);
+
+		OC_Installer::installApp($oldData);
+		$oldVersionNumber = OC_App::getAppVersion(self::$appid);
+
+		OC_Installer::updateApp($newData);
+		$newVersionNumber = OC_App::getAppVersion(self::$appid);
+
+		$this->assertNotEquals($oldVersionNumber, $newVersionNumber);
+	}
+}
diff --git a/tests/preseed-config.php b/tests/preseed-config.php
index 95ffb4514bf663b8caf4ced6cad438c0769d1f66..3fd5b3cb7fc72007e553947326048e5b38376b2a 100644
--- a/tests/preseed-config.php
+++ b/tests/preseed-config.php
@@ -7,7 +7,7 @@ $CONFIG = array (
     array (
       'path' => OC::$SERVERROOT.'/apps',
       'url' => '/apps',
-      'writable' => false,
+      'writable' => true,
     ),
     1 =>
     array (