Ubuntu One Oauth从PHP登录

我搜索了整个互联网,试图找到一个简单的例子,可以指出我正确的方向,但没有运气,所以这里有我的问题:

我想登录Ubuntu One并从我的网页同步(或几乎读取)文件,所有这些都是用PHP完成的。 访问文件的需求都在该页面中描述: https : //one.ubuntu.com/developer/account_admin/issue_tokens/cloud/

我可以通过以下方式完成第一个请求:

$url = 'https://login.ubuntu.com/api/1.0/authentications?ws.op=authenticate&token_name=Ubuntu%20One%20@%20try1'; $data = curlPetition(array('URL'=>$url,'USERPWD'=>'user:pass')); $ar = fopen('uOne','w');fwrite($ar,$data['responseBody']);fclose($ar); $tokenA = json_decode($data['responseBody'],1); 

好吧,curlPetition只会进行基本的curl请求。 请注意,您需要一个有效的用户:通过ubuntu一个帐户。 我在json中使用“consumer_secret”,“token”,“consumer_key”,“name”,“token_secret”正确地得到了响应。 甚至该条目也会出现在ubuntu的授权应用程序中。

我已经安装了最新的OAuth PCLphpextensnsión及其良好的工作。 但是当我尝试:

  $api_url = 'https://one.ubuntu.com/api/file_storage/v1/'; $conskey = $tokenA['consumer_key']; $conssec = $tokenA['consumer_secret']; $token = $tokenA['token']; $secret = $tokenA['token_secret']; $oauth = new OAuth($conskey,$conssec,OAUTH_SIG_METHOD_HMACSHA1,OAUTH_AUTH_TYPE_URI); $oauth->enableDebug(); $oauth->enableSSLChecks(); $oauth->setToken($token,$secret); $oauth->fetch($api_url.'~/Ubuntu%20One/'); print_r($oauth->getLastResponse()); 

我转到了“正在进行的OpenID事务”页面,您在进行手动Web登录时会通过该页面。 我肯定做错了什么。 我尝试从https://one.ubuntu.com/developer/account_admin/issue_tokens/cloud/获得第二步,使用$ oauth-> fetch,$ oauth-> getAccessToken和$ oauth-> getRequestToken,所有的响应都相同403错误:S

我试图计算有效载荷是如何工作的,但是主要的例子是用python编写的,使用“import ubuntuone.couch.auth as auth”使得令牌内容几乎是自动的。

我很乐意得到一些提示。 谢谢

我认为问题是,在https://one.ubuntu.com/developer/account_admin/issue_tokens/cloud/上定义的“创建新令牌”工作流程的第2步失败了,因为该服务是本周末有几点下来。 您需要捕获这种情况并处理它(503表示您应该稍后根据标准HTTP重试请求)。

我已经测试了下面的PHP(注意:我不是PHP黑客,所以它可能不是最惯用的代码),它对我来说很好。 它经历了三个步骤:

  1. 在Ubuntu SSO中创建一个新令牌(login.ubuntu.com)( API文档 )
  2. 告诉Ubuntu One关于新令牌( API文档 )
  3. 使用该新令牌签署对Ubuntu One文件API的请求( API文档 )

您将在下面看到各个部分的评论。 请记住,这请求并获得一个全新的令牌; 一旦你有了令牌(在第2步之后),将它保存在某个地方; 不要每次都要求新的。

 $url,'USERPWD'=> $email_address.':'.$password)); $tokenA = json_decode($data['responseBody'],1); /* Set up that new token for use in OAuth requests */ $conskey = $tokenA['consumer_key']; $conssec = $tokenA['consumer_secret']; $token = $tokenA['token']; $secret = $tokenA['token_secret']; $oauth = new OAuth($conskey,$conssec,OAUTH_SIG_METHOD_HMACSHA1,OAUTH_AUTH_TYPE_URI); $oauth->enableDebug(); $oauth->enableSSLChecks(); $oauth->setToken($token,$secret); /* Step 2: tell Ubuntu One about the new token (signed with the token itself) */ $tell_u1_about_token_url = 'https://one.ubuntu.com/oauth/sso-finished-so-get-tokens/' . $email_address; $oauth->fetch($tell_u1_about_token_url); print_r($oauth->getLastResponse()); /* Step 3: use the token to make a request to the Files API */ $api_url = 'https://one.ubuntu.com/api/file_storage/v1/'; $oauth->fetch($api_url.'~/Ubuntu%20One/'); print_r($oauth->getLastResponse()); ?> 

