2
0
Nicolas Cannasse 19 жил өмнө
parent
commit
c13b577158

+ 79 - 0
std/tools/haxelib/Datas.hx

@@ -0,0 +1,79 @@
+package tools.haxlib;
+
+typedef UserInfos = {
+	var name : String;
+	var fullname : String;
+	var email : String;
+	var libraries : Array<String>;
+}
+
+typedef VersionInfos = {
+	var name : String;
+	var comments : String;
+}
+
+typedef LibraryInfos = {
+	var name : String;
+	var fullname : String;
+	var desc : String;
+	var url : String;
+	var owner : String;
+	var curversion : String;
+	var versions : Array<VersionInfos>;
+}
+
+typedef XmlInfos = {
+	var lib : String;
+	var url : String;
+	var user : String;
+	var desc : String;
+	var version : String;
+	var versionDesc : String;
+}
+
+class Datas {
+
+
+	public static var XML = "haxlib.xml";
+
+	static function requiredAttribute( x : Xml, name ) {
+		var v = x.get(name);
+		if( v == null )
+			throw "Missing required attribute '"+name+"' in node "+x.nodeName;
+		return v;
+	}
+
+	static function requiredNode( x : Xml, name ) {
+		var v = x.elementsNamed(name).next();
+		if( v == null )
+			throw "Missing required node '"+name+"' in node "+x.nodeName;
+		return v;
+	}
+
+	static function requiredText( x : Xml ) {
+		var v = x.firstChild();
+		if( v == null || (v.nodeType != Xml.PCData && v.nodeType != Xml.CData) )
+			throw "Missing required text in node "+x.nodeName;
+		return v.nodeValue;
+	}
+
+	public static function readInfos( xmldata : String ) : XmlInfos {
+		var x = Xml.parse(xmldata).firstElement();
+		var lib = requiredAttribute(x,"name");
+		var url = requiredAttribute(x,"url");
+		var user = requiredAttribute(requiredNode(x,"user"),"name");
+		var desc = requiredText(requiredNode(x,"description"));
+		var vnode = requiredNode(x,"version");
+		var version = requiredAttribute(vnode,"name");
+		var vdesc = requiredText(vnode);
+		return {
+			lib : lib,
+			url : url,
+			user : user,
+			desc : desc,
+			version : version,
+			versionDesc : vdesc,
+		}
+	}
+
+}

+ 176 - 0
std/tools/haxelib/Main.hx

