Main.hx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512
  1. package tools.haxelib;
  2. class SiteProxy extends haxe.remoting.Proxy<tools.haxelib.SiteApi> {
  3. }
  4. class Progress extends neko.io.Output {
  5. var o : neko.io.Output;
  6. var cur : Int;
  7. var max : Int;
  8. var start : Float;
  9. public function new(o) {
  10. this.o = o;
  11. cur = 0;
  12. start = haxe.Timer.stamp();
  13. }
  14. function bytes(n) {
  15. cur += n;
  16. if( max == null )
  17. neko.Lib.print(cur+" bytes\r");
  18. else
  19. neko.Lib.print(cur+"/"+max+" ("+Std.int((cur*100.0)/max)+"%)\r");
  20. }
  21. public override function writeChar(c) {
  22. o.writeChar(c);
  23. bytes(1);
  24. }
  25. public override function writeBytes(s,p,l) {
  26. var r = o.writeBytes(s,p,l);
  27. bytes(r);
  28. return r;
  29. }
  30. public override function close() {
  31. super.close();
  32. o.close();
  33. var time = haxe.Timer.stamp() - start;
  34. var speed = (cur / time) / 1024;
  35. time = Std.int(time * 10) / 10;
  36. speed = Std.int(speed * 10) / 10;
  37. neko.Lib.print("Download complete : "+cur+" bytes in "+time+"s ("+speed+"KB/s)\n");
  38. }
  39. public override function prepare(m) {
  40. max = m;
  41. }
  42. }
  43. class Main {
  44. static var VERSION = 100;
  45. static var REPNAME = "lib";
  46. static var SERVER = {
  47. host : "localhost",
  48. port : 2000,
  49. dir : "",
  50. url : "index.n"
  51. };
  52. var argcur : Int;
  53. var args : Array<String>;
  54. var commands : List<{ name : String, doc : String, f : Void -> Void }>;
  55. var siteUrl : String;
  56. var site : SiteProxy;
  57. function new() {
  58. args = neko.Sys.args();
  59. commands = new List();
  60. addCommand("install",install,"install a given project");
  61. addCommand("list",list,"list all installed projects");
  62. addCommand("update",update,"update all installed projects");
  63. addCommand("remove",remove,"remove a given project/version");
  64. addCommand("search",search,"list projects matching a word");
  65. addCommand("infos",infos,"list informations on a given project");
  66. addCommand("user",user,"list informations on a given user");
  67. addCommand("submit",submit,"submit or update a project package");
  68. addCommand("setup",setup,"set the haxelib repository path");
  69. addCommand("config",config,"print the repository path");
  70. siteUrl = "http://"+SERVER.host+":"+SERVER.port+"/"+SERVER.dir;
  71. site = new SiteProxy(haxe.remoting.Connection.urlConnect(siteUrl+SERVER.url).api);
  72. }
  73. function param( name, ?passwd ) {
  74. if( args.length > argcur )
  75. return args[argcur++];
  76. neko.Lib.print(name+" : ");
  77. if( passwd ) {
  78. var s = new StringBuf();
  79. var c;
  80. while( (c = neko.io.File.getChar(false)) != 13 )
  81. s.addChar(c);
  82. print("");
  83. return s.toString();
  84. }
  85. return neko.io.File.stdin().readLine();
  86. }
  87. function paramOpt() {
  88. if( args.length > argcur )
  89. return args[argcur++];
  90. return null;
  91. }
  92. function addCommand( name, f, doc ) {
  93. commands.add({ name : name, doc : doc, f : f });
  94. }
  95. function usage() {
  96. var vmin = Std.string(VERSION % 100);
  97. var ver = Std.int(VERSION/100) + "." + if( vmin.length == 0 ) "0"+vmin else vmin;
  98. print("Haxe Library Manager "+ver+" - (c)2006 Motion-Twin");
  99. print(" Usage : haxlib [command] [options]");
  100. print(" Commands :");
  101. for( c in commands )
  102. print(" "+c.name+" : "+c.doc);
  103. neko.Sys.exit(1);
  104. }
  105. function run() {
  106. var debug = false;
  107. argcur = 0;
  108. if( args[argcur] == "-debug" ) {
  109. argcur++;
  110. debug = true;
  111. }
  112. var cmd = args[argcur++];
  113. if( cmd == null )
  114. usage();
  115. for( c in commands )
  116. if( c.name == cmd ) {
  117. try {
  118. c.f();
  119. } catch( e : Dynamic ) {
  120. if( debug )
  121. neko.Lib.rethrow(e);
  122. print(Std.string(e));
  123. neko.Sys.exit(1);
  124. }
  125. return;
  126. }
  127. print("Unknown command "+cmd);
  128. usage();
  129. }
  130. // ---- COMMANDS --------------------
  131. function search() {
  132. var word = param("Search word");
  133. var l = site.search(word);
  134. for( s in l )
  135. print(s.name);
  136. print(l.length+" projects found");
  137. }
  138. function infos() {
  139. var prj = param("Project name");
  140. var inf = site.infos(prj);
  141. print("Name: "+inf.name);
  142. print("Desc: "+inf.desc);
  143. print("Website: "+inf.website);
  144. print("Owner: "+inf.owner);
  145. print("Version: "+inf.curversion);
  146. print("Releases: ");
  147. if( inf.versions.length == 0 )
  148. print(" (no version released yet)");
  149. for( v in inf.versions )
  150. print(" "+v.date+" "+v.name+" : "+v.comments);
  151. }
  152. function user() {
  153. var uname = param("User name");
  154. var inf = site.user(uname);
  155. print("Id: "+inf.name);
  156. print("Name: "+inf.fullname);
  157. print("Mail: "+inf.email);
  158. print("Projects: ");
  159. if( inf.projects.length == 0 )
  160. print(" (no projects)");
  161. for( p in inf.projects )
  162. print(" "+p);
  163. }
  164. function register(name) {
  165. print("This is your first submission as '"+name+"'");
  166. print("Please enter the following informations for registration");
  167. var email = param("Email");
  168. var fullname = param("Fullname");
  169. var pass = param("Password",true);
  170. var pass2 = param("Confirm",true);
  171. if( pass != pass2 )
  172. throw "Password does not match";
  173. pass = haxe.Md5.encode(pass);
  174. site.register(name,pass,email,fullname);
  175. return pass;
  176. }
  177. function submit() {
  178. var file = param("Package");
  179. var data = neko.io.File.getContent(file);
  180. var zip = neko.zip.File.read(new neko.io.StringInput(data));
  181. var infos = Datas.readInfos(zip);
  182. var password;
  183. site.checkOwner(infos.project,infos.owner);
  184. if( site.isNewUser(infos.owner) )
  185. password = register(infos.owner);
  186. else {
  187. password = haxe.Md5.encode(param("Password",true));
  188. if( !site.checkPassword(infos.owner,password) )
  189. throw "Invalid password for "+infos.owner;
  190. }
  191. // check dependencies validity
  192. for( d in infos.dependencies ) {
  193. var infos = site.infos(d.project);
  194. if( d.version == "" )
  195. continue;
  196. var found = false;
  197. for( v in infos.versions )
  198. if( v.name == d.version ) {
  199. found = true;
  200. break;
  201. }
  202. if( !found )
  203. throw "Project "+d.project+" does not have version "+d.version;
  204. }
  205. // query a submit id that will identify the file
  206. var id = site.getSubmitId();
  207. // directly send the file data over Http
  208. // we can't use haxe.Http because we want *sent* data progress
  209. var s = new neko.io.Socket();
  210. s.connect(neko.io.Socket.resolve(SERVER.host),SERVER.port);
  211. s.write("POST /"+SERVER.url+"?submit="+id);
  212. s.write(" HTTP/1.1\r\nHost: "+SERVER.host+"\r\n");
  213. s.write("Content-Type: application/octet-stream\r\n");
  214. s.write("Content-Length: "+data.length+"\r\n");
  215. s.write("\r\n");
  216. var pos = 0;
  217. var bufsize = 1;
  218. print("Sending data.... ");
  219. while( pos < data.length ) {
  220. s.write(data.substr(pos,bufsize));
  221. pos += bufsize;
  222. neko.Lib.print( Std.int((pos * 100.0) / data.length) + "%\r" );
  223. }
  224. s.shutdown(false,true);
  225. s.input.readAll();
  226. s.close();
  227. // ask the server to register the sent file
  228. var msg = site.processSubmit(id,password);
  229. print(msg);
  230. }
  231. function install() {
  232. var prj = param("Project name");
  233. var inf = site.infos(prj);
  234. if( inf.curversion == null )
  235. throw "This project has not yet released a version";
  236. var reqversion = paramOpt();
  237. var version = if( reqversion != null ) reqversion else inf.curversion;
  238. var found = false;
  239. for( v in inf.versions )
  240. if( v.name == version ) {
  241. found = true;
  242. break;
  243. }
  244. if( !found )
  245. throw "No such version "+version;
  246. doInstall(inf.name,version,version == inf.curversion);
  247. }
  248. function doInstall( project, version, setcurrent ) {
  249. var rep = getRepository();
  250. // create/delete directories first
  251. var pdir = rep+Datas.safe(project);
  252. safeDir(pdir);
  253. pdir += "/";
  254. var target = pdir+Datas.safe(version);
  255. if( !safeDir(target) ) {
  256. print("You already have "+project+" version "+version+" installed");
  257. return;
  258. }
  259. target += "/";
  260. // download to temporary file
  261. var filename = Datas.fileName(project,version);
  262. var filepath = rep+filename;
  263. var out = neko.io.File.write(filepath,true);
  264. var progress = new Progress(out);
  265. var h = new haxe.Http(siteUrl+Datas.REPOSITORY+"/"+filename);
  266. h.onError = function(e) {
  267. progress.close();
  268. neko.FileSystem.deleteFile(filepath);
  269. throw e;
  270. };
  271. print("Downloading "+filename+"...");
  272. h.asyncRequest(false,progress);
  273. // read zip content
  274. var f = neko.io.File.read(filepath,true);
  275. var zip = neko.zip.File.read(f);
  276. f.close();
  277. // locate haxelib.xml base path
  278. var basepath = null;
  279. for( f in zip ) {
  280. if( StringTools.endsWith(f.fileName,Datas.XML) ) {
  281. basepath = f.fileName.substr(0,f.fileName.length - Datas.XML.length);
  282. break;
  283. }
  284. }
  285. if( basepath == null )
  286. throw "No "+Datas.XML+" found";
  287. // unzip content
  288. for( zipfile in zip ) {
  289. var n = zipfile.fileName;
  290. if( StringTools.startsWith(n,basepath) ) {
  291. // remove basepath
  292. n = n.substr(basepath.length,n.length-basepath.length);
  293. if( n.charAt(0) == "/" || n.charAt(0) == "\\" || n.split("..").length > 1 )
  294. throw "Invalid filename : "+n;
  295. var dirs = ~/[\/\\]/.split(n);
  296. var path = "";
  297. var file = dirs.pop();
  298. for( d in dirs ) {
  299. path += d;
  300. safeDir(target+path);
  301. path += "/";
  302. }
  303. if( file == "" ) {
  304. if( path != "" ) print(" Created "+path);
  305. continue; // was just a directory
  306. }
  307. path += file;
  308. print(" Install "+path);
  309. var data = neko.zip.File.unzip(zipfile);
  310. var f = neko.io.File.write(target+path,true);
  311. f.write(data);
  312. f.close();
  313. }
  314. }
  315. // set current version
  316. if( setcurrent || !neko.FileSystem.exists(pdir+".current") ) {
  317. var f = neko.io.File.write(pdir+".current",true);
  318. f.write(version);
  319. f.close();
  320. print(" Current version is now "+version);
  321. }
  322. // end
  323. neko.FileSystem.deleteFile(filepath);
  324. print("Done");
  325. // process dependencies
  326. var infos = Datas.readInfos(zip);
  327. for( d in infos.dependencies ) {
  328. print("Installing dependency "+d.project+" "+d.version);
  329. if( d.version == "" )
  330. d.version = site.infos(d.project).curversion;
  331. doInstall(d.project,d.version,false);
  332. }
  333. }
  334. function safeDir( dir ) {
  335. if( neko.FileSystem.exists(dir) ) {
  336. if( !neko.FileSystem.isDirectory(dir) )
  337. throw ("A file is preventing "+dir+" to be created");
  338. return false;
  339. }
  340. neko.FileSystem.createDirectory(dir);
  341. return true;
  342. }
  343. function getRepository( ?setup : Bool ) {
  344. var sys = neko.Sys.systemName();
  345. if( sys == "Windows" ) {
  346. var haxepath = neko.Sys.getEnv("HAXEPATH");
  347. if( haxepath == null )
  348. throw "HAXEPATH environment variable not defined, please run haxesetup.exe first";
  349. var rep = haxepath+REPNAME;
  350. safeDir(rep);
  351. return rep+"\\";
  352. }
  353. var config = neko.Sys.getEnv("HOME")+"/.haxelib";
  354. var rep = try
  355. neko.io.File.getContent(config)
  356. catch( e : Dynamic ) try
  357. neko.io.File.getContent("/etc/.haxelib")
  358. catch( e : Dynamic )
  359. if( setup ) {
  360. if( sys == "Linux" ) "/usr/lib/haxe/"+REPNAME else "/usr/local/lib/haxe/"+REPNAME;
  361. } else
  362. throw "This is the first time you are runing haxelib. Please run haxelib setup first";
  363. if( setup ) {
  364. print("Please enter haxelib repository path with write access");
  365. print("Hit enter for default ("+rep+")");
  366. var line = param("Path");
  367. if( line != "" )
  368. rep = line;
  369. if( !neko.FileSystem.exists(rep) )
  370. neko.FileSystem.createDirectory(rep);
  371. var f = neko.io.File.write(config,true);
  372. f.write(rep);
  373. f.close();
  374. }
  375. return rep+"/";
  376. }
  377. function setup() {
  378. var path = getRepository(true);
  379. print("haxelib repository is now "+path);
  380. }
  381. function config() {
  382. print(getRepository());
  383. }
  384. function list() {
  385. var rep = getRepository();
  386. for( p in neko.FileSystem.readDirectory(rep) ) {
  387. var versions = neko.FileSystem.readDirectory(rep+p);
  388. var current = neko.io.File.getContent(rep+p+"/.current");
  389. versions.remove(".current");
  390. for( i in 0...versions.length ) {
  391. versions[i] = Datas.unsafe(versions[i]);
  392. if( versions[i] == current )
  393. versions[i] = "["+current+"]";
  394. }
  395. print(Datas.unsafe(p) + ": "+versions.join(" "));
  396. }
  397. }
  398. function update() {
  399. var rep = getRepository();
  400. var prompt = true;
  401. var update = false;
  402. for( p in neko.FileSystem.readDirectory(rep) ) {
  403. var current = neko.io.File.getContent(rep+p+"/.current");
  404. var p = Datas.unsafe(p);
  405. print("Checking "+p);
  406. var inf = site.infos(p);
  407. if( inf.curversion != current ) {
  408. if( prompt ) {
  409. var answer;
  410. do {
  411. neko.Lib.print("Update "+p+" to "+inf.curversion+" [y/n/a] ? ");
  412. answer = neko.io.File.stdin().readLine();
  413. } while( answer != "y" && answer != "n" && answer != "a" );
  414. if( answer == "n" )
  415. continue;
  416. if( answer == "a" )
  417. prompt = false;
  418. }
  419. doInstall(p,inf.curversion,true);
  420. update = true;
  421. }
  422. }
  423. if( update )
  424. print("Done");
  425. else
  426. print("All projects are up-to-date");
  427. }
  428. function deleteRec(dir) {
  429. for( p in neko.FileSystem.readDirectory(dir) ) {
  430. var path = dir+"/"+p;
  431. if( neko.FileSystem.isDirectory(path) )
  432. deleteRec(path);
  433. else
  434. neko.FileSystem.deleteFile(path);
  435. }
  436. neko.FileSystem.deleteDirectory(dir);
  437. }
  438. function remove() {
  439. var prj = param("Project");
  440. var version = paramOpt();
  441. var rep = getRepository();
  442. var pdir = rep + Datas.safe(prj);
  443. if( version == null ) {
  444. if( !neko.FileSystem.exists(pdir) )
  445. throw "Project "+prj+" is not installed";
  446. deleteRec(pdir);
  447. print("Project "+prj+" removed");
  448. return;
  449. }
  450. }
  451. // ----------------------------------
  452. static function print(str) {
  453. neko.Lib.print(str+"\n");
  454. }
  455. static function main() {
  456. new Main().run();
  457. }
  458. }