从一个第一步的代码开始与ubuntuOne交谈

 'cookie.txt','enableCookies'=>false); var $auth = array('consumer_key'=>false,'consumer_secret'=>false,'token'=>false,'token_secret'=>false); var $oauth = false; function ubuntuOne(){ } function u1_getRoot(){ if($this->oauth === false){return false;} $url = 'https://one.ubuntu.com/api/file_storage/v1'; $this->oauth->fetch($url); print_r($this->oauth->getLastResponse()); } function u1_listFolder($path){ if($this->oauth === false){return false;} //FIXME: parse $path $url = 'https://one.ubuntu.com/api/file_storage/v1'; //FIXME: $path debe terminar en '/' $url .= str_replace(' ','%20',$path); $this->oauth->fetch($url); $lr = $this->oauth->getLastResponse(); if($lr === '{"error": "not found"}'){return false;} print_r($this->oauth->getLastResponse()); } function u1_createFolder($name,$path = ''){ //FIXME: folder exists? $url = 'https://one.ubuntu.com/api/file_storage/v1'; //FIXME: $path debe terminar en '/' $url .= str_replace(' ','%20',$path); //FIXME: $name no puede contener '/' $url .= str_replace(' ','%20',$name); $this->oauth->fetch($url,'{"kind":"directory"}',OAUTH_HTTP_METHOD_PUT); print_r($this->oauth->getLastResponse()); } function u1_file_exists($path){ //FIXME: cache? $url = 'https://one.ubuntu.com/api/file_storage/v1'; $url .= str_replace(' ','%20',$path); try{$this->oauth->fetch($url);} catch(OAuthException $E){if($E->lastResponse === '{"error": "not found"}'){return false;}} $i = $this->oauth->getLastResponseInfo(); if($i['http_code'] === 200){} print_r($this->oauth->getLastResponseInfo()); } function requestAuthentification($user,$pass,$name){ $url = 'https://login.ubuntu.com/api/1.0/authentications?ws.op=authenticate&token_name=Ubuntu%20One%20@%20'.rawurlencode($name); $data = curlPetition(array('URL'=>$url,'USERPWD'=>$user.':'.$pass)); //FIXME: check the response header -> 200 $this->auth = json_decode($data['responseBody'],1); } function registerToken($user){ $url = 'https://one.ubuntu.com/oauth/sso-finished-so-get-tokens/'.$user; $this->oauth->fetch($url); $r = $this->oauth->getLastResponse(); if(substr($r,02) !== 'ok'){ //FIXME: poner error } print_r($this->oauth->getLastResponse()); } function saveAuth($fileName){$ar = fopen($fileName,'w');fwrite($ar,json_encode($this->auth));fclose($ar);return true;} function loadAuth($fileName){ if(!file_exists($fileName)){return false;} $this->auth = json_decode(file_get_contents($fileName),1); if($this->auth === NULL){return false;} $this->oauth = new OAuth($this->auth['consumer_key'],$this->auth['consumer_secret'],OAUTH_SIG_METHOD_HMACSHA1,OAUTH_AUTH_TYPE_URI); $this->oauth->enableDebug(); $this->oauth->enableSSLChecks(); $this->oauth->setToken($this->auth['token'],$this->auth['token_secret']); return true; } function curlPetition($arr,$data = array()){ $curl = curl_init($arr['URL']); if(!isset($data['URLTRACK'])){$data['URLTRACK'] = array();} $data['URLTRACK'][] = $arr['URL']; if(isset($this->curl['userAgent'])){curl_setopt($curl,CURLOPT_USERAGENT,$this->curl['userAgent']);} if(count($data['URLTRACK']) > 1){curl_setopt($curl,CURLOPT_REFERER,$data['URLTRACK'][count($data['URLTRACK'])-2]);} if(isset($arr['USERPWD'])){curl_setopt($curl,CURLOPT_USERPWD,$arr['USERPWD']);} if(isset($arr['userAgent'])){curl_setopt($curl,CURLOPT_USERAGENT,$arr['userAgent']);} if(isset($arr['POST'])){curl_setopt($curl,CURLOPT_POST,true);curl_setopt($curl,CURLOPT_POSTFIELDS,$arr['POST']);} if(isset($arr['referer'])){curl_setopt($curl,CURLOPT_REFERER,$arr['referer']);} if(isset($arr['timeout'])){curl_setopt($curl,CURLOPT_TIMEOUT,$arr['timeout']);} curl_setopt($curl,CURLOPT_SSL_VERIFYPEER,FALSE); curl_setopt($curl,CURLOPT_SSL_VERIFYHOST,2); curl_setopt($curl,CURLOPT_RETURNTRANSFER,1); curl_setopt($curl,CURLOPT_HEADER,true); if($this->curl['enableCookies'] !== false ){$cookieSrc = $this->curl['cookieSrc'];curl_setopt($curl,CURLOPT_COOKIEFILE,$cookieSrc);curl_setopt($curl,CURLOPT_COOKIEJAR,$cookieSrc);} if(isset($arr['header'])){curl_setopt($curl,CURLOPT_HTTPHEADER,$arr['header']);} curl_setopt($curl,CURLOPT_TIMEOUT,25); $viewcode = curl_exec($curl); $curlInfo = curl_getinfo($curl); curl_close($curl); if(empty($viewcode)){return false;} $data['responseHeader'] = substr($viewcode,0,$curlInfo['header_size']); $data['responseBody'] = substr($viewcode,$curlInfo['header_size']); //$data['viewcode'] = $viewcode; if(isset($arr['FOLLOWLOCATION']) && preg_match('/HTTP\/1\.1 30[12]{1}/',$data['responseHeader'])){ preg_match('/Location: (.*)/',$data['responseHeader'],$p); $nurl = trim($p[1]); if($nurl[0]=='/'){list($arr['URL'],) = explode('/',str_replace('http://','',$arr['URL']));$nurl = 'http://'.$arr['URL'].$nurl;} $arr['URL'] = $nurl; unset($arr['POST']); return curlPetition($arr,$data); } return $data; } } ?> 

一些示例调用(srry为无序和注释代码,可能有一天文档):

 echo time()."\n"; $ub = new ubuntuOne; /* The first time you made the commented calls, then you save the authorization * to a file. Once you have it on a file, you load it every time from there */ //$ub->requestAuthentification('sombra2eternity@gmail.com','*****','st'); //$ub->registerToken('sombra2eternity@gmail.com'); //print_r($ub->auth); //$ub->saveAuth($GLOBALS['userPath'].'db/uOne.protected'); $ub->loadAuth($GLOBALS['userPath'].'db/uOne.protected'); //$ub->registerToken('sombra2eternity@gmail.com'); $ub->u1_getRoot(); //$ub->u1_file_exists('/~/Ubuntu One/non_exists/'); echo "\n"; $ub->u1_listFolder('/~/Ubuntu One/'); echo "\n"; $ub->u1_createFolder('new folder','/~/Ubuntu One/'); 

祝你好运,我希望它有所帮助

更新版本,添加了一些function,一些bug被捕获

  class ubuntuOne{ var $curl = array('cookieSrc'=>'cookie.txt','enableCookies'=>false); var $auth = array('consumer_key'=>false,'consumer_secret'=>false,'token'=>false,'token_secret'=>false); var $oneInfo = false; var $oauth = false; var $E = array('errorCode'=>0,'errorDescription'=>''); var $fs = array(); function ubuntuOne(){ $this->fs['/'] = $this->helper_nodeSkeleton(array('name'=>'/','kind'=>'directory','resource_path'=>'/')); } function helper_nodeSkeleton($a = array()){return array_merge(array('name'=>false,'kind'=>false,'when_created'=>false,'generation'=>false,'has_children'=>false,'content_path'=>false,'generation_created'=>false,'parent_path'=>false,'resource_path'=>false,'when_changed'=>false,'key'=>false,'path'=>false,'volume_path'=>false,'size'=>0,'children'=>array()),$a);} function helper_storePath($path,$node = false){ $path = explode('/',$path); $curPath = &$this->fs['/']; $resPath = ''; foreach($path as $p){if($p === ''){continue;}$resPath .= '/'.$p;if(!isset($curPath['children'][$p])){$curPath['children'][$p] = $this->helper_nodeSkeleton(array('name'=>$p,'kind'=>'directory','resource_path'=>$resPath));}$curPath = &$curPath['children'][$p];} if($node !== false){$curPath = array_merge($curPath,$node);if($curPath['kind'] == 'file'){unset($curPath['children']);}} } function helper_storeNode($node){ if(!isset($node['name'])){$r = preg_match('/\/([^\/]+)$/',$node['resource_path'],$name);if($r === 0){$this->E = array('errorCode'=>1,'errorDescription'=>'NAME_NOT_PARSED');return null;}$node['name'] = $name[1];} $this->helper_storePath($node['resource_path'],$node); $this->E = array('errorCode'=>0,'errorDescription'=>''); } function u1_getRoot(){ if($this->oauth === false){return false;} $url = 'https://one.ubuntu.com/api/file_storage/v1'; try{$this->oauth->fetch($url);} catch(OAuthException $E){print_r($E);return false;} $lr = json_decode($this->oauth->getLastResponse(),1); $this->helper_storePath($lr['root_node_path']); foreach($lr['user_node_paths'] as $np){$this->helper_storePath($np);} $this->oneInfo = $lr; return $lr; } function u1_getVolumeTree(){ if($this->oneInfo === false){$r = $this->u1_getRoot();if($r === null){return $r;}} $base = $this->fs['/']['children']['~']['children']; foreach($base as $k=>$node){$this->u1_helper_getVolumeTree($node);} return $this->fs; } function u1_helper_getVolumeTree($node,$i = 0){ if($node['kind'] == 'file'){return;} $r = $this->u1_folder_list($node['resource_path']); foreach($r['children'] as $child){$this->u1_helper_getVolumeTree($child,$i);} } function u1_folder_list($path){ if($this->oauth === false){$this->E = array('errorCode'=>99,'errorDescription'=>'NO_OAUTH_DATA');return null;} if(substr($path,-1) != '/'){$path .= '/';} $url = 'https://one.ubuntu.com/api/file_storage/v1'.$this->helper_encodeURL($path).'?include_children=true'; try{$this->oauth->fetch($url);} catch(OAuthException $E){echo $path;print_r($E);return null;} $lr = $this->oauth->getLastResponse(); if($lr === '{"error": "not found"}'){return null;} $lr = json_decode($lr,1); /* Store the base node */ $node = $lr;unset($node['children']); $this->helper_storeNode($node); foreach($lr['children'] as $child){$this->helper_storeNode($child);} return $lr; } function u1_folder_create($name,$path = '/~/Ubuntu One/'){ if($this->oauth === false){$this->E = array('errorCode'=>99,'errorDescription'=>'NO_OAUTH_DATA');return null;} if(substr($path,-1) != '/'){$path .= '/';} $name = preg_replace(array('/[\.]$/','/[\/]*/'),'',$name); //FIXME: folder exists? $url = 'https://one.ubuntu.com/api/file_storage/v1'.$this->helper_encodeURL($path).$this->helper_encodeURL($name); $this->oauth->fetch($url,'{"kind":"directory"}',OAUTH_HTTP_METHOD_PUT); $node = json_decode($this->oauth->getLastResponse(),1); $this->helper_storeNode($node); return $node; } function u1_file_create($path,$blob){ if($this->oauth === false){$this->E = array('errorCode'=>99,'errorDescription'=>'NO_OAUTH_DATA');return null;} //if(substr($path,-1) != '/'){$path .= '/';} $url = 'https://files.one.ubuntu.com/content'.$this->helper_encodeURL($path); //FIXME: u1_file_exists $this->oauth->fetch($url,$blob,OAUTH_HTTP_METHOD_PUT,array('Content-Type'=>'application/json')); //$i = $this->oauth->getLastResponseInfo(); //print_r($i); $node = json_decode($this->oauth->getLastResponse(),1); $this->helper_storeNode($node); return $node; } function u1_file_exists($path,$nocache = false){ if($this->oauth === false){$this->E = array('errorCode'=>99,'errorDescription'=>'NO_OAUTH_DATA');return null;} //FIXME: cache? $url = 'https://one.ubuntu.com/api/file_storage/v1'.$this->helper_encodeURL($path); try{$this->oauth->fetch($url);} catch(OAuthException $E){if($E->lastResponse === '{"error": "not found"}'){return false;}} $i = $this->oauth->getLastResponseInfo(); if($i['http_code'] === 200){} //FIXME: respuesta adecuada print_r($this->oauth->getLastResponseInfo()); } function u1_file_get($contentPath,$destinyPath = false){ if($this->oauth === false){$this->E = array('errorCode'=>99,'errorDescription'=>'NO_OAUTH_DATA');return null;} if(substr($contentPath,0,9) != '/content/'){$this->E = array('errorCode'=>1,'errorDescription'=>'NO_CONTENT_PATH');return null;} $url = 'https://files.one.ubuntu.com'.$this->helper_encodeURL($contentPath); /* I hope nobody ask me about the following concat, never gonna give you up!! */ $time = time(); $data = array('oauth_consumer_key'=>$this->auth['consumer_key'],'oauth_nonce'=>$time*rand(0,200),'oauth_signature_method'=>'HMAC-SHA1','oauth_timestamp'=>$time,'oauth_token'=>$this->auth['token'],'oauth_version'=>'1.0'); $b = '';foreach($data as $k=>$v){$b .= '&'.$k.'='.$v;} $b = 'GET&'.rawurlencode($url).'&'.rawurlencode(substr($b,1)); $key = $this->auth['consumer_secret'].'&'.$this->auth['token_secret']; $signature = $this->helper_oauth_hmacsha1($key,$b); $data['oauth_signature'] = $signature; $a = $url.'?';foreach($data as $k=>$v){$a .= $k.'='.rawurlencode($v).'&';} $h = fopen($a,'r'); if(!$h){ //FIXME: poner error return null; } //FIXME: is_writable //FIXME: file_exists $fileName = basename($contentPath); $ar = fopen($destinyPath.$fileName,'w'); //FIXME: comprobar los primeros bits del buffer para asegurarse de que no está fallando $buffer = '';while(!feof($h)){$buffer = fgets($h,8192);fwrite($ar,$buffer);}fclose($h); fclose($ar); $filehash = sha1_file($destinyPath.$fileName); //echo "\n".$filehash."\n"; return array('fileName'=>$fileName,'filePath'=>$destinyPath,'fileHash'=>$filehash); } function u1_file_unlink($path){ if($this->oauth === false){$this->E = array('errorCode'=>99,'errorDescription'=>'NO_OAUTH_DATA');return null;} $url = 'https://one.ubuntu.com/api/file_storage/v1'.$this->helper_encodeURL($path); //FIXME: u1_file_exists try{$this->oauth->fetch($url,$blob,OAUTH_HTTP_METHOD_DELETE,array('Content-Type'=>'application/json'));} catch(OAuthException $E){print_r($E);$this->E = array('errorCode'=>1,'errorDescription'=>'FILE_NOT_EXISTS');return null;} $i = $this->oauth->getLastResponseInfo(); //FIXME: eliminar el fichero de la caché //print_r($i); } function requestAuthentification($user,$pass,$name){ $url = 'https://login.ubuntu.com/api/1.0/authentications?ws.op=authenticate&token_name=Ubuntu%20One%20@%20'.rawurlencode($name); $data = $this->curlPetition(array('URL'=>$url,'USERPWD'=>$user.':'.$pass)); //FIXME: check the response header -> 200 $this->auth = json_decode($data['responseBody'],1); if($this->auth === NULL){return false;} $this->oauth = new OAuth($this->auth['consumer_key'],$this->auth['consumer_secret'],OAUTH_SIG_METHOD_HMACSHA1,OAUTH_AUTH_TYPE_URI); $this->oauth->enableDebug(); $this->oauth->enableSSLChecks(); $this->oauth->setToken($this->auth['token'],$this->auth['token_secret']); return true; } function registerToken($user){ //FIXME: check $this->oauth == false $url = 'https://one.ubuntu.com/oauth/sso-finished-so-get-tokens/'.$user; $this->oauth->fetch($url); $r = $this->oauth->getLastResponse(); if(substr($r,02) !== 'ok'){ //FIXME: poner error } //print_r($this->oauth->getLastResponse()); } function saveAuth($fileName){$ar = fopen($fileName,'w');fwrite($ar,json_encode($this->auth));fclose($ar);return true;} function loadAuth($fileName){ if(!file_exists($fileName)){return false;} $this->auth = json_decode(file_get_contents($fileName),1); if($this->auth === NULL){return false;} return $this->helper_makeOauth(); } function setAuth($auth){$this->auth = $auth;return $this->helper_makeOauth();} function helper_makeOauth(){ $this->oauth = new OAuth($this->auth['consumer_key'],$this->auth['consumer_secret'],OAUTH_SIG_METHOD_HMACSHA1,OAUTH_AUTH_TYPE_URI); $this->oauth->enableDebug(); $this->oauth->enableSSLChecks(); $this->oauth->setToken($this->auth['token'],$this->auth['token_secret']); return true; } function helper_encodeURL($url){return str_replace('%2F','/',rawurlencode($url));} function helper_oauth_hmacsha1($key,$data){ $blocksize=64;$hashfunc='sha1'; if(strlen($key)>$blocksize){$key=pack('H*',$hashfunc($key));} $key=str_pad($key,$blocksize,chr(0x00)); $ipad=str_repeat(chr(0x36),$blocksize); $opad=str_repeat(chr(0x5c),$blocksize); $hmac=pack('H*',$hashfunc(($key^$opad).pack('H*',$hashfunc(($key^$ipad).$data)))); return base64_encode($hmac); } function curlPetition($arr,$data = array()){ //FIXME: data puede ser una propiedad de la clase $curl = curl_init($arr['URL']); if(!isset($data['URLTRACK'])){$data['URLTRACK'] = array();} $data['URLTRACK'][] = $arr['URL']; if(isset($this->curl['userAgent'])){curl_setopt($curl,CURLOPT_USERAGENT,$this->curl['userAgent']);} if(count($data['URLTRACK']) > 1){curl_setopt($curl,CURLOPT_REFERER,$data['URLTRACK'][count($data['URLTRACK'])-2]);} if(isset($arr['USERPWD'])){curl_setopt($curl,CURLOPT_USERPWD,$arr['USERPWD']);} if(isset($arr['userAgent'])){curl_setopt($curl,CURLOPT_USERAGENT,$arr['userAgent']);} if(isset($arr['POST'])){curl_setopt($curl,CURLOPT_POST,true);curl_setopt($curl,CURLOPT_POSTFIELDS,$arr['POST']);} if(isset($arr['referer'])){curl_setopt($curl,CURLOPT_REFERER,$arr['referer']);} if(isset($arr['timeout'])){curl_setopt($curl,CURLOPT_TIMEOUT,$arr['timeout']);} curl_setopt($curl,CURLOPT_SSL_VERIFYPEER,FALSE); curl_setopt($curl,CURLOPT_SSL_VERIFYHOST,2); curl_setopt($curl,CURLOPT_RETURNTRANSFER,1); curl_setopt($curl,CURLOPT_HEADER,true); if($this->curl['enableCookies'] !== false ){$cookieSrc = $this->curl['cookieSrc'];curl_setopt($curl,CURLOPT_COOKIEFILE,$cookieSrc);curl_setopt($curl,CURLOPT_COOKIEJAR,$cookieSrc);} if(isset($arr['header'])){curl_setopt($curl,CURLOPT_HTTPHEADER,$arr['header']);} curl_setopt($curl,CURLOPT_TIMEOUT,25); $viewcode = curl_exec($curl); $curlInfo = curl_getinfo($curl); curl_close($curl); if(empty($viewcode)){return false;} $data['responseHeader'] = substr($viewcode,0,$curlInfo['header_size']); $data['responseBody'] = substr($viewcode,$curlInfo['header_size']); //$data['viewcode'] = $viewcode; if(isset($arr['FOLLOWLOCATION']) && preg_match('/HTTP\/1\.1 30[12]{1}/',$data['responseHeader'])){ preg_match('/Location: (.*)/',$data['responseHeader'],$p); $nurl = trim($p[1]); if($nurl[0]=='/'){list($arr['URL'],) = explode('/',str_replace('http://','',$arr['URL']));$nurl = 'http://'.$arr['URL'].$nurl;} $arr['URL'] = $nurl; unset($arr['POST']); return $this->curlPetition($arr,$data); } return $data; } } 

哇,Stuart Langridge,你就像我的传奇!!

我想明天我会找到一些空闲时间来破解你的例子,看看我得到了什么。 同时我研究了一些基于Curl的函数来登录和抢夺UbuntuOne HTML页面。 一旦我稳定它,我会尝试在这里发布。

是的,我会写一个几乎完整的基于PHP的API,并会注意到你,只有一些时间,我现在有点过载:S

我很乐意向你展示我的作品,也许有一天我会克服恐惧并申请加工Canonical,这对我来说就像是一场梦。 目前我已经写了一篇小评论,向您展示我目前的项目,如果您愿意,我很乐意给您发送一份副本。 我想将它作为自由软件发布,但我是那些需要掩盖一个网络项目的人之一,并确保它在发布之前是安全的。

(这不是任何一种perma-link,抱歉) http://marcos.colorvamp.com/mongo/lain-and-ubuntu-one/

如果你查看基页…就像在我的土地上说的那样。 “在铁匠的房子里,有木刀”:-)

谢谢你的答复 :-)