@@ -0,0 +1,176 @@
+package tools.haxlib;
+
+class SiteProxy extends haxe.remoting.Proxy<tools.haxlib.SiteApi> {
+}
+
+class Main {
+
+	static var VERSION = 100;
+	static var SERVER = {
+		host : "localhost",
+		port : 2000,
+		url : "index.n"
+	};
+
+	var argcur : Int;
+	var args : Array<String>;
+	var commands : List<{ name : String, doc : String, f : Void -> Void }>;
+	var site : SiteProxy;
+
+	function new() {
+		argcur = 1;
+		args = neko.Sys.args();
+		commands = new List();
+		addCommand("search",search,"list libraries matching a word");
+		addCommand("infos",infos,"list informations on a given library");
+		addCommand("user",user,"list informations on a given user");
+		addCommand("submit",submit,"submit or update a library package");
+		site = new SiteProxy(haxe.remoting.Connection.urlConnect("http://"+SERVER.host+":"+SERVER.port+"/"+SERVER.url).api);
+	}
+
+	function param( name ) {
+		if( args.length > argcur )
+			return args[argcur++];
+		neko.Lib.print(name+" : ");
+		return neko.io.File.stdin().readLine();
+	}
+
+	function addCommand( name, f, doc ) {
+		commands.add({ name : name, doc : doc, f : f });
+	}
+
+	function usage() {
+		var vmin = Std.string(VERSION % 100);
+		var ver = Std.int(VERSION/100) + "." + if( vmin.length == 0 ) "0"+vmin else vmin;
+		print("Haxe Library Manager "+ver+" - (c)2006 Motion-Twin");
+		print(" Usage : haxlib [command] [options]");
+		print(" Commands :");
+		for( c in commands )
+			print("  "+c.name+" : "+c.doc);
+		neko.Sys.exit(1);
+	}
+
+	function run() {
+		var cmd = args[0];
+		if( cmd == null )
+			usage();
+		for( c in commands )
+			if( c.name == cmd ) {
+				c.f();
+				return;
+			}
+		print("Unknown command "+cmd);
+		usage();
+	}
+
+	// ---- COMMANDS --------------------
+
+ 	function search() {
+		var word = param("Search word");
+		var l = site.search(word);
+		for( s in l )
+			print(s.fullname+" ("+s.name+")");
+		print(l.length+" libraries found");
+	}
+
+	function infos() {
+		var prj = param("Library name");
+		var inf = site.infos(prj);
+		print("Id: "+inf.name);
+		print("Name: "+inf.fullname);
+		print("Dec: "+inf.desc);
+		print("Website: "+inf.url);
+		print("Owner: "+inf.owner);
+		print("Versions: ");
+		if( inf.versions.length == 0 )
+			print("  (no version released yet)");
+		for( v in inf.versions ) {
+			var cur = if( v.name == inf.curversion ) " *" else "  ";
+			print(cur+v.name+" : "+v.comments);
+		}
+	}
+
+	function user() {
+		var uname = param("User name");
+		var inf = site.user(uname);
+		print("Id: "+inf.name);
+		print("Name: "+inf.fullname);
+		print("Mail: "+inf.email);
+		print("Libraries: ");
+		if( inf.libraries.length == 0 )
+			print("  (no libraries)");
+		for( p in inf.libraries )
+			print("  "+p);
+	}
+
+	function register(name) {
+		print("This is your first submission as '"+name+"'");
+		print("Please enter the following informations for registration");
+		var email = param("Email");
+		var fullname = param("Fullname");
+		var pass = haxe.Md5.encode(param("Password"));
+		param("Enter to Confirm");
+		site.register(name,pass,email,fullname);
+		return pass;
+	}
+
+	function submit() {
+		var file = param("Package");
+		var data = neko.io.File.getContent(file);
+		var zip = neko.zip.File.read(new neko.io.StringInput(data));
+		var xmldata = null;
+		for( f in zip )
+			if( StringTools.endsWith(f.fileName,Datas.XML) ) {
+				xmldata = neko.zip.File.unzip(f);
+				break;
+			}
+		if( xmldata == null )
+			throw Datas.XML+" not found in package";
+		var infos = Datas.readInfos(xmldata);
+		var password;
+		site.checkLibOwner(infos.lib,infos.user);
+		if( site.isNewUser(infos.user) )
+			password = register(infos.user);
+		else {
+			password = haxe.Md5.encode(param("Password"));
+			if( !site.checkPassword(infos.user,password) )
+				throw "Invalid password for "+infos.user;
+		}
+
+		var id = site.getSubmitId();
+
+		// directly send the file data over Http
+		// we can't use haxe.Http because we want *sent* data progress
+		var s = new neko.io.Socket();
+		s.connect(neko.io.Socket.resolve(SERVER.host),SERVER.port);
+		s.write("POST /"+SERVER.url+"?submit="+id);
+		s.write(" HTTP/1.1\r\nHost: "+SERVER.host+"\r\n");
+		s.write("Content-Type: application/octet-stream\r\n");
+		s.write("Content-Length: "+data.length+"\r\n");
+		s.write("\r\n");
+		var pos = 0;
+		var bufsize = 1;
+		while( pos < data.length ) {
+			s.write(data.substr(pos,bufsize));
+			pos += bufsize;
+			neko.Lib.print( Std.int((pos * 100) / data.length) + "%\r" );
+		}
+		s.shutdown(false,true);
+		s.input.readAll();
+		neko.Lib.print("Done!\n");
+		s.close();
+
+		site.processSubmit(id,password);
+	}
+
+	// ----------------------------------
+
+	static function print(str) {
+		neko.Lib.print(str+"\n");
+	}
+
+	static function main() {
+		new Main().run();
+	}
+
+}

+ 62 - 0
std/tools/haxelib/Site.hx

