Socket.pm.svn-base
上传用户:market2
上传日期:2018-11-18
资源大小:18786k
文件大小:9k
源码类别:

外挂编程

开发平台:

Windows_Unix

  1. #########################################################################
  2. #  OpenKore - Socket interface
  3. #
  4. #  Copyright (c) 2007 OpenKore development team
  5. #
  6. #  This program is free software; you can redistribute it and/or modify
  7. #  it under the terms of the GNU General Public License as published by
  8. #  the Free Software Foundation; either version 2 of the License, or
  9. #  (at your option) any later version.
  10. #
  11. #  This program is distributed in the hope that it will be useful,
  12. #  but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  14. #  GNU General Public License for more details.
  15. #########################################################################
  16. ##
  17. # MODULE DESCRIPTION: Socket interface.
  18. #
  19. # An interface which runs on a Unix socket. Any number of clients can
  20. # connect to the socket to view OpenKore messages and to enter user input.
  21. # This allows one to run OpenKore in the background without the use of tools
  22. # like GNU screen.
  23. #
  24. # <h2>Protocol</h2>
  25. #
  26. # <h3>Passive vs active modes</h3>
  27. # Clients can be in two modes:
  28. # `l
  29. # - Passive. In this state, no data is sent to the client unless the
  30. #   client queries the server for certain information.
  31. # - Active. In this state, the server will actively send the latest events
  32. #   (new log messages, title changes, etc.) to the client.
  33. # `l`
  34. # Upon connecting to the server, a client is set to passive mode by default.
  35. #
  36. # The client can switch to passive or active modes with the messages
  37. # "set passive" and "set active".
  38. #
  39. # <h3>Interface event messages</h3>
  40. #
  41. # <h4>output (server to client)</h4>
  42. # This message is sent when a message is to be displayed on screen. It has the following
  43. # parameters: "type", "message", "domain".
  44. #
  45. # <h4>title changed (server to client)</h4>
  46. # This message is sent whenever the title of the interface is changed. It has one parameter, "title".
  47. #
  48. # <h4>input (client to server)</h4>
  49. # Tell the server that the user has entered some text as input. It has one parameter, "data".
  50. package Interface::Socket;
  51. use strict;
  52. use Time::HiRes qw(sleep);
  53. use IO::Socket;
  54. use Interface;
  55. use Translation;
  56. use base qw(Interface);
  57. use Utils qw(timeOut);
  58. use Interface::Console::Simple;
  59. use constant MAX_LOG_ENTRIES => 5000;
  60. sub new {
  61. my ($class) = @_;
  62. my (%self, $f);
  63. $self{server} = new Interface::Socket::Server();
  64. $self{console} = new Interface::Console::Simple();
  65. open($f, ">:utf8", "$Settings::logs_folder/console.log");
  66. $self{consoleLogFile} = $f;
  67. $self{logEntryCount} = 0;
  68. return bless %self, $class;
  69. }
  70. sub iterate {
  71. my ($self) = @_;
  72. $self->{server}->iterate();
  73. }
  74. sub getInput {
  75. my ($self, $timeout) = @_;
  76. my $line;
  77. if ($timeout < 0) {
  78. $self->{server}->setWaitingForInput(1);
  79. while (!defined($line)) {
  80. if (my $input = $self->{console}->getInput(0)) {
  81. $self->{server}->addInput($input);
  82. }
  83. if ($self->{server}->hasInput()) {
  84. $line = $self->{server}->getInput();
  85. } else {
  86. $self->{server}->iterate();
  87. sleep 0.01;
  88. }
  89. }
  90. $self->{server}->setWaitingForInput(0);
  91. } elsif ($timeout == 0) {
  92. if ($self->{server}->hasInput()) {
  93. $line = $self->{server}->getInput();
  94. }
  95. } else {
  96. my %time = (time => time, timeout => $timeout);
  97. $self->{server}->setWaitingForInput(1);
  98. while (!defined($line) && !timeOut(%time)) {
  99. if (my $input = $self->{console}->getInput(0)) {
  100. $self->{server}->addInput($input);
  101. }
  102. if ($self->{server}->hasInput()) {
  103. $line = $self->{server}->getInput();
  104. } else {
  105. $self->{server}->iterate();
  106. sleep 0.01;
  107. }
  108. }
  109. $self->{server}->setWaitingForInput(0);
  110. }
  111. return $line;
  112. }
  113. sub writeOutput {
  114. my $self = shift;
  115. $self->{server}->addMessage(@_);
  116. $self->{console}->writeOutput(@_);
  117. if ($self->{consoleLogFile}) {
  118. $self->{logEntryCount}++;
  119. if ($self->{logEntryCount} < MAX_LOG_ENTRIES) {
  120. $self->{consoleLogFile}->print($_[1]);
  121. } else {
  122. truncate $self->{consoleLogFile}, 0;
  123. $self->{consoleLogFile}->print($self->{server}->getScrollbackBuffer());
  124. }
  125. $self->{consoleLogFile}->flush();
  126. }
  127. }
  128. sub title {
  129. my ($self, $title) = @_;
  130. if ($title) {
  131. if (!defined($self->{title}) || $self->{title} ne $title) {
  132. $self->{title} = $title;
  133. $self->{server}->setTitle($title);
  134. $self->{console}->title($title);
  135. }
  136. } else {
  137. return $self->{title};
  138. }
  139. }
  140. sub errorDialog {
  141. my ($self, $message, $fatal) = @_;
  142. $fatal = 1 unless defined $fatal;
  143. $self->writeOutput("error", "$messagen", "error");
  144. if ($fatal) {
  145. $self->writeOutput("message", Translation::T("Enter 'e' or 'q' to exit this program.n"), "console")
  146. } else {
  147. $self->writeOutput("message", Translation::T("Enter 'c' to continue...n"), "console")
  148. }
  149. $self->getInput(-1);
  150. }
  151. package Interface::Socket::Server;
  152. use strict;
  153. use IO::Socket::UNIX;
  154. use Base::Server;
  155. use base qw(Base::Server);
  156. use Settings;
  157. use Bus::Messages qw(serialize);
  158. use Bus::MessageParser;
  159. use constant MAX_MESSAGE_SCROLLBACK => 20;
  160. # Client modes.
  161. use enum qw(PASSIVE ACTIVE);
  162. sub new {
  163. my ($class) = @_;
  164. my $socket_file = "$Settings::logs_folder/console.socket";
  165. my $pid_file = "$Settings::logs_folder/openkore.pid";
  166. my $socket = new IO::Socket::UNIX(
  167. Local => $socket_file,
  168. Type => SOCK_STREAM,
  169. Listen => 5
  170. );
  171. if (!$socket && $! == 98) {
  172. $socket = new IO::Socket::UNIX(
  173. Peer => $socket_file,
  174. Type => SOCK_STREAM
  175. );
  176. if (!$socket) {
  177. unlink($socket_file);
  178. $socket = new IO::Socket::UNIX(
  179. Local => $socket_file,
  180. Type => SOCK_STREAM,
  181. Listen => 5
  182. );
  183. } else {
  184. print STDERR "There is already an OpenKore instance listening at '$socket_file'.n";
  185. exit 1;
  186. }
  187. }
  188. if (!$socket) {
  189. print STDERR "Cannot listen at '$socket_file': $!n";
  190. exit 1;
  191. }
  192. my $f;
  193. if (open($f, ">", $pid_file)) {
  194. print $f $$;
  195. close($f);
  196. } else {
  197. unlink $socket_file;
  198. print STDERR "Cannot write to PID file '$pid_file'.n";
  199. exit 1;
  200. }
  201. my $self = $class->SUPER::createFromSocket($socket);
  202. $self->{parser} = new Bus::MessageParser();
  203. # A message log, used to sent the last MAX_MESSAGE_SCROLLBACK messages to
  204. # the client when that client switches to active mode.
  205. $self->{messages} = [];
  206. $self->{inputs} = [];
  207. $self->{socket_file} = $socket_file;
  208. $self->{pid_file} = $pid_file;
  209. $self->{waitingForInput} = 0;
  210. $SIG{INT} = $SIG{TERM} = $SIG{QUIT} = sub {
  211. unlink $socket_file;
  212. unlink $pid_file;
  213. exit 2;
  214. };
  215. return $self;
  216. }
  217. sub DESTROY {
  218. my ($self) = @_;
  219. unlink $self->{socket_file};
  220. unlink $self->{pid_file};
  221. $self->SUPER::DESTROY();
  222. }
  223. #### Public methods ####
  224. sub addMessage {
  225. my ($self, $type, $message, $domain) = @_;
  226. $self->broadcast("output", {
  227. type    => $type,
  228. message => $message,
  229. domain  => $domain
  230. });
  231. # Add to message log.
  232. push @{$self->{messages}}, [$type, $message, $domain];
  233. if (@{$self->{messages}} > MAX_MESSAGE_SCROLLBACK) {
  234. shift @{$self->{messages}};
  235. }
  236. }
  237. # Broadcast a message to all clients.
  238. sub broadcast {
  239. my $self = shift;
  240. my $clients = $self->clients();
  241. if (@{$clients} > 0) {
  242. my $messageID = shift;
  243. my $message = serialize($messageID, @_);
  244. foreach my $client (@{$clients}) {
  245. if ($client->{mode} == ACTIVE) {
  246. $client->send($message);
  247. }
  248. }
  249. }
  250. }
  251. # Check there is anything in the input queue.
  252. sub hasInput {
  253. my ($self) = @_;
  254. return @{$self->{inputs}} > 0;
  255. }
  256. # Get the first input from the input queue.
  257. sub getInput {
  258. my ($self) = @_;
  259. return shift @{$self->{inputs}};
  260. }
  261. # Put something in the input queue.
  262. sub addInput {
  263. my ($self, $input, $client) = @_;
  264. push @{$self->{inputs}}, $input;
  265. # Tell all clients, except the one that generated this input,
  266. # that new input is received.
  267. my $clients = $self->clients();
  268. my $message = serialize('inputted', { data => $input });
  269. foreach my $client (@{$clients}) {
  270. if ($client->{mode} == ACTIVE) {
  271. $client->send($message);
  272. }
  273. }
  274. }
  275. sub setTitle {
  276. my ($self, $title) = @_;
  277. $self->{title} = $title;
  278. $self->broadcast("title changed", { title => $title });
  279. }
  280. sub setWaitingForInput {
  281. my ($self, $waitingForInput) = @_;
  282. $self->{waitingForInput} = $waitingForInput;
  283. }
  284. sub getScrollbackBuffer {
  285. my ($self) = @_;
  286. my $text = '';
  287. foreach my $message (@{$self->{messages}}) {
  288. $text .= $message->[1];
  289. }
  290. return $text;
  291. }
  292. #### Protected overrided methods ####
  293. sub onClientNew {
  294. my ($self, $client) = @_;
  295. $client->{mode} = PASSIVE;
  296. }
  297. sub onClientData {
  298. my ($self, $client, $data) = @_;
  299. $self->{parser}->add($data);
  300. my $ID;
  301. while (my $args = $self->{parser}->readNext($ID)) {
  302. if ($ID eq "input") {
  303. $self->addInput($args->{data}, $client);
  304. } elsif ($ID eq "set active") {
  305. $client->{mode} = ACTIVE;
  306. # Send the last few messages and the current title.
  307. foreach my $entry (@{$self->{messages}}) {
  308. my $message = serialize("output", {
  309. type    => $entry->[0],
  310. message => $entry->[1],
  311. domain  => $entry->[2]
  312. });
  313. $client->send($message);
  314. }
  315. $client->send(serialize("title changed", { title => $self->{title} })) if ($self->{title});
  316. } elsif ($ID eq "set passive") {
  317. $client->{mode} = PASSIVE;
  318. }
  319. }
  320. }
  321. 1;