|
From: <ma...@us...> - 2011-09-11 20:49:27
|
Revision: 417
http://openautomation.svn.sourceforge.net/openautomation/?rev=417&view=rev
Author: mayerch
Date: 2011-09-11 20:49:21 +0000 (Sun, 11 Sep 2011)
Log Message:
-----------
Initial version of rsslog
Added Paths:
-----------
tools/rsslog/rsslog.php
Added: tools/rsslog/rsslog.php
===================================================================
--- tools/rsslog/rsslog.php (rev 0)
+++ tools/rsslog/rsslog.php 2011-09-11 20:49:21 UTC (rev 417)
@@ -0,0 +1,90 @@
+<?php
+/*****************************************************************************/
+/* rsslog.php - A simple log message reciever and sender via RSS */
+/* */
+/* (c) 2011 by Christian Mayer */
+/* Licenced under the GPLv3 */
+/*****************************************************************************/
+
+// There are diffentent modes of operation
+// 1. Creating a new log line:
+// URL parameter "c": the content of the log
+// URL parameter "t[]": a tag for later filtering. Multiple might be given
+// 2. Recieve the log as RSS:
+// URL parameter "f": The (optional) filter, only log lines with a tag
+// that fit this string are sent
+if( isset($_GET['c']) )
+{
+ // store a new log
+ $store = true;
+ $log_content = $_GET['c'] ? $_GET['c'] : '<no content>';
+ $log_tags = $_GET['t'] ? $_GET['t'] : array();
+} else {
+ // send logs
+ $store = false;
+ $log_filter = $_GET['f'] ? $_GET['f'] : '';
+}
+
+// create database connection
+$db = sqlite_open('rsslog.db', 0666, $error);
+if (!$db) die ($error);
+
+// create table if it doesn't exists
+$q = "SELECT name FROM sqlite_master WHERE type='table' AND name='Logs';";
+$result = sqlite_query( $db, $q, SQLITE_NUM);
+if (!$result) die("Cannot execute query.");
+if( !sqlite_has_more($result) )
+{
+ // no table found - create it
+ $q = "CREATE TABLE Logs(" .
+ " id INTEGER PRIMARY KEY," .
+ " content TEXT NOT NULL," .
+ " t TIMESTAMP" .
+ ");";
+ $ok = sqlite_exec($db, $q, $error);
+
+ if (!$ok)
+ die("Cannot execute query. $error");
+}
+
+if( $store )
+{
+ // store a new log line
+ $q = "INSERT INTO Logs(content, t) VALUES( " .
+ " '" . sqlite_escape_string( $log_content ) . "'," .
+ " datetime('now','localtime')" .
+ ")";
+
+ $ok = sqlite_exec($db, $q, $error);
+
+ if (!$ok)
+ die("Cannot execute query. $error");
+} else {
+ // retrieve data
+ $q = "SELECT * FROM Logs";
+ $result = sqlite_query( $db, $q, SQLITE_ASSOC);
+// phpinfo();
+ echo '<?xml version="1.0"?>';
+ ?>
+<rss version="2.0">
+ <channel>
+ <title>RSS supplied logs</title>
+ <link><?php echo 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['SCRIPT_NAME']; ?></link>
+ <?php
+ // echo '<description>foo</description>';
+ while( sqlite_has_more($result) )
+ {
+ $row = sqlite_fetch_array($result, SQLITE_ASSOC);
+ echo '<item>';
+ echo '<title>' . $row['content'] . '</title>';
+ echo '<pubDate>' . $row['t'] . '</pubDate>';
+ echo '</item>';
+ }
+ ?>
+ </channel>
+</rss>
+ <?php
+}
+
+sqlite_close($db);
+?>
\ No newline at end of file
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <ma...@us...> - 2011-09-19 22:10:53
|
Revision: 421
http://openautomation.svn.sourceforge.net/openautomation/?rev=421&view=rev
Author: mayerch
Date: 2011-09-19 22:10:46 +0000 (Mon, 19 Sep 2011)
Log Message:
-----------
Updated version which can dump its content in HTML as well as delete old content.
Also UTF-8 fixes to work with picky RSS readers.
Modified Paths:
--------------
tools/rsslog/rsslog.php
Modified: tools/rsslog/rsslog.php
===================================================================
--- tools/rsslog/rsslog.php 2011-09-19 15:58:09 UTC (rev 420)
+++ tools/rsslog/rsslog.php 2011-09-19 22:10:46 UTC (rev 421)
@@ -13,72 +13,75 @@
// 2. Recieve the log as RSS:
// URL parameter "f": The (optional) filter, only log lines with a tag
// that fit this string are sent
+// 3. Dump all the content in a HTML page:
+// URL parameter "dump" - no value needed
+// 4. Remove old content:
+// URL parameter "r": the timestamp (seconds since 1970) of the oldest log
+// line to keep
+
+// create database connection
+$db = sqlite_open('rsslog.db', 0666, $error);
+if (!$db) die ($error);
+
+// create table if it doesn't exists
+create( $db );
+
if( isset($_GET['c']) )
{
// store a new log
$store = true;
$log_content = $_GET['c'] ? $_GET['c'] : '<no content>';
+ if( mb_detect_encoding($log_content, 'UTF-8', true) != 'UTF-8' )
+ $log_content = utf8_encode($log_content);
$log_tags = $_GET['t'] ? $_GET['t'] : array();
+
+ insert( $db, $log_content, $log_tags );
+} else if( isset($_GET['dump']) )
+{
+ $result = retrieve( $db, $log_filter );
+ ?>
+<html><head><meta http-equiv="Content-Type" content="text/html;charset=utf-8" /></head><body>
+<table border="1">
+ <?php
+ while( sqlite_has_more($result) )
+ {
+ $row = sqlite_fetch_array($result, SQLITE_ASSOC );
+ echo '<tr>';
+ echo '<td>' . date( DATE_RFC822, $row['t'] ) . '</td>';
+ echo '<td>' . $row['content'] . '</td>';
+ echo "</tr>\n";
+ }
+ ?>
+</table>
+</body></html>
+ <?php
+} else if( isset($_GET['r']) )
+{
+ $timestamp = $_GET['r'] ? $_GET['r'] : '';
+ delete( $db, $timestamp );
} else {
// send logs
- $store = false;
$log_filter = $_GET['f'] ? $_GET['f'] : '';
-}
-// create database connection
-$db = sqlite_open('rsslog.db', 0666, $error);
-if (!$db) die ($error);
-
-// create table if it doesn't exists
-$q = "SELECT name FROM sqlite_master WHERE type='table' AND name='Logs';";
-$result = sqlite_query( $db, $q, SQLITE_NUM);
-if (!$result) die("Cannot execute query.");
-if( !sqlite_has_more($result) )
-{
- // no table found - create it
- $q = "CREATE TABLE Logs(" .
- " id INTEGER PRIMARY KEY," .
- " content TEXT NOT NULL," .
- " t TIMESTAMP" .
- ");";
- $ok = sqlite_exec($db, $q, $error);
-
- if (!$ok)
- die("Cannot execute query. $error");
-}
-
-if( $store )
-{
- // store a new log line
- $q = "INSERT INTO Logs(content, t) VALUES( " .
- " '" . sqlite_escape_string( $log_content ) . "'," .
- " datetime('now','localtime')" .
- ")";
-
- $ok = sqlite_exec($db, $q, $error);
-
- if (!$ok)
- die("Cannot execute query. $error");
-} else {
// retrieve data
- $q = "SELECT * FROM Logs";
- $result = sqlite_query( $db, $q, SQLITE_ASSOC);
-// phpinfo();
+ $result = retrieve( $db, $log_filter );
echo '<?xml version="1.0"?>';
?>
<rss version="2.0">
<channel>
<title>RSS supplied logs</title>
<link><?php echo 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['SCRIPT_NAME']; ?></link>
+ <description>RSS supplied logs</description>
<?php
// echo '<description>foo</description>';
while( sqlite_has_more($result) )
{
- $row = sqlite_fetch_array($result, SQLITE_ASSOC);
+ $row = sqlite_fetch_array($result, SQLITE_ASSOC );
echo '<item>';
echo '<title>' . $row['content'] . '</title>';
- echo '<pubDate>' . $row['t'] . '</pubDate>';
- echo '</item>';
+ echo '<description>' . $row['content'] . '</description>';
+ echo '<pubDate>' . date( DATE_RFC822, $row['t'] ) . '</pubDate>';
+ echo '</item>' . "\n";
}
?>
</channel>
@@ -87,4 +90,58 @@
}
sqlite_close($db);
+
+///////////////////////////////////////////////////////////////////////////////
+// create tables if they don't exist
+function create( $db )
+{
+ $q = "SELECT name FROM sqlite_master WHERE type='table' AND name='Logs';";
+ $result = sqlite_query( $db, $q, SQLITE_NUM );
+ if (!$result) die("Cannot execute query.");
+ if( !sqlite_has_more($result) )
+ {
+ // no table found - create it
+ $q = 'CREATE TABLE Logs(' .
+ ' id INTEGER PRIMARY KEY,' .
+ ' content TEXT NOT NULL,' .
+ ' t TIMESTAMP' .
+ ');';
+ $ok = sqlite_exec($db, $q, $error);
+
+ if (!$ok)
+ die("Cannot execute query. $error");
+ }
+}
+
+// insert a new log line
+function insert( $db, $content, $tags )
+{
+ // store a new log line
+ $q = 'INSERT INTO Logs(content, t) VALUES( ' .
+ " '" . sqlite_escape_string( $content ) . "'," .
+ " datetime('now','localtime')" .
+ ')';
+
+ $ok = sqlite_exec($db, $q, $error);
+
+ if (!$ok)
+ die("Cannot execute query. $error");
+}
+
+// return a handle to all the data
+function retrieve( $db, $filter )
+{
+ $q = "SELECT content, strftime('%s', t, 'localtime') AS t FROM Logs";
+ return sqlite_query( $db, $q, SQLITE_ASSOC );
+}
+
+// delete all log lines older than the timestamp
+function delete( $db, $timestamp )
+{
+ $q = "DELETE from Logs WHERE t < datetime($timestamp, 'unixepoch', 'localtime')";
+ $ok = sqlite_exec($db, $q, $error);
+
+ if (!$ok)
+ die("Cannot execute query. $error");
+}
?>
\ No newline at end of file
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <ma...@us...> - 2011-10-22 19:11:05
|
Revision: 464
http://openautomation.svn.sourceforge.net/openautomation/?rev=464&view=rev
Author: mayerch
Date: 2011-10-22 19:10:59 +0000 (Sat, 22 Oct 2011)
Log Message:
-----------
Fix timezone troubles.
(On a German WireGate the sent back dates were two hours off...)
Modified Paths:
--------------
tools/rsslog/rsslog.php
Modified: tools/rsslog/rsslog.php
===================================================================
--- tools/rsslog/rsslog.php 2011-10-22 16:33:02 UTC (rev 463)
+++ tools/rsslog/rsslog.php 2011-10-22 19:10:59 UTC (rev 464)
@@ -48,6 +48,7 @@
$row = sqlite_fetch_array($result, SQLITE_ASSOC );
echo '<tr>';
echo '<td>' . date( DATE_RFC822, $row['t'] ) . '</td>';
+ echo '<td>' . $row['t'] . '</td>';
echo '<td>' . $row['content'] . '</td>';
echo "</tr>\n";
}
@@ -119,7 +120,8 @@
// store a new log line
$q = 'INSERT INTO Logs(content, t) VALUES( ' .
" '" . sqlite_escape_string( $content ) . "'," .
- " datetime('now','localtime')" .
+// " datetime('now','localtime')" .
+ " datetime('now')" .
')';
$ok = sqlite_exec($db, $q, $error);
@@ -131,14 +133,16 @@
// return a handle to all the data
function retrieve( $db, $filter )
{
- $q = "SELECT content, strftime('%s', t, 'localtime') AS t FROM Logs";
+// $q = "SELECT content, strftime('%s', t, 'localtime') AS t FROM Logs";
+ $q = "SELECT content, strftime('%s', t) AS t FROM Logs";
return sqlite_query( $db, $q, SQLITE_ASSOC );
}
// delete all log lines older than the timestamp
function delete( $db, $timestamp )
{
- $q = "DELETE from Logs WHERE t < datetime($timestamp, 'unixepoch', 'localtime')";
+ //$q = "DELETE from Logs WHERE t < datetime($timestamp, 'unixepoch', 'localtime')";
+ $q = "DELETE from Logs WHERE t < datetime($timestamp, 'unixepoch')";
$ok = sqlite_exec($db, $q, $error);
if (!$ok)
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <ma...@us...> - 2011-10-22 20:16:52
|
Revision: 465
http://openautomation.svn.sourceforge.net/openautomation/?rev=465&view=rev
Author: mayerch
Date: 2011-10-22 20:16:45 +0000 (Sat, 22 Oct 2011)
Log Message:
-----------
Added inital JSON output when the URI parameter "j" is set
Modified Paths:
--------------
tools/rsslog/rsslog.php
Modified: tools/rsslog/rsslog.php
===================================================================
--- tools/rsslog/rsslog.php 2011-10-22 19:10:59 UTC (rev 464)
+++ tools/rsslog/rsslog.php 2011-10-22 20:16:45 UTC (rev 465)
@@ -18,6 +18,8 @@
// 4. Remove old content:
// URL parameter "r": the timestamp (seconds since 1970) of the oldest log
// line to keep
+// 5. Get content as JSON:
+// URL parameter "j"
// create database connection
$db = sqlite_open('rsslog.db', 0666, $error);
@@ -60,6 +62,41 @@
{
$timestamp = $_GET['r'] ? $_GET['r'] : '';
delete( $db, $timestamp );
+} else if( isset($_GET['j']) )
+{
+ ?>
+{
+ "responseData" : {
+ "feed" : {
+ "feedUrl": "<?php echo 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['SCRIPT_NAME']; ?>",
+ "title": "RSS supplied logs",
+ "link": "<?php echo 'http://' . $_SERVER['HTTP_HOST'] . $_SERVER['SCRIPT_NAME']; ?>",
+ "author": "",
+ "description": "RSS supplied logs",
+ "type": "rss20",
+ "entries": [
+<?php
+ $result = retrieve( $db, $log_filter );
+ $first = true;
+ while( sqlite_has_more($result) )
+ {
+ $row = sqlite_fetch_array($result, SQLITE_ASSOC );
+ if( !$first ) echo ",\n";
+ echo '{';
+ echo '"title": "' . $row['content'] . '",';
+ echo '"content": "' . $row['content'] . '",';
+ echo '"publishedDate": "' . date( DATE_RFC822, $row['t'] ) . '"';
+ echo '}';
+ $first = false;
+ }
+?>
+ ]
+ }
+ },
+ "responseDetails" : null,
+ "responseStatus" : 200
+}
+ <?php
} else {
// send logs
$log_filter = $_GET['f'] ? $_GET['f'] : '';
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <ma...@us...> - 2012-01-08 00:30:35
|
Revision: 641
http://openautomation.svn.sourceforge.net/openautomation/?rev=641&view=rev
Author: makki1
Date: 2012-01-08 00:30:29 +0000 (Sun, 08 Jan 2012)
Log Message:
-----------
rsslog: change date-output to ISO-8601 to comply with RDF/RSS-standards being readable by JS - further commits pending
Modified Paths:
--------------
tools/rsslog/rsslog.php
Modified: tools/rsslog/rsslog.php
===================================================================
--- tools/rsslog/rsslog.php 2012-01-07 22:43:15 UTC (rev 640)
+++ tools/rsslog/rsslog.php 2012-01-08 00:30:29 UTC (rev 641)
@@ -49,7 +49,7 @@
{
$row = sqlite_fetch_array($result, SQLITE_ASSOC );
echo '<tr>';
- echo '<td>' . date( DATE_RFC822, $row['t'] ) . '</td>';
+ echo '<td>' . date( DATE_ATOM, $row['t'] ) . '</td>';
echo '<td>' . $row['t'] . '</td>';
echo '<td>' . $row['content'] . '</td>';
echo "</tr>\n";
@@ -85,7 +85,7 @@
echo '{';
echo '"title": "' . $row['content'] . '",';
echo '"content": "' . $row['content'] . '",';
- echo '"publishedDate": "' . date( DATE_RFC822, $row['t'] ) . '"';
+ echo '"publishedDate": "' . date( DATE_ATOM, $row['t'] ) . '"';
echo '}';
$first = false;
}
@@ -118,7 +118,7 @@
echo '<item>';
echo '<title>' . $row['content'] . '</title>';
echo '<description>' . $row['content'] . '</description>';
- echo '<pubDate>' . date( DATE_RFC822, $row['t'] ) . '</pubDate>';
+ echo '<pubDate>' . date( DATE_ATOM, $row['t'] ) . '</pubDate>';
echo '</item>' . "\n";
}
?>
@@ -185,4 +185,4 @@
if (!$ok)
die("Cannot execute query. $error");
}
-?>
\ No newline at end of file
+?>
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <ma...@us...> - 2012-01-08 16:08:30
|
Revision: 645
http://openautomation.svn.sourceforge.net/openautomation/?rev=645&view=rev
Author: makki1
Date: 2012-01-08 16:08:23 +0000 (Sun, 08 Jan 2012)
Log Message:
-----------
rsslog.php: added title,tags,filter,sanity-checks
Modified Paths:
--------------
tools/rsslog/rsslog.php
Modified: tools/rsslog/rsslog.php
===================================================================
--- tools/rsslog/rsslog.php 2012-01-08 15:30:03 UTC (rev 644)
+++ tools/rsslog/rsslog.php 2012-01-08 16:08:23 UTC (rev 645)
@@ -10,6 +10,7 @@
// 1. Creating a new log line:
// URL parameter "c": the content of the log
// URL parameter "t[]": a tag for later filtering. Multiple might be given
+// URL parameter "h": a header(title) for the entry; maybe empty
// 2. Recieve the log as RSS:
// URL parameter "f": The (optional) filter, only log lines with a tag
// that fit this string are sent
@@ -21,8 +22,20 @@
// 5. Get content as JSON:
// URL parameter "j"
+// look where to store DB
+if (is_dir('/etc/wiregate/rss'))
+ $dbfile = '/etc/wiregate/rss/rsslog.db';
+else
+ $dbfile = 'rsslog.db';
+
+//check if the DIRECTORY is writeable by the webserver
+$dbfile_dir = dirname($dbfile);
+if (! is_writable($dbfile_dir))
+ die ("Database $dbfile not writeable! make sure the file AND " .
+ "the directory are writeable by the webserver!");
+
// create database connection
-$db = sqlite_open('rsslog.db', 0666, $error);
+$db = sqlite_open($dbfile, 0666, $error);
if (!$db) die ($error);
// create table if it doesn't exists
@@ -33,11 +46,15 @@
// store a new log
$store = true;
$log_content = $_GET['c'] ? $_GET['c'] : '<no content>';
+ $log_title = $_GET['h'] ? $_GET['h'] : '';
if( mb_detect_encoding($log_content, 'UTF-8', true) != 'UTF-8' )
$log_content = utf8_encode($log_content);
+ if( mb_detect_encoding($log_title, 'UTF-8', true) != 'UTF-8' )
+ $log_title = utf8_encode($log_title);
$log_tags = $_GET['t'] ? $_GET['t'] : array();
-
- insert( $db, $log_content, $log_tags );
+ if(! is_array($log_tags))
+ die("wrong format - use one or more t[]= for tags");
+ insert( $db, $log_content, $log_title, $log_tags );
} else if( isset($_GET['dump']) )
{
$result = retrieve( $db, $log_filter );
@@ -51,7 +68,9 @@
echo '<tr>';
echo '<td>' . date( DATE_ATOM, $row['t'] ) . '</td>';
echo '<td>' . $row['t'] . '</td>';
+ echo '<td>' . $row['title'] . '</td>';
echo '<td>' . $row['content'] . '</td>';
+ echo '<td>' . $row['tags'] . '</td>';
echo "</tr>\n";
}
?>
@@ -83,7 +102,7 @@
$row = sqlite_fetch_array($result, SQLITE_ASSOC );
if( !$first ) echo ",\n";
echo '{';
- echo '"title": "' . $row['content'] . '",';
+ echo '"title": "' . $row['title'] . '",';
echo '"content": "' . $row['content'] . '",';
echo '"publishedDate": "' . date( DATE_ATOM, $row['t'] ) . '"';
echo '}';
@@ -115,8 +134,10 @@
while( sqlite_has_more($result) )
{
$row = sqlite_fetch_array($result, SQLITE_ASSOC );
+ if ($row['tags'])
+ $tags = ' [' . $row['tags'] . ']';
echo '<item>';
- echo '<title>' . $row['content'] . '</title>';
+ echo '<title>' . $row['title'] . $tags . '</title>';
echo '<description>' . $row['content'] . '</description>';
echo '<pubDate>' . date( DATE_ATOM, $row['t'] ) . '</pubDate>';
echo '</item>' . "\n";
@@ -135,43 +156,47 @@
{
$q = "SELECT name FROM sqlite_master WHERE type='table' AND name='Logs';";
$result = sqlite_query( $db, $q, SQLITE_NUM );
- if (!$result) die("Cannot execute query.");
+ if (!$result) die("Cannot execute query. $q");
if( !sqlite_has_more($result) )
{
// no table found - create it
$q = 'CREATE TABLE Logs(' .
- ' id INTEGER PRIMARY KEY,' .
+ ' id INTEGER PRIMARY KEY,' .
+ ' title TEXT,' .
' content TEXT NOT NULL,' .
+ ' tags TEXT,' .
' t TIMESTAMP' .
');';
$ok = sqlite_exec($db, $q, $error);
if (!$ok)
- die("Cannot execute query. $error");
+ die("Cannot execute query $q. $error");
}
}
// insert a new log line
-function insert( $db, $content, $tags )
+function insert( $db, $content, $title, $tags )
{
// store a new log line
- $q = 'INSERT INTO Logs(content, t) VALUES( ' .
+ $q = 'INSERT INTO Logs(content, title, tags, t) VALUES( ' .
" '" . sqlite_escape_string( $content ) . "'," .
-// " datetime('now','localtime')" .
+ " '" . sqlite_escape_string( $title ) . "'," .
+ " '" . sqlite_escape_string( implode(",",$tags) ) . "'," .
" datetime('now')" .
')';
$ok = sqlite_exec($db, $q, $error);
if (!$ok)
- die("Cannot execute query. $error");
+ die("Cannot execute query. $error (Content: $content Tags: $tags");
}
// return a handle to all the data
function retrieve( $db, $filter )
{
// $q = "SELECT content, strftime('%s', t, 'localtime') AS t FROM Logs";
- $q = "SELECT content, strftime('%s', t) AS t FROM Logs";
+ $q = "SELECT title, content, tags, strftime('%s', t) AS t FROM Logs " .
+ "WHERE tags LIKE '%" . sqlite_escape_string($filter) . "%'";
return sqlite_query( $db, $q, SQLITE_ASSOC );
}
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|
|
From: <ma...@us...> - 2012-01-15 20:46:53
|
Revision: 657
http://openautomation.svn.sourceforge.net/openautomation/?rev=657&view=rev
Author: makki1
Date: 2012-01-15 20:46:47 +0000 (Sun, 15 Jan 2012)
Log Message:
-----------
rsslog: missing commit of sort-order
Modified Paths:
--------------
tools/rsslog/rsslog.php
Modified: tools/rsslog/rsslog.php
===================================================================
--- tools/rsslog/rsslog.php 2012-01-15 20:13:17 UTC (rev 656)
+++ tools/rsslog/rsslog.php 2012-01-15 20:46:47 UTC (rev 657)
@@ -196,7 +196,8 @@
{
// $q = "SELECT content, strftime('%s', t, 'localtime') AS t FROM Logs";
$q = "SELECT title, content, tags, strftime('%s', t) AS t FROM Logs " .
- "WHERE tags LIKE '%" . sqlite_escape_string($filter) . "%'";
+ "WHERE tags LIKE '%" . sqlite_escape_string($filter) . "%' " .
+ "ORDER by t DESC";
return sqlite_query( $db, $q, SQLITE_ASSOC );
}
This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site.
|