@@ -0,0 +1,62 @@
+package tools.haxlib;
+import tools.haxlib.SiteDb;
+
+class Site {
+
+	static var db : neko.db.Connection;
+
+	static var CWD = neko.Web.getCwd();
+	static var DB_FILE = CWD+"haxlib.db";
+	public static var TMP_DIR = CWD+"tmp/";
+	public static var REP_DIR = CWD+"files/";
+
+	static function setup() {
+		SiteDb.create(db);
+	}
+
+	static function initDatabase() {
+		db = neko.db.Sqlite.open(DB_FILE);
+		neko.db.Manager.cnx = db;
+		neko.db.Manager.initialize();
+	}
+
+	static function run() {
+		if( !neko.FileSystem.exists(TMP_DIR) )
+			neko.FileSystem.createDirectory(TMP_DIR);
+
+		var server = new haxe.remoting.Server();
+		var log = neko.io.File.write(TMP_DIR+"log.txt",false);
+		var api =
+		server.setPrivatePrefix("db");
+		server.setLogger(log.write);
+		server.addObject("api",new SiteApi(db));
+		var flag = server.handleRequest();
+		log.close();
+		if( flag )
+			return;
+		if( neko.Sys.args()[0] == "setup" ) {
+			setup();
+			neko.Lib.print("Setup done\n");
+			return;
+		}
+		var sid = Std.parseInt(neko.Web.getParams().get("submit"));
+		if( sid != null ) {
+			var file = neko.io.File.write(TMP_DIR+sid,true);
+			var data = neko.Web.getPostData();
+			file.write(data);
+			file.close();
+			neko.Lib.print("File #"+sid+" accepted : "+data.length+" bytes written");
+			return;
+		}
+		neko.Lib.print("I'm haXLib");
+	}
+
+	static function main() {
+		initDatabase();
+		run();
+		db.close();
+		neko.db.Manager.cleanup();
+		return;
+	}
+
+}

+ 106 - 0
std/tools/haxelib/SiteApi.hx

@@ -0,0 +1,106 @@
+package tools.haxlib;
+import tools.haxlib.Datas;
+import tools.haxlib.SiteDb;
+
+class SiteApi {
+
+	var db : neko.db.Connection;
+
+	static var alphanum = ~/^[A-Za-z0-9_-]+$/;
+
+	public function new( db ) {
+		this.db = db;
+	}
+
+	public function search( word : String ) : List<{ name : String, fullname : String }> {
+		return Lib.manager.containing(word);
+	}
+
+	public function infos( prj : String ) : LibraryInfos {
+		var p = Lib.manager.search({ name : prj }).first();
+		if( p == null )
+			throw "No such library : "+prj;
+		var vl = Version.manager.search({ library : p.id });
+		var versions = new Array();
+		for( v in vl )
+			versions.push({ name : v.name, comments : v.comments });
+		return {
+			name : p.name,
+			fullname : p.fullname,
+			curversion : if( p.version == null ) null else p.version.name,
+			desc : p.description,
+			versions : versions,
+			owner : p.owner.name,
+			url : p.website,
+		};
+	}
+
+	public function user( name : String ) : UserInfos {
+		var u = User.manager.search({ name : name }).first();
+		if( u == null )
+			throw "No such user : "+name;
+		var pl = Lib.manager.search({ owner : u.id });
+		var libraries = new Array();
+		for( p in pl )
+			libraries.push(p.name);
+		return {
+			name : u.name,
+			fullname : u.fullname,
+			email : u.email,
+			libraries : libraries,
+		};
+	}
+
+	public function register( name : String, pass : String, mail : String, fullname : String ) : Bool {
+		if( !alphanum.match(name) )
+			throw "Invalid user name, please use alphanumeric characters";
+		var u = new User();
+		u.name = name;
+		u.pass = pass;
+		u.email = mail;
+		u.fullname = fullname;
+		u.insert();
+		return null;
+	}
+
+	public function isNewUser( name : String ) : Bool {
+		return User.manager.search({ name : name }).first() == null;
+	}
+
+	public function checkLibOwner( lib : String, user : String ) : Void {
+		var l = Lib.manager.search({ name : lib }).first();
+		if( l == null )
+			return;
+		if( l.owner.name != user )
+			throw "Owner of "+l.name+" is '"+l.owner.name+"'";
+	}
+
+	public function checkPassword( user : String, pass : String ) : Bool {
+		var u = User.manager.search({ name : user }).first();
+		return u != null && u.pass == pass;
+	}
+
+	public function getSubmitId() : String {
+		return Std.string(Std.random(100000000));
+	}
+
+	public function processSubmit( id : String, pass : String ) : Void {
+		var path = Site.TMP_DIR+Std.parseInt(id);
+		var file = try neko.io.File.read(path,true) catch( e : Dynamic ) throw "Invalid file id #"+id;
+		var zip = try neko.zip.File.read(file) catch( e : Dynamic ) { file.close(); neko.Lib.rethrow(e); };
+		file.close();
+
+		var xmldata = null;
+		for( f in zip )
+			if( StringTools.endsWith(f.fileName,Datas.XML) ) {
+				xmldata = neko.zip.File.unzip(f);
+				break;
+			}
+		if( xmldata == null )
+			throw Datas.XML+" not found in package";
+
+		var infos = Datas.readInfos(xmldata);
+	}
+
+}
+

