Browse Source

Create gh-pages branch via GitHub

agapple 12 years ago
commit
e3fc6059c4
7 changed files with 757 additions and 0 deletions
  1. BIN
      images/bkg.png
  2. BIN
      images/blacktocat.png
  3. 441 0
      index.html
  4. 1 0
      javascripts/main.js
  5. 0 0
      params.json
  6. 68 0
      stylesheets/pygment_trac.css
  7. 247 0
      stylesheets/stylesheet.css

BIN
images/bkg.png


BIN
images/blacktocat.png


+ 441 - 0
index.html

@@ -0,0 +1,441 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset='utf-8'>
+    <meta http-equiv="X-UA-Compatible" content="chrome=1">
+
+    <link rel="stylesheet" type="text/css" href="stylesheets/stylesheet.css" media="screen" />
+    <link rel="stylesheet" type="text/css" href="stylesheets/pygment_trac.css" media="screen" />
+    <link rel="stylesheet" type="text/css" href="stylesheets/print.css" media="print" />
+
+    <title>Canal by otter-projects</title>
+  </head>
+
+  <body>
+
+    <header>
+      <div class="container">
+        <h1>Canal</h1>
+        <h2>阿里巴巴mysql数据库binlog的增量订阅&amp;消费组件</h2>
+
+        <section id="downloads">
+          <a href="https://github.com/otter-projects/canal/zipball/master" class="btn">Download as .zip</a>
+          <a href="https://github.com/otter-projects/canal/tarball/master" class="btn">Download as .tar.gz</a>
+          <a href="https://github.com/otter-projects/canal" class="btn btn-github"><span class="icon"></span>View on GitHub</a>
+        </section>
+      </div>
+    </header>
+
+    <div class="container">
+      <section id="main_content">
+        <div>
+    <div>
+<p>  </p>
+<h1>背景</h1>
+<p>   早期,阿里巴巴B2B公司因为存在杭州和美国双机房部署,存在跨机房同步的业务需求。不过早期的数据库同步业务,主要是基于trigger的方式获取增量变更,不过从2010年开始,阿里系公司开始逐步的尝试基于数据库的日志解析,获取增量变更进行同步,由此衍生出了增量订阅&amp;消费的业务,从此开启了一段新纪元。ps. 目前内部使用的同步,已经支持mysql5.x和oracle部分版本的日志解析</p>
+<p> </p>
+<p>基于日志增量订阅&amp;消费支持的业务:</p>
+<ol>
+<li>数据库镜像</li>
+<li>数据库实时备份</li>
+<li>多级索引 (卖家和买家各自分库索引)</li>
+<li>search build</li>
+<li>业务cache刷新</li>
+<li>价格变化等重要业务消息</li>
+</ol>
+<h1>项目介绍</h1>
+<p>   名称:canal [kə'næl]</p>
+<p>   译意: 水道/管道/沟渠 </p>
+<p>   语言: 纯java开发</p>
+<p>   定位: 基于数据库增量日志解析,提供增量数据订阅&amp;消费,目前主要支持了mysql</p>
+<p> </p>
+<h2>工作原理</h2>
+<h3>mysql主备复制实现</h3>
+<p><img src="http://dl.iteye.com/upload/attachment/0080/3086/468c1a14-e7ad-3290-9d3d-44ac501a7227.jpg" alt=""><br> 从上层来看,复制分成三步:</p>
+<ol>
+<li>master将改变记录到二进制日志(binary log)中(这些记录叫做二进制日志事件,binary log events,可以通过show binlog events进行查看);</li>
+<li>slave将master的binary log events拷贝到它的中继日志(relay log);</li>
+<li>slave重做中继日志中的事件,将改变反映它自己的数据。</li>
+</ol>
+<h3>canal的工作原理:</h3>
+<p><img width="590" src="http://dl.iteye.com/upload/attachment/0080/3107/c87b67ba-394c-3086-9577-9db05be04c95.jpg" alt="" height="273"></p>
+<p>原理相对比较简单:</p>
+<ol>
+<li>canal模拟mysql slave的交互协议,伪装自己为mysql slave,向mysql master发送dump协议</li>
+<li>mysql master收到dump请求,开始推送binary log给slave(也就是canal)</li>
+<li>canal解析binary log对象(原始为byte流)</li>
+</ol>
+<h1>架构</h1>
+<p><img width="548" src="http://dl.iteye.com/upload/attachment/0080/3126/49550085-0cd2-32fa-86a6-f676db5b597b.jpg" alt="" height="238"></p>
+<p>说明:</p>
+<ul>
+<li>server代表一个canal运行实例,对应于一个jvm</li>
+<li>instance对应于一个数据队列  (1个server对应1..n个instance)</li>
+</ul>
+<p>instance模块:</p>
+<ul>
+<li>eventParser (数据源接入,模拟slave协议和master进行交互,协议解析)</li>
+<li>eventSink (Parser和Store链接器,进行数据过滤,加工,分发的工作)</li>
+<li>eventStore (数据存储)</li>
+<li>metaManager (增量订阅&amp;消费信息管理器)</li>
+</ul>
+<h3>数据对象格式:<a href="https://github.com/otter-projects/canal/blob/master/protocol/src/main/java/com/alibaba/otter/canal/protocol/EntryProtocol.proto">EntryProtocol.proto</a>
+</h3>
+<pre name="code">Entry
+    Header
+        logfileName [binlog文件名]
+        logfileOffset [binlog position]
+        executeTime [发生的变更]
+        schemaName 
+        tableName
+        eventType [insert/update/delete类型]
+    entryType   [事务头BEGIN/事务尾END/数据ROWDATA]
+    storeValue  [byte数据,可展开,对应的类型为RowChange]
+    
+RowChange
+    isDdl       [是否是ddl变更操作,比如create table/drop table]
+    sql     [具体的ddl sql]
+    rowDatas    [具体insert/update/delete的变更数据,可为多条,1个binlog event事件可对应多条变更,比如批处理]
+        beforeColumns [Column类型的数组]
+        afterColumns [Column类型的数组]
+        
+Column 
+    index       
+    sqlType     [jdbc type]
+    name        [column name]
+    isKey       [是否为主键]
+    updated     [是否发生过变更]
+    isNull      [值是否为null]
+    value       [具体的内容,注意为文本]</pre>
+<p>说明:</p>
+<ul>
+<li>可以提供数据库变更前和变更后的字段内容,针对binlog中没有的name,isKey等信息进行补全</li>
+<li>可以提供ddl的变更语句</li>
+</ul>
+<h1>QuickStart</h1>
+<h2>几点说明:(mysql初始化)</h2>
+<p>a.  canal的原理是基于mysql binlog技术,所以这里一定需要开启mysql的binlog写入功能,并且配置binlog模式为row. </p>
+<pre name="code">[mysqld]
+log-bin=mysql-bin #添加这一行就ok
+binlog-format=ROW #选择row模式
+server_id=1 #配置mysql replaction需要定义,不能和canal的slaveId重复</pre>
+b.  canal的原理是模拟自己为mysql slave,所以这里一定需要做为mysql slave的相关权限.</div>
+<div>
+<pre name="code">CREATE USER canal IDENTIFIED BY 'canal';  
+GRANT SELECT, REPLICATION SLAVE, REPLICATION CLIENT ON *.* TO 'canal'@'%';
+-- GRANT ALL PRIVILEGES ON *.* TO 'canal'@'%' ;
+FLUSH PRIVILEGES;</pre>
+<p>针对已有的账户可通过grants查询权限:</p>
+<h2>启动步骤:</h2>
+<p>1.  下载canal</p>
+<p>下载部署包</p>
+<pre name="code">wget http://canal4mysql.googlecode.com/files/canal.deployer-1.0.0.tar.gz</pre>
+<p>or </p>
+<p>自己编译 </p>
+<pre name="code">git clone git@github.com:otter-projects/canal.git
+cd canal; 
+mvn clean install -Dmaven.test.skip -Denv=release</pre>
+<p>    编译完成后,会在根目录下产生target/canal.deployer-$version.tar.gz </p>
+<p> </p>
+<p>2.  解压缩</p>
+<pre name="code">mkdir /tmp/canal
+tar zxvf canal.deployer-1.0.0.tar.gz  -C /tmp/canal</pre>
+<p>   </p>
+<p>   解压完成后,进入/tmp/canal目录,可以看到如下结构:</p>
+<p> </p>
+<pre name="code">drwxr-xr-x 2 jianghang jianghang  136 2013-02-05 21:51 bin
+drwxr-xr-x 4 jianghang jianghang  160 2013-02-05 21:51 conf
+drwxr-xr-x 2 jianghang jianghang 1.3K 2013-02-05 21:51 lib
+drwxr-xr-x 2 jianghang jianghang   48 2013-02-05 21:29 logs</pre>
+<p> </p>
+<p>3.  配置修改</p>
+<p> </p>
+<p>公用参数:   </p>
+<pre name="code">vi conf/canal.properties</pre>
+<pre name="code">#################################################
+#########               common argument         ############# 
+#################################################
+canal.id= 1
+canal.address=
+canal.port= 11111
+canal.zkServers=
+# flush data to zk
+canal.zookeeper.flush.period = 1000
+## memory store RingBuffer size, should be Math.pow(2,n)
+canal.instance.memory.buffer.size = 32768
+
+## detecing config
+canal.instance.detecting.enable = false
+canal.instance.detecting.sql = insert into retl.xdual values(1,now()) on duplicate key update x=now()
+canal.instance.detecting.interval.time = 3 
+canal.instance.detecting.retry.threshold = 3 
+canal.instance.detecting.heartbeatHaEnable = false
+
+# support maximum transaction size, more than the size of the transaction will be cut into multiple transactions delivery
+canal.instance.transactionn.size =  1024
+
+# network config
+canal.instance.network.receiveBufferSize = 16384
+canal.instance.network.sendBufferSize = 16384
+canal.instance.network.soTimeout = 30
+
+#################################################
+#########               destinations            ############# 
+#################################################
+canal.destinations= example
+
+canal.instance.global.mode = spring 
+canal.instance.global.lazy = true  ##修改为false,代表立马启动
+#canal.instance.global.manager.address = 127.0.0.1:1099
+canal.instance.global.spring.xml = classpath:spring/memory-instance.xml
+#canal.instance.global.spring.xml = classpath:spring/default-instance.xml</pre>
+<p> </p>
+<p>应用参数:</p>
+<pre name="code">vi conf/example/instance.properties</pre>
+<pre name="code">#################################################
+## mysql serverId
+canal.instance.mysql.slaveId = 1234
+
+# position info
+canal.instance.master.address = 127.0.0.1:3306 #改成自己的数据库地址
+canal.instance.master.journal.name = 
+canal.instance.master.position = 
+canal.instance.master.timestamp = 
+
+#canal.instance.standby.address = 
+#canal.instance.standby.journal.name =
+#canal.instance.standby.position = 
+#canal.instance.standby.timestamp = 
+
+# username/password
+canal.instance.dbUsername = retl  #改成自己的数据库信息
+canal.instance.dbPassword = retl  #改成自己的数据库信息
+canal.instance.defaultDatabaseName =   #改成自己的数据库信息
+canal.instance.connectionCharsetNumber = 33  #改成自己的数据库信息
+canal.instance.connectionCharset = UTF-8  #改成自己的数据库信息
+
+# table regex
+canal.instance.filter.regex = .*\\..*
+
+#################################################
+</pre>
+<p> </p>
+<p> </p>
+<p> 说明:</p>
+<ul>
+<li>canal.instance.connectionCharset 代表数据库的编码方式对应到java中的编码类型,比如UTF-8,GBK , ISO-8859-1</li>
+<li>canal.instance.connectionCharsetNumber 代表数据库的编码方式对应mysql中的唯一id,详细的映射关系可查看:com.mysql.jdbc.CharsetMapping.INDEX_TO_CHARSET<br>针对常见的编码:<br>utf-8  &lt;=&gt;  33<br>gb2312 &lt;=&gt; 24<br>gbk &lt;=&gt; 28</li>
+</ul>
+<p>4.   准备启动</p>
+<p> </p>
+<pre name="code">sh bin/startup.sh</pre>
+<p> </p>
+<p>5.  查看日志</p>
+<pre name="code">vi logs/canal/canal.log</pre>
+<pre name="code">2013-02-05 22:45:27.967 [main] INFO  com.alibaba.otter.canal.deployer.CanalLauncher - ## start the canal server.
+2013-02-05 22:45:28.113 [main] INFO  com.alibaba.otter.canal.deployer.CanalController - ## start the canal server[10.1.29.120:11111]
+2013-02-05 22:45:28.210 [main] INFO  com.alibaba.otter.canal.deployer.CanalLauncher - ## the canal server is running now ......</pre>
+<p>     </p>
+<p>    具体instance的日志:</p>
+<pre name="code">vi logs/example/example.log</pre>
+<pre name="code">2013-02-05 22:50:45.636 [main] INFO  c.a.o.c.i.spring.support.PropertyPlaceholderConfigurer - Loading properties file from class path resource [canal.properties]
+2013-02-05 22:50:45.641 [main] INFO  c.a.o.c.i.spring.support.PropertyPlaceholderConfigurer - Loading properties file from class path resource [example/instance.properties]
+2013-02-05 22:50:45.803 [main] INFO  c.a.otter.canal.instance.spring.CanalInstanceWithSpring - start CannalInstance for 1-example 
+2013-02-05 22:50:45.810 [main] INFO  c.a.otter.canal.instance.spring.CanalInstanceWithSpring - start successful....</pre>
+<p> </p>
+<p>6.  关闭</p>
+<pre name="code">sh bin/stop.sh</pre>
+<p> </p>
+<p>it's over. </p>
+</div>
+
+<h1>ClientExample</h1>
+
+<p>依赖配置:(目前暂未正式发布到mvn仓库,所以需要各位下载canal源码后手工执行下mvn clean install -Dmaven.test.skip)</p>
+
+<pre name="code">&lt;dependency&gt;
+    &lt;groupId&gt;com.alibaba.otter&lt;/groupId&gt;
+    &lt;artifactId&gt;canal.client&lt;/artifactId&gt;
+    &lt;version&gt;1.0.0&lt;/version&gt;
+&lt;/dependency&gt;</pre>
+
+<p> </p>
+
+<p>1. 创建mvn标准工程:</p>
+
+<pre name="code">mvn archetype:create -DgroupId=com.alibaba.otter -DartifactId=canal.sample</pre>
+
+<p> </p>
+
+<p>2.  修改pom.xml,添加依赖</p>
+
+<p> </p>
+
+<p>3.  ClientSample代码</p>
+
+<pre name="code">package com.alibaba.otter.canal.sample;
+
+import java.net.InetSocketAddress;
+import java.util.List;
+
+import com.alibaba.otter.canal.common.utils.AddressUtils;
+import com.alibaba.otter.canal.protocol.Message;
+import com.alibaba.otter.canal.protocol.CanalEntry.Column;
+import com.alibaba.otter.canal.protocol.CanalEntry.Entry;
+import com.alibaba.otter.canal.protocol.CanalEntry.EntryType;
+import com.alibaba.otter.canal.protocol.CanalEntry.EventType;
+import com.alibaba.otter.canal.protocol.CanalEntry.RowChange;
+import com.alibaba.otter.canal.protocol.CanalEntry.RowData;
+
+public class SimpleCanalClientExample {
+
+    public static void main(String args[]) {
+        // 创建链接
+        CanalConnector connector = CanalConnectors.newSingleConnector(new InetSocketAddress(AddressUtils.getHostIp(),
+                                                                                            11111), "example", "", "");
+        int batchSize = 1000;
+        int emptyCount = 0;
+        try {
+            connector.connect();
+            connector.subscribe(".*\\..*");
+            connector.rollback();
+            int totalEmtryCount = 120;
+            while (emptyCount &lt; totalEmtryCount) {
+                Message message = connector.getWithoutAck(batchSize); // 获取指定数量的数据
+                long batchId = message.getId();
+                int size = message.getEntries().size();
+                if (batchId == -1 || size == 0) {
+                    emptyCount++;
+                    System.out.println("empty count : " + emptyCount);
+                    try {
+                        Thread.sleep(1000);
+                    } catch (InterruptedException e) {
+                    }
+                } else {
+                    emptyCount = 0;
+                    // System.out.printf("message[batchId=%s,size=%s] \n", batchId, size);
+                    printEntry(message.getEntries());
+                }
+
+                connector.ack(batchId); // 提交确认
+                // connector.rollback(batchId); // 处理失败, 回滚数据
+            }
+
+            System.out.println("empty too many times, exit");
+        } finally {
+            connector.disconnect();
+        }
+    }
+
+    private static void printEntry(List&lt;Entry&gt; entrys) {
+        for (Entry entry : entrys) {
+            if (entry.getEntryType() == EntryType.TRANSACTIONBEGIN || entry.getEntryType() == EntryType.TRANSACTIONEND) {
+                continue;
+            }
+
+            RowChange rowChage = null;
+            try {
+                rowChage = RowChange.parseFrom(entry.getStoreValue());
+            } catch (Exception e) {
+                throw new RuntimeException("ERROR ## parser of eromanga-event has an error , data:" + entry.toString(),
+                                           e);
+            }
+
+            EventType eventType = rowChage.getEventType();
+            System.out.println(String.format("================&gt; binlog[%s:%s] , name[%s,%s] , eventType : %s",
+                                             entry.getHeader().getLogfileName(), entry.getHeader().getLogfileOffset(),
+                                             entry.getHeader().getSchemaName(), entry.getHeader().getTableName(),
+                                             eventType));
+
+            for (RowData rowData : rowChage.getRowDatasList()) {
+                if (eventType == EventType.DELETE) {
+                    printColumn(rowData.getBeforeColumnsList());
+                } else if (eventType == EventType.INSERT) {
+                    printColumn(rowData.getAfterColumnsList());
+                } else {
+                    System.out.println("-------&gt; before");
+                    printColumn(rowData.getBeforeColumnsList());
+                    System.out.println("-------&gt; after");
+                    printColumn(rowData.getAfterColumnsList());
+                }
+            }
+        }
+    }
+
+    private static void printColumn(List&lt;Column&gt; columns) {
+        for (Column column : columns) {
+            System.out.println(column.getName() + " : " + column.getValue() + "    update=" + column.getUpdated());
+        }
+    }
+}</pre>
+
+<p> </p>
+
+<p>4. 运行Client</p>
+
+<p>首先启动Canal Server,可参加QuickStart : <a href="/blogs/1796070">http://agapple.iteye.com/blogs/1796070</a></p>
+
+<p>启动Canal Client后,可以从控制台从看到类似消息:</p>
+
+<pre name="code">empty count : 1
+empty count : 2
+empty count : 3
+empty count : 4</pre>
+
+<p> 此时代表当前数据库无变更数据</p>
+
+<p> </p>
+
+<p>5.  触发数据库变更</p>
+
+<pre name="code">mysql&gt; use test;
+Database changed
+mysql&gt; CREATE TABLE `xdual` (
+    -&gt;   `ID` int(11) NOT NULL AUTO_INCREMENT,
+    -&gt;   `X` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    -&gt;   PRIMARY KEY (`ID`)
+    -&gt; ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 ;
+Query OK, 0 rows affected (0.06 sec)
+
+mysql&gt; insert into xdual(id,x) values(null,now());Query OK, 1 row affected (0.06 sec)</pre>
+
+<p> </p>
+
+<p>可以从控制台中看到:</p>
+
+<pre name="code">empty count : 1
+empty count : 2
+empty count : 3
+empty count : 4
+================&gt; binlog[mysql-bin.001946:313661577] , name[test,xdual] , eventType : INSERT
+ID : 4    update=true
+X : 2013-02-05 23:29:46    update=true</pre>
+
+<p> </p>
+
+<h2>最后:</h2>
+
+<p>  整个代码在附件中可以下载,如有问题可及时联系。 </p>
+
+<p></p>
+</div>
+
+<p></p><div>
+<a href="http://dl.iteye.com/topics/download/7a893f19-bafb-313a-8a7a-e371a4265ad9">canal.sample.tar.gz</a> (2.2 KB)
+  </div>
+      </section>
+    </div>
+
+              <script type="text/javascript">
+            var gaJsHost = (("https:" == document.location.protocol) ? "https://ssl." : "http://www.");
+            document.write(unescape("%3Cscript src='" + gaJsHost + "google-analytics.com/ga.js' type='text/javascript'%3E%3C/script%3E"));
+          </script>
+          <script type="text/javascript">
+            try {
+              var pageTracker = _gat._getTracker("UA-10379866-5");
+            pageTracker._trackPageview();
+            } catch(err) {}
+          </script>
+
+  </body>
+</html>

+ 1 - 0
javascripts/main.js

@@ -0,0 +1 @@
+console.log('This would be the main JS file.');

File diff suppressed because it is too large
+ 0 - 0
params.json


+ 68 - 0
stylesheets/pygment_trac.css

@@ -0,0 +1,68 @@
+.highlight .c { color: #999988; font-style: italic } /* Comment */
+.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
+.highlight .k { font-weight: bold } /* Keyword */
+.highlight .o { font-weight: bold } /* Operator */
+.highlight .cm { color: #999988; font-style: italic } /* Comment.Multiline */
+.highlight .cp { color: #999999; font-weight: bold } /* Comment.Preproc */
+.highlight .c1 { color: #999988; font-style: italic } /* Comment.Single */
+.highlight .cs { color: #999999; font-weight: bold; font-style: italic } /* Comment.Special */
+.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
+.highlight .gd .x { color: #000000; background-color: #ffaaaa } /* Generic.Deleted.Specific */
+.highlight .ge { font-style: italic } /* Generic.Emph */
+.highlight .gr { color: #aa0000 } /* Generic.Error */
+.highlight .gh { color: #999999 } /* Generic.Heading */
+.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
+.highlight .gi .x { color: #000000; background-color: #aaffaa } /* Generic.Inserted.Specific */
+.highlight .go { color: #888888 } /* Generic.Output */
+.highlight .gp { color: #555555 } /* Generic.Prompt */
+.highlight .gs { font-weight: bold } /* Generic.Strong */
+.highlight .gu { color: #800080; font-weight: bold; } /* Generic.Subheading */
+.highlight .gt { color: #aa0000 } /* Generic.Traceback */
+.highlight .kc { font-weight: bold } /* Keyword.Constant */
+.highlight .kd { font-weight: bold } /* Keyword.Declaration */
+.highlight .kn { font-weight: bold } /* Keyword.Namespace */
+.highlight .kp { font-weight: bold } /* Keyword.Pseudo */
+.highlight .kr { font-weight: bold } /* Keyword.Reserved */
+.highlight .kt { color: #445588; font-weight: bold } /* Keyword.Type */
+.highlight .m { color: #009999 } /* Literal.Number */
+.highlight .s { color: #d14 } /* Literal.String */
+.highlight .na { color: #008080 } /* Name.Attribute */
+.highlight .nb { color: #0086B3 } /* Name.Builtin */
+.highlight .nc { color: #445588; font-weight: bold } /* Name.Class */
+.highlight .no { color: #008080 } /* Name.Constant */
+.highlight .ni { color: #800080 } /* Name.Entity */
+.highlight .ne { color: #990000; font-weight: bold } /* Name.Exception */
+.highlight .nf { color: #990000; font-weight: bold } /* Name.Function */
+.highlight .nn { color: #555555 } /* Name.Namespace */
+.highlight .nt { color: #CBDFFF } /* Name.Tag */
+.highlight .nv { color: #008080 } /* Name.Variable */
+.highlight .ow { font-weight: bold } /* Operator.Word */
+.highlight .w { color: #bbbbbb } /* Text.Whitespace */
+.highlight .mf { color: #009999 } /* Literal.Number.Float */
+.highlight .mh { color: #009999 } /* Literal.Number.Hex */
+.highlight .mi { color: #009999 } /* Literal.Number.Integer */
+.highlight .mo { color: #009999 } /* Literal.Number.Oct */
+.highlight .sb { color: #d14 } /* Literal.String.Backtick */
+.highlight .sc { color: #d14 } /* Literal.String.Char */
+.highlight .sd { color: #d14 } /* Literal.String.Doc */
+.highlight .s2 { color: #d14 } /* Literal.String.Double */
+.highlight .se { color: #d14 } /* Literal.String.Escape */
+.highlight .sh { color: #d14 } /* Literal.String.Heredoc */
+.highlight .si { color: #d14 } /* Literal.String.Interpol */
+.highlight .sx { color: #d14 } /* Literal.String.Other */
+.highlight .sr { color: #009926 } /* Literal.String.Regex */
+.highlight .s1 { color: #d14 } /* Literal.String.Single */
+.highlight .ss { color: #990073 } /* Literal.String.Symbol */
+.highlight .bp { color: #999999 } /* Name.Builtin.Pseudo */
+.highlight .vc { color: #008080 } /* Name.Variable.Class */
+.highlight .vg { color: #008080 } /* Name.Variable.Global */
+.highlight .vi { color: #008080 } /* Name.Variable.Instance */
+.highlight .il { color: #009999 } /* Literal.Number.Integer.Long */
+
+.type-csharp .highlight .k { color: #0000FF }
+.type-csharp .highlight .kt { color: #0000FF }
+.type-csharp .highlight .nf { color: #000000; font-weight: normal }
+.type-csharp .highlight .nc { color: #2B91AF }
+.type-csharp .highlight .nn { color: #000000 }
+.type-csharp .highlight .s { color: #A31515 }
+.type-csharp .highlight .sc { color: #A31515 }

+ 247 - 0
stylesheets/stylesheet.css

@@ -0,0 +1,247 @@
+body {
+  margin: 0;
+  padding: 0;
+  background: #151515 url("../images/bkg.png") 0 0;
+  color: #eaeaea;
+  font: 16px;
+  line-height: 1.5;
+  font-family: Monaco, "Bitstream Vera Sans Mono", "Lucida Console", Terminal, monospace;
+}
+
+/* General & 'Reset' Stuff */
+
+.container {
+  width: 90%;
+  max-width: 600px;
+  margin: 0 auto;
+}
+
+section {
+  display: block;
+  margin: 0 0 20px 0;
+}
+
+h1, h2, h3, h4, h5, h6 {
+  margin: 0 0 20px;
+}
+
+li {
+  line-height: 1.4 ;
+}
+
+/* Header, <header>
+   header   - container
+   h1       - project name
+   h2       - project description
+*/
+
+header {
+  background: rgba(0, 0, 0, 0.1);
+  width: 100%;
+  border-bottom: 1px dashed #b5e853;
+  padding: 20px 0;
+  margin: 0 0 40px 0;
+}
+
+header h1 {
+  font-size: 30px;
+  line-height: 1.5;
+  margin: 0 0 0 -40px;
+  font-weight: bold;
+  font-family: Monaco, "Bitstream Vera Sans Mono", "Lucida Console", Terminal, monospace;
+  color: #b5e853;
+  text-shadow: 0 1px 1px rgba(0, 0, 0, 0.1),
+               0 0 5px rgba(181, 232, 83, 0.1),
+               0 0 10px rgba(181, 232, 83, 0.1);
+  letter-spacing: -1px;
+  -webkit-font-smoothing: antialiased;
+}
+
+header h1:before {
+  content: "./ ";
+  font-size: 24px;
+}
+
+header h2 {
+  font-size: 18px;
+  font-weight: 300;
+  color: #666;
+}
+
+#downloads .btn {
+  display: inline-block;
+  text-align: center;
+  margin: 0;
+}
+
+/* Main Content
+*/
+
+#main_content {
+  width: 100%;
+  -webkit-font-smoothing: antialiased;
+}
+section img {
+  max-width: 100%
+}
+
+h1, h2, h3, h4, h5, h6 {
+  font-weight: normal;
+  font-family: Monaco, "Bitstream Vera Sans Mono", "Lucida Console", Terminal, monospace;
+  color: #b5e853;
+  letter-spacing: -0.03em;
+  text-shadow: 0 1px 1px rgba(0, 0, 0, 0.1),
+               0 0 5px rgba(181, 232, 83, 0.1),
+               0 0 10px rgba(181, 232, 83, 0.1);
+}
+
+#main_content h1 {
+  font-size: 30px;
+}
+
+#main_content h2 {
+  font-size: 24px;
+}
+
+#main_content h3 {
+  font-size: 18px;
+}
+
+#main_content h4 {
+  font-size: 14px;
+}
+
+#main_content h5 {
+  font-size: 12px;
+  text-transform: uppercase;
+  margin: 0 0 5px 0;
+}
+
+#main_content h6 {
+  font-size: 12px;
+  text-transform: uppercase;
+  color: #999;
+  margin: 0 0 5px 0;
+}
+
+dt {
+  font-style: italic;
+  font-weight: bold;
+}
+
+ul li {
+  list-style: none;
+}
+
+ul li:before {
+  content: ">>";
+  font-family: Monaco, "Bitstream Vera Sans Mono", "Lucida Console", Terminal, monospace;
+  font-size: 13px;
+  color: #b5e853;
+  margin-left: -37px;
+  margin-right: 21px;
+  line-height: 16px;
+}
+
+blockquote {
+  color: #aaa;
+  padding-left: 10px;
+  border-left: 1px dotted #666;
+}
+
+pre {
+  background: rgba(0, 0, 0, 0.9);
+  border: 1px solid rgba(255, 255, 255, 0.15);
+  padding: 10px;
+  font-size: 14px;
+  color: #b5e853;
+  border-radius: 2px;
+  -moz-border-radius: 2px;
+  -webkit-border-radius: 2px;
+  text-wrap: normal;
+  overflow: auto;
+  overflow-y: hidden;
+}
+
+table {
+  width: 100%;
+  margin: 0 0 20px 0;
+}
+
+th {
+  text-align: left;
+  border-bottom: 1px dashed #b5e853;
+  padding: 5px 10px;
+}
+
+td {
+  padding: 5px 10px;
+}
+
+hr {
+  height: 0;
+  border: 0;
+  border-bottom: 1px dashed #b5e853;
+  color: #b5e853;
+}
+
+/* Buttons
+*/
+
+.btn {
+  display: inline-block;
+  background: -webkit-linear-gradient(top, rgba(40, 40, 40, 0.3), rgba(35, 35, 35, 0.3) 50%, rgba(10, 10, 10, 0.3) 50%, rgba(0, 0, 0, 0.3));
+  padding: 8px 18px;
+  border-radius: 50px;
+  border: 2px solid rgba(0, 0, 0, 0.7);
+  border-bottom: 2px solid rgba(0, 0, 0, 0.7);
+  border-top: 2px solid rgba(0, 0, 0, 1);
+  color: rgba(255, 255, 255, 0.8);
+  font-family: Helvetica, Arial, sans-serif;
+  font-weight: bold;
+  font-size: 13px;
+  text-decoration: none;
+  text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.75);
+  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05);
+}
+
+.btn:hover {
+  background: -webkit-linear-gradient(top, rgba(40, 40, 40, 0.6), rgba(35, 35, 35, 0.6) 50%, rgba(10, 10, 10, 0.8) 50%, rgba(0, 0, 0, 0.8));
+}
+
+.btn .icon {
+  display: inline-block;
+  width: 16px;
+  height: 16px;
+  margin: 1px 8px 0 0;
+  float: left;
+}
+
+.btn-github .icon {
+  opacity: 0.6;
+  background: url("../images/blacktocat.png") 0 0 no-repeat;
+}
+
+/* Links
+   a, a:hover, a:visited
+*/
+
+a {
+  color: #63c0f5;
+  text-shadow: 0 0 5px rgba(104, 182, 255, 0.5);
+}
+
+/* Clearfix */
+
+.cf:before, .cf:after {
+  content:"";
+  display:table;
+}
+
+.cf:after {
+  clear:both;
+}
+
+.cf {
+  zoom:1;
+}

Some files were not shown because too many files changed in this diff