Posts Tagged ‘Google’

[App Engine] Google App Engine 10个应用的限制

Tuesday, January 12th, 2010

Google App Engine默认是容许用户最多创建10个应用,如果你已经用完了10个怎么办?

Chinese 方式:申请更多的Google Account,为每个账户申请GAE。
American 方式:到邮件列表发个Email,说明自己的情况,请Google Support来处理。

今天在邮件列表里面见到一封这样的信,Google的Support人员在2天后为他增加了10个应用的限制。

使用Google Analytics追踪和分析网站Flash的点击

Tuesday, January 12th, 2010

Google Analytics是当前最流行的网站流量统计和分析的工具,加上与Google Adwords和Google Andsense的集成,使它成为企业级的网站分析工具。在前面的《Flex/ActionScript访问Google Analytics数据》的文章中,介绍了如何将Google Analytics的统计数据导出,在Flex/Flash的应用里面展示。现在来讨论如何统计记录网站上Flash的点击,这对于使用Flex/Flash开发网站应用是十分重要的。

在Google Analytics刚发布的时候,想统计站点内Flash的点击是做不到的。后有开发者通过分析Google的Track脚本,在Flash内部使用ExternalInterface来调用Google Analytics的Track代码实现统计。Google Analytics发布了API后,包括了一个用于统计Flash访问的项目gaforflash,其托管在Google Code上并且是Open Source的。其实现的Track功能和ga.js是完全一样的,可以在Flash和Flex的开发环境中使用,但是这个项目是基于ActionScript 3的版本。

gaforflash的使用方式有两种:Bridge Model和AS 3 Model。Bridge Model是指站点的页面已经实现了Google Analytics的ga.js的统计,想要统计页面里embed的Flash的点击,使用gaforflash项目调用外部的ga.js里面的函数来实现统计,所以称为“桥”模式。使用Bridge Model要注意的是embed Flash的标签里面allowScriptAccess的属性设为True,否则无法调用外部的js脚本;AS 3 Model是纯ActionScript的模式,如果你的Flash会Embed到不同站点去,也就是有cross domain的情况产生,使用AS 3 Model就会没有问题,AS 3 Model使用shareobject的来存储访问者session等信息,如果Track的Flash也使用了shareobject,请注意命名。

gaforflash统计的信息目前有两种:Page Views和Event。如果是全站的Flash适用于Page View模式;如果是一个简单的播放器嵌在HTML页面,适用于Event模式。

<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" 
xmlns:ga4flash="com.google.analytics.components.*" 
addedToStage="trackInitialView()">
 
<ga4flash:FlexTracker id="tracker" account="UA-111-222" 
visualDebug="true" mode="AS3"/>
 
<mx:Script>
    <![CDATA[
        public function trackInitialView():void
        {
            tracker.debug.minimizedOnStart = true;
            tracker.trackPageview("/pane1");
        }
    ]]>
</mx:Script>
 
<mx:Accordion x="31" y="43" width="380" height="423" 
change="tracker.trackPageview('/pane' + String(event.newIndex+1));">
    <mx:Canvas label="Pane 1" width="100%" height="100%">
        <mx:Text text="Welcome to Pane 1."/>
    </mx:Canvas>
    <mx:Canvas label="Pane 2" width="100%" height="100%">
        <mx:Text text="Benvenuti a Pane 2."/>
    </mx:Canvas>
    <mx:Canvas label="Pane 3" width="100%" height="100%">
        <mx:Text text="Bienvenue à Pane 3!"/>
    </mx:Canvas>
</mx:Accordion>
 
</mx:Application>

上面例子简单的实现了Page View的方式,当点击Accordion的不同标签时会按照每个标签一次Page View的方式记录到Google Analytics中。

Event的代码也很简单,就是在按钮或者空间的点击事件中加入:

    tracker.trackEvent("Something", "Some Status", "Some Component");

gafoflash还有很多其它的应用方式,譬如前面文章曾经介绍过《Adobe AIR Badge详解》,当你自定义一个Badge的时候有没有想过通过gaforflash的Event模式来确定你的AIR应用的安装情况呢?或者在一个Media Player中可以根据Event的统计来确定视频最佳的缓冲大小?

参看文章:
Google Analytics within Flex/Flash Applications
Google Analytics Tracking for Adobe Flash

Flex/ActionScript访问Google Analytics数据

Tuesday, January 12th, 2010