+ 99 - 0
std/tools/haxelib/SiteDb.hx

@@ -0,0 +1,99 @@
+package tools.haxlib;
+
+class User extends neko.db.Object {
+
+	public static var manager = new neko.db.Manager<User>(User);
+
+	public var id : Int;
+	public var name : String;
+	public var fullname : String;
+	public var email : String;
+	public var pass : String;
+
+}
+
+class Lib extends neko.db.Object {
+
+	static function RELATIONS() {
+		return [
+			{ key : "owner", prop : "owner", manager : User.manager },
+			{ key : "version", prop : "version", manager : Version.manager },
+		];
+	}
+
+	public static var manager = new LibraryManager(Lib);
+
+	public var id : Int;
+	public var name : String;
+	public var fullname : String;
+	public var description : String;
+	public var website : String;
+	public var owner(dynamic,dynamic) : User;
+	public var version(dynamic,dynamic) : Version;
+
+}
+
+class Version extends neko.db.Object {
+
+	static function RELATIONS() {
+		return [{ key : "pid", prop : "library", manager : Lib.manager }];
+	}
+
+	public static var manager = new neko.db.Manager<Version>(Version);
+
+	public var id : Int;
+	public var library(dynamic,dynamic) : Lib;
+	public var name : String;
+	public var comments : String;
+	public var downloads : Int;
+	public var data : String;
+
+}
+
+class LibraryManager extends neko.db.Manager<Lib> {
+
+	public function containing( word ) {
+		word = quote("%"+word+"%");
+		return results("SELECT name, fullname FROM Lib WHERE name LIKE "+word+" OR fullname LIKE "+word+" OR description LIKE "+word);
+	}
+
+}
+
+class SiteDb {
+
+	public static function create( db : neko.db.Connection ) {
+		db.request("DROP TABLE IF EXISTS User");
+		db.request("
+			CREATE TABLE User (
+				id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
+				name VARCHAR(16) NOT NULL UNIQUE,
+				fullname VARCHAR(50) NOT NULL,
+				pass VARCHAR(32) NOT NULL,
+				email VARCHAR(50) NOT NULL
+			)
+		");
+		db.request("DROP TABLE IF EXISTS Lib");
+		db.request("
+			CREATE TABLE Lib (
+				id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
+				owner INTEGER NOT NULL,
+				name VARCHAR(16) NOT NULL UNIQUE,
+				fullname VARCHAR(50) NOT NULL,
+				description TEXT NOT NULL,
+				website VARCHAR(100) NOT NULL,
+				version INT
+			)
+		");
+		db.request("DROP TABLE IF EXISTS Version");
+		db.request("
+			CREATE TABLE Version (
+				id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
+				library INTEGER NOT NULL,
+				downloads INTEGER NOT NULL,
+				name VARCHAR(20) NOT NULL,
+				comments TEXT NOT NULL,
+				data BLOB NOT NULL
+			)
+		");
+	}
+}

+ 8 - 0
std/tools/haxelib/haxelib.hxml

@@ -0,0 +1,8 @@
+-main tools.haxlib.Site
+-neko index.n
+
+--next
+
+-main tools.haxlib.Main
+-neko haxlib.n
+-cmd nekotools boot haxlib.n