1717namespace iTXTech \SimpleFramework \Console ;
1818
1919use iTXTech \SimpleFramework \Thread ;
20- use iTXTech \SimpleFramework \Util \Util ;
2120
2221class ConsoleReader extends Thread{
22+ public const TYPE_STREAM = 1 ;
23+ public const TYPE_PIPED = 2 ;
24+
25+ /** @var resource */
26+ private static $ stdin ;
27+
2328 /** @var \Threaded */
2429 protected $ buffer ;
2530 private $ shutdown = false ;
26- private $ stdin ;
31+ private $ type = self ::TYPE_STREAM ;
32+
2733
2834 public function __construct (){
29- $ this ->stdin = fopen ("php://stdin " , "r " );
3035 $ this ->buffer = new \Threaded ;
3136 $ this ->start ();
3237 }
@@ -36,13 +41,81 @@ public function shutdown(){
3641 }
3742
3843 public function quit (){
44+ $ wait = microtime (true ) + 0.5 ;
45+ while (microtime (true ) < $ wait ){
46+ if ($ this ->isRunning ()){
47+ usleep (100000 );
48+ }else {
49+ parent ::quit ();
50+ return ;
51+ }
52+ }
53+ }
54+
55+ private function initStdin (){
56+ if (is_resource (self ::$ stdin )){
57+ fclose (self ::$ stdin );
58+ }
59+
60+ self ::$ stdin = fopen ("php://stdin " , "r " );
61+ if ($ this ->isPipe (self ::$ stdin )){
62+ $ this ->type = self ::TYPE_PIPED ;
63+ }else {
64+ $ this ->type = self ::TYPE_STREAM ;
65+ }
66+ }
67+
68+ /**
69+ * Checks if the specified stream is a FIFO pipe.
70+ *
71+ * @param resource $stream
72+ *
73+ * @return bool
74+ */
75+ private function isPipe ($ stream ) : bool {
76+ return is_resource ($ stream ) and (!stream_isatty ($ stream ) or ((fstat ($ stream )["mode " ] & 0170000 ) === 0010000 ));
3977 }
4078
41- private function readLine (){
42- $ line = trim (fgets ($ this ->stdin ));
79+ /**
80+ * Reads a line from the console and adds it to the buffer. This method may block the thread.
81+ *
82+ * @return bool if the main execution should continue reading lines
83+ */
84+ private function readLine () : bool {
85+ $ line = "" ;
86+ if (!is_resource (self ::$ stdin )){
87+ $ this ->initStdin ();
88+ }
89+
90+ switch ($ this ->type ){
91+ /** @noinspection PhpMissingBreakStatementInspection */
92+ case self ::TYPE_STREAM :
93+ //stream_select doesn't work on piped streams for some reason
94+ $ r = [self ::$ stdin ];
95+ if (($ count = stream_select ($ r , $ w , $ e , 0 , 200000 )) === 0 ){ //nothing changed in 200000 microseconds
96+ return true ;
97+ }elseif ($ count === false ){ //stream error
98+ $ this ->initStdin ();
99+ }
100+
101+ case self ::TYPE_PIPED :
102+ if (($ raw = fgets (self ::$ stdin )) === false ){ //broken pipe or EOF
103+ $ this ->initStdin ();
104+ $ this ->synchronized (function (){
105+ $ this ->wait (200000 );
106+ }); //prevent CPU waste if it's end of pipe
107+ return true ; //loop back round
108+ }
109+
110+ $ line = trim ($ raw );
111+ break ;
112+ }
113+
43114 if ($ line !== "" ){
44- $ this ->buffer [] = $ line ;
115+ $ this ->buffer [] = preg_replace ( " # \\ x1b \\ x5b([^ \\ x1b]* \\ x7e|[ \\ x40- \\ x50])# " , "" , $ line) ;
45116 }
117+
118+ return true ;
46119 }
47120
48121 /**
@@ -52,35 +125,20 @@ private function readLine(){
52125 */
53126 public function getLine (){
54127 if ($ this ->buffer ->count () !== 0 ){
55- return $ this ->buffer ->shift ();
128+ return ( string ) $ this ->buffer ->shift ();
56129 }
57130
58131 return null ;
59132 }
60133
61134 public function run (){
62- while (!$ this ->shutdown ){
63- $ r = [$ this ->stdin ];
64- $ w = null ;
65- $ e = null ;
66- if (stream_select ($ r , $ w , $ e , 0 , 200000 ) > 0 ){
67- // PHP on Windows sucks
68- if (feof ($ this ->stdin )){
69- if (Util::getOS () == "win " ){
70- $ this ->stdin = fopen ("php://stdin " , "r " );
71- if (!is_resource ($ this ->stdin )){
72- break ;
73- }
74- }else {
75- break ;
76- }
77- }
78- $ this ->readLine ();
79- }
80- }
135+ $ this ->registerClassLoader ();
136+ $ this ->initStdin ();
137+ while (!$ this ->shutdown and $ this ->readLine ()) ;
138+ fclose (self ::$ stdin );
81139 }
82140
83- public function getThreadName (){
141+ public function getThreadName () : string {
84142 return "Console " ;
85143 }
86144}
0 commit comments