在Adobe AIR刚刚推出的时候,有一个Google Analytics AIR的应用非常吸引人。它读取Google Analytics的数据,运用各种Chart图表进行展示。后来这个项目从beta到正式版,名字也更换为Polaris,在Adobe的AIR Market上叫做Polaris For Google Anlytics。当它beta版本的时候,Google Analytics还没有发布相关的API,如何取得Google Analytics的数据着实让人迷惑。现在Google Analytics发布了Data Export API,利用这个API我们也可以读取Google Analytics的数据,做出自己的AIR应用。而且Google Analytics现在可以跟踪记录Flash/Flex应用里面的按钮或者链接事件,能够让我们更准确的记录和分析用户在网站上的行为。

首先让我们了解一下Google Analytics Data Export API的一些读取限制政策,避免在开发的时候遇到访问数量的限制:

  • Google Analytics Data Export API的数量读取限制是针对一个站点的,即一个profile的。
  • 24小时内不能多于10,000个HTTP Request。
  • 10秒内不能多于100个HTTP Request。
  • 每次返回的XML数据中,不多于10,000个entry。
  • 开发一个AIR应用使用Google的API,首先要让用户通过Google账户的认证。Google API的认证有3种方式:

  • AuthSub Proxy Authentication
  • OAuth Authentication
  • ClientLogin Username/Password Authentication
  • 由于是AIR客户端,所以在这篇文章中我们选择ClientLogin Username/Password Authentication的认证方式,如果想开发基于OAuth的认证方式可以参考以前的文章《ActionScript For OAuth Step By Step》。要注意的是开发WEB Application的话,尽量选择OAuth的认证方式,因为Google安全策略的限制,ClientLogin的认证方式可能不能正常登陆。

    ClientLogin Username/Password Authentication是Google为客户端提供的认证方式,AIR的程序或者使用C#、C++开发的桌面程序都可以使用这种认证方式。这种认证方式很简单,就是向ClientLogin的认证地址POST一个表单,登录成功后从返回的数据中提取Auth这个变量。在以后的读取过程中在HTTP Header的Authorization中加入Auth这个变量就可以。

    ClientLogin的POST地址:
    https://www.google.com/accounts/ClientLogin

    表单有以下几项:

  • accountType 账户类型有GOOGLE、HOSTED和HOSTED_OR_GOOGLE。由于是Google Analytics和Google Account绑定在一起,所以这里是使用GOOGLE这个账户类型。
  • Email Google Analytics对应的Gmail账号。
  • Passwd Gmail密码
  • service 这里service是个固定值:analytics。如果想使用其它服务的API,这里应该是对应服务的名称。具体参考看Google Service Name List
  • source 可以是空的,也可以按照companyName-applicationName-versionID这个格式填写。
  • 现在我们写个函数登录和处理登录的返回值,取得想要的Auth变量。

    private function login(email : String, password : String) : void
    {
        var _loginRequest : URLRequest = new URLRequest();
        _loginRequest.url = 'https://www.google.com/accounts/ClientLogin';
        _loginRequest.method = URLRequestMethod.POST;
        _loginRequest.requestHeaders.push(
            new URLRequestHeader('Content-Type',
                'application/x-www-form-urlencoded'));
     
        var _data : URLVariables = new URLVariables();
        _data.accountType = 'GOOGLE';
        _data.Email = email;
        _data.Passwd = password;
        _data.service = 'analytics';
        _data.source = '';
     
        _loginRequest.data = _data;
     
        var _loader : URLLoader = new URLLoader();
        _loader.addEventListener(Event.COMPLETE, doLogin);
        _loader.addEventListener(HTTPStatusEvent.HTTP_STATUS, doLogin);
        _loader.addEventListener(IOErrorEvent.IO_ERROR, doLogin);
        _loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, doLogin);
        try
        {
            _loader.load(_loginRequest);
        }
        catch(error : Error)
        {
            Alert.show(error.message);
        }
    }
     
    private function doLogin(event : Event) : void
    {
        switch(event.type)
        {
            case Event.COMPLETE:
                authGoogle((event as Event).target.data);
            break;
            case HTTPStatusEvent.HTTP_STATUS:
                if((event as HTTPStatusEvent).status == 200)
                {
                    //do something
                }
            break;
            case IOErrorEvent.IO_ERROR:
                Alert.show((event as IOErrorEvent).text);
            break;
            case SecurityErrorEvent.SECURITY_ERROR:
                Alert.show((event as SecurityErrorEvent).text);
            break;
        }
    }
     
    private function authGoogle(data : Object) : void
    {
        var auth : String;
        var sid : String;
        var lsid : String;
     
        var _array : Array = (data as String).split('\n');
        for(var i : int = 0; i &lt; _array.length; i++)     {
            if((_array[i] as String).length &gt; 0)
            {
                var _param : URLVariables = new URLVariables()
                _param.decode(_array[i] as String);
                if(_param.hasOwnProperty('Auth'))
                    auth = _param['Auth'];
                if(_param.hasOwnProperty('SID'))
                    sid = _param['SID'];
                if(_param.hasOwnProperty('LSID'))
                    lsid = _param['LSID'];
            }
        }
        analyticsProfiles(auth);
    }

    一个Google Account可以添加多个Web Site的Profile,同时还可能会有别人share的Profile。所以在认证完成后,需要从Google Analytics取得Profile的列表。这个Profile列表是一个feed的xml文件,读取这个文件的时候要注意使用上一步的auth添加到HTTP的Header中。

    private function analyticsProfiles(auth : String) : void
    {
        var _profiles : URLRequest = new URLRequest();
        _profiles.url =
            'https://www.google.com/analytics/feeds/accounts/default';
        _profiles.method = URLRequestMethod.GET;
     
        var _auth : String = 'GoogleLogin auth=' + auth;
        _profiles.requestHeaders.push(
            new URLRequestHeader('Authorization', _auth));
     
        var _loader : URLLoader = new URLLoader();
        _loader.addEventListener(Event.COMPLETE, doProfile);
        try
        {
            _loader.load(_profiles);
        }
        catch(err : Error)
        {
            //Alert Error Message
        }
    }

    返回的xml数据:

    <?xml version='1.0' encoding='UTF-8'?>
    <feed xmlns='http://www.w3.org/2005/Atom' 
        xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' 
        xmlns:dxp='http://schemas.google.com/analytics/2009'>
        <id>
            http://www.google.com/analytics/feeds/accounts/genedna@gmail.com
        </id>
        <updated>2009-01-07T22:13:35.000-08:00</updated>
        <title type='text'>Profile list for genedna@gmail.com</title>
        <link rel='self' type='application/atom+xml' 
            href='http://www.google.com/analytics/feeds/accounts/default'/>
        <author>
            <name>Google Analytics</name>
        </author>
        <generator version='1.0'>Google Analytics</generator>
        <openSearch:totalResults>2</openSearch:totalResults>
        <openSearch:startIndex>1</openSearch:startIndex>
        <openSearch:itemsPerPage>2</openSearch:itemsPerPage>
        <entry>
            <id>
                http://www.google.com/analytics/feeds/accounts/ga:10200000
            </id>
            <updated>2008-08-11T19:21:12.000-07:00</updated>
            <title type='text'>www.douhua.im</title>
            <link rel='alternate' type='text/html' 
                href='http://www.google.com/analytics'/>
            <dxp:tableId>ga:10200000</dxp:tableId>
            <dxp:property name='ga:accountId' value='1400000'/>
            <dxp:property name='ga:accountName' value='Meaglith Ma'/>
            <dxp:property name='ga:profileId' value='10204831'/>
            <dxp:property name='ga:webPropertyId' value='UA-140000-15'/>
            <dxp:property name='ga:currency' value='USD'/>
            <dxp:property name='ga:timezone' value='Asia/Shanghai'/>
        </entry>
    </feed>

    返回的XML数据中tableId的值是一一对应Profile的,我们要访问douhua.im站点的访问数据情况,必须使用tableId这个值。读取Google Analytics Report的数据方式和读取Profile的方式一样,只是根据不同的参数读取不同的Report类型,目前可以访问的类型有Visitors、Traffic Sources 、Content、Goals和Ecommerce,访问的地址根据需要进行拼装,具体请参考Google Analytics Data Export API的Dimensions & Metrics Reference章节。示例:

    https://www.google.com/analytics/feeds/data?ids=ga:10204831&dimensions=ga:country,ga:city&metrics=ga:pageviews&start-date=2009-05-01&end-date=2009-05-31

    ActionScript For OAuth Step By Step

    Tuesday, January 12th, 2010

    OAuth协 议是现在众多网站提供API服务所选择的认证方式,是由Blaine Cook、Chris Messina、Larry Halff 及David Recordon共同发起,目的是为API服务提供一个安全、统一和开放的标准。众多知名的互联网公司选择了OAuth协议,如:Twitter、 Google和MySpace等;或者采用类似的认证方式,如flickr等。

    Twitter.com的API在初期使用了Basic Auth的认证方 式,第三方应用只要把用户名和密码进行BASE64加密,加入到HTTP的Header中就可以使用访问受限资源的API。随着用户和第三方应用越来越 多,把用户名和密码交给第三方应用的方式越来越不安全;如果修改了密码,要在众多的第三方程序间同步密码也变得非常麻烦。最终Twitter的开发团队终 于下决心采用OAuth协议对第三方应用进行授权,用户可以随时在设置里取消授权,就算修改了密码也不必为修改授权应用的密码操心。因为OAuth协议的 安全性和可靠性,在当今流行的网络服务提供商中绝大多数选择了它。

    Google也是OAuth的支持者,同时Google的DeWitt Clinton也为OAuth开发做出了巨大的贡献。我们用Google的OAuth Diagram来解释一下OAuth协议的流程。

    Web Application是指我们开发的第三方应用(在下面的讲解中我们假设开发了一个访问Google日历的Flash Application),Google Account Authentication指Google的认证服务(这里可以简单的理解为是OAuth认证服务器),Google Service Access指Google提供的API服务(下面的讲解中用Google Calendar Service来替代)。当用户使用我们假设的Flash应用访问Google日历时,需要通过OAuth协议来进行授权:

  • 首先Flash应用会访问Google OAuth认证服务,获取一个Request Token。
  • 从Google OAuth认证服务返回一个未授权的Request Token和对应的Request Token Secret。
  • Flash应用继续访问Google OAuth认证服务,要求对未授权的Request Token进行授权。
  • 如果用户已经登录了Google,会被重定向到一个请求授权页面。
  • 用户对应用进行授权。
  • 从Google OAuth认证服务返回已授权的Request Token和对应的Request Token Secret。
  • 用已授权的Request Token和对应的Request Token Secret在Google OAuth服务换取Access Token。
  • 从Google OAuth认证服务返回Access Token和对应的Access Token Secret。
  • 使用Access Token和对应的Access Token Secret访问Google Calendar服务。
  • 从Google Calendar返回用户的日历数据。
  • 在Twitter的众第三方多应用中,AIR类占了很大比例。其中最知名的就是Twhirl了,可惜的是它目前还不支持OAuth认证。要想为Twhirl加入OAuth其实很容易,看完下面的例子你也可以轻松做到。

    下面选择豆瓣的API为例,说明如何使用ActionScript 3开发基于OAuth认证的程序。示例程序可以在Google Code找到,在那里还有其它语言的OAuth例子。程序使用了一个OAuth的ActionScript 3类库,地址是 http://code.google.com/p/oauth-as3/,由于在OAuth的认证过程中需要HMAC-SHA1加密,所以程序还导入了一个加密功能的ActionScript 3类库,地址是 http://code.google.com/p/as3crypto/ 。

    首先要在豆瓣注册一个应用,会得到豆瓣分配的Key和Secret Key。代码里面先把Key、Secret Key和豆瓣的OAuth Service地址定义为常量。

    private static var DOUBAN_KEY : String = "xxxxxxxxxxxx";
    private static var DOUBAN_SECRET_KEY : String = "xxxxxxxxx";
     
    private static var DOUBAN_REQUEST : String =
            "http://www.douban.com/service/auth/request_token";
    private static var DOUBAN_AUTHORIZE : String =
            "http://www.douban.com/service/auth/authorize";
    private static var DOUBAN_ACCESS_TOKEN : String =
            "http://www.douban.com/service/auth/access_token";

    然后参照上图 流程1 编写一个获取Request Token访问地址的函数。

    /**
    * 生成获取Request Token的URL
    */
    private function generalDoubanRequestTokenURL() : String
    {
        var oauth : OAuthRequest = new OAuthRequest("GET", DOUBAN_REQUEST);
        oauth.consumer = new OAuthConsumer(DOUBAN_KEY, DOUBAN_SECRET_KEY);
        return oauth.buildRequest(new OAuthSignatureMethod_HMAC_SHA1(),
                            OAuthRequest.RESULT_TYPE_URL_STRING);
    }

    调用这个函数会生成一个URL地址,把这个地址复制到浏览器里进行访问,会在页面中看到如下的显示:

    oauth_token=abcde&oauth_token_secret=fghil

    这两个参数的值就是未授权的Request Token和Request Token Secret。根据 流程3 对Request Token的进行授权的访问地址是
    http://www.douban.com/service/auth/authorize?oauth_token=abcde
    在浏览器里面输入这个地址后,就会进入到豆瓣的授权页面,如果用户还没有登录会先重定向到登录页面。

    用户对应用进行授权后,需要根据 流程7 换取Access Token。
    下面编写了一个函数生成换取Access Token访问地址的函数。

    /**
    * 生成换取Access Token使用的URL。
    */
    private function generalDoubanAccessTokenURL() : String
    {
        var oauth : OAuthRequest = new OAuthRequest("GET", DOUBAN_ACCESS_TOKEN);
        oauth.consumer = new OAuthConsumer(DOUBAN_KEY, DOUBAN_SECRET_KEY);
        oauth.token = new OAuthToken('abcde', 'fghil');
        return oauth.buildRequest(new OAuthSignatureMethod_HMAC_SHA1(),
                      OAuthRequest.RESULT_TYPE_URL_STRING);
    }

    将上面函数生成的地址复制到浏览器后访问,会在页面中看到如下的显示:

    oauth_token=abcdww&oauth_token_secret=daseccs

    这次得到的就是访问受限资源的Access Token和Access Token Secret了,使用这两个参数就可以生成访问豆瓣受限资源的URL了。

    /**
    * General Access douban.com's resource url
    */
    private function generalPostURL(res:String, params:Object = null):String
    {
        var oauth : OAuthRequest = new OAuthRequest('POST', res);
        oauth.consumer = new OAuthConsumer(DOUBAN_KEY, DOUBAN_SECRET_KEY);
        oauth.token = new OAuthToken(‘abcdww’, ‘daseccs’);
        return oauth.buildRequest(new OAuthSignatureMethod_HMAC_SHA1(),
                          OAuthRequest.RESULT_TYPE_URL_STRING);
    }

    访问的受限资源范围是根据授权用户的权限所限定的,授权用户无权访问的数据,第三方应用也无法访问。

    上面的代码中,OAuth的ActionScript 3类库帮助我们封装了很多细节,很容易地实现了OAuth协议的授权。把其中豆瓣OAuth服务地址替换成Twitter OAuth服务地址,就可以生成访问Twitter的OAuth程序。这些代码很容易运用到Flex/AIR的程序里,开发类似twhirl这样的第三方应用也变得简单了。

    Google Map API For Flash的3D视图功能

    Tuesday, January 12th, 2010

    Google Map API For Flash 1.16这个版本在2D地图之上增加了3D 视图,可以从3维的角度去观察和使用Google地图,在控制的操作上同Google Earth相同,这不仅让人无限遐想是不是Google Earth将来也会出Flash的版本?3D视图的Flash API为基于Google Map的应用开发带来了新的视角,可以基于一个平面的地图构建一个立体的虚拟城市,或者在3D视图上开发出一个Flash赛车游戏….

    想要使用3D版本的地图API,必须使用Flash Player 9 或者10的版本才可可以。由于3D的视角有利于发挥Flash Player 10的高级几何特性,所以官方推荐开发者尽量使用Flash Player 10。如果在代码中没有使用Flash Player 10才有的类,那么编译的程序在Flash Player 9和10上浏览都正常;如果使用10才有的类库,用Flash Player 9去浏览就会抛出一个错误,在这种情况下建议使用为9和10维护不同的版本。需要将Flex SDK的版本更新到3.3以上,支持对Flash Player 10的编译。

    下面从最简单的一个例子开始,了解和使用Google Map API For Flash的3D视图功能。

    <?xml version="1.0" encoding="utf-8"?>
    <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" 
        layout="absolute"> 
        <maps:Map3D xmlns:maps="com.google.maps.*" 
            mapevent_mappreinitialize="onMapPreinitialize(event)" 
            id="map" width="100%" height="100%" key="abc"/>
        <mx:Script>  
            <![CDATA[  
                import com.google.maps.LatLng;  
                import com.google.maps.Map3D;  
                import com.google.maps.MapEvent;  
                import com.google.maps.MapOptions;  
                import com.google.maps.MapType;  
                import com.google.maps.View;  
                import com.google.maps.geom.Attitude;  
     
                private function onMapPreinitialize(event:MapEvent):void 
                {    
                    var myMapOptions:MapOptions = new MapOptions();    
                    myMapOptions.zoom = 12;    
                    myMapOptions.center = 
                          new LatLng(40.756054, -73.986951);    
                    myMapOptions.mapType = MapType.NORMAL_MAP_TYPE;    
                    myMapOptions.viewMode = View.VIEWMODE_PERSPECTIVE;    
                    myMapOptions.attitude = new Attitude(20,30,0);    
                    map.setInitOptions(myMapOptions);  
                }  
            ]]>
        </mx:Script>
    </mx:Application>

    在代码里可以看到,要使用Map3D对象代替Map对象,然后在Map3D对象的MAP_PREINITIALIZE事件中初始化这个对象。除了设定viewMode的值为View.VIEWMODE_PERSPECTIVE,还要设置一个Map3D对象的属性Attitude。 Attitude是根据航空学中飞行器在飞行过程中视角来定义地图的视角,有roll、yaw和pitch三个值。从下面的张图片中很容易理解这三个角度和地图的关系。

    要增加地图的3D控制,就需要在Map3D的MAP_READY的事件中增加NavigationControl。

    map.addControl(new NavigationControl());

    在上面的代码中为Map3D的viewMode指定一个VIEWMODE_PERSPECTIVE,意思就是为地图建立一个Attitude定义的摄像机视角来看地图。viewMode如果设定为VIEWMODE_2D,就是建立一个2D视角的地图;如果设定为VIEWMODE_ORTHOGONAL,就是定义一个倾斜的固定视角。
    在Map3D提供一个flyTo()的函数,可以让用户从当前地点移动到另一个经纬度,实现和Google Earth一样的移动效果。

    map.flyTo(new LatLng(40.756054, -73.986951), 12, new Attitude(20,30,0), 3);

    更多的实例和说明请访问http://code.google.com/apis/maps/documentation/flash/3d-maps.html

    [Google AppEngine] 中使用Flex实现Google Account SignIn/SignOut

    Monday, January 4th, 2010

    在AppEngine中可以使用Google Account实现认证。如果使用Flex Application作为UI,使用按钮实现Sign In/Sign Out需要把URL通过HTML页面的FlashVars传递到Flex Application中。但是这个URL的值是几个HTTP地址经过&符号拼接而成,在Flex里通过FlexGlobals.topLevelApplication.parameters读取的时候只能读取第一个&符号前的地址。所以这个URL需要在Python代码里面进行URL encode,在Flex Application中URL decode。

    Python代码:

        def get (self):
            user = users.get_current_user()
            #Check login user
            if user:
                sign = urllib2.quote(users.create_logout_url('/openparty'))
            else:
                sign = urllib2.quote(users.create_login_url('/openparty'))
            #Init template vars
            template_values = {
                'sign' : sign,
            }

    Flex代码:

      var url : String = decodeURI(FlexGlobals.topLevelApplication.parameters['sign']);

    [Google AppEngine] app.yaml的skip_files

    Sunday, January 3rd, 2010

    app.yaml的skip_files项通过设置正则表达式,设置在Upload过程中不被上传到AppEngine的文件和文件夹。 这个skip_files有默认值:

    skip_files: |
     ^(.*/)?(
     (app\.yaml)|
     (app\.yml)|
     (index\.yaml)|
     (index\.yml)|
     (#.*#)|
     (.*~)|
     (.*\.py[co])|
     (.*/RCS/.*)|
     (\..*)|
     )$

    如果需要修改默认值,需要在app.yaml中重写skip_files的值。下面的例子加入了.bak和.DS_Store在排除的文件中:

    skip_files:
    - ^(.*/)?app\.yaml
    - ^(.*/)?app\.yml
    - ^(.*/)?index\.yaml
    - ^(.*/)?index\.yml
    - ^(.*/)?#.*#
    - ^(.*/)?.*~
    - ^(.*/)?.*\.py[co]
    - ^(.*/)?.*/RCS/.*
    - ^(.*/)?\..*
    - ^(.*/)?.*\.bak$
    - ^(.*/)?.DS_Store

    这个写法跟AppEngine刚刚发布的时候不一样,不清楚是在哪个版本更新中进行了修改。AppEngine的新版本不断的发布,不知道最后的版本会带来什么功能。

    介绍一个名为Picky的AppEngine项目,是@livid开发的基于AppEngine的博客应用。源代码在:http://code.google.com/p/project-picky