mirror of
https://github.com/gnustep/libs-base.git
synced 2025-04-25 17:51:01 +00:00
Various tidyups and documentation added.
git-svn-id: svn+ssh://svn.gna.org/svn/gnustep/libs/base/trunk@9622 72102866-910b-0410-8b05-ffd578937521
This commit is contained in:
parent
274fd06643
commit
d6be9f040d
3 changed files with 183 additions and 64 deletions
|
@ -1,3 +1,9 @@
|
||||||
|
2001-04-19 Richard Frith-Macdonald <rfm@gnu.org>
|
||||||
|
|
||||||
|
* Source/NSTask.m: Tidied code a little, added safety checks for
|
||||||
|
usePseudoTerminal method.
|
||||||
|
* Documentation/gsdoc/NSTask.gsdoc: documentation fleshed out.
|
||||||
|
|
||||||
2001-04-16 Adam Fedor <fedor@gnu.org>
|
2001-04-16 Adam Fedor <fedor@gnu.org>
|
||||||
|
|
||||||
* aclocal.m4: Detect the xm2-config script.
|
* aclocal.m4: Detect the xm2-config script.
|
||||||
|
|
|
@ -21,6 +21,8 @@
|
||||||
</p>
|
</p>
|
||||||
<hr>
|
<hr>
|
||||||
|
|
||||||
|
The NSTask class provides a mechanism to run separate tasks
|
||||||
|
under (limited) control of your program.
|
||||||
|
|
||||||
<h2>Instance Variables </h2>
|
<h2>Instance Variables </h2>
|
||||||
<ul>
|
<ul>
|
||||||
|
@ -31,135 +33,224 @@
|
||||||
<li><a href ="NSTask.html#method-1">-arguments</a>
|
<li><a href ="NSTask.html#method-1">-arguments</a>
|
||||||
<li><a href ="NSTask.html#method-2">-currentDirectoryPath</a>
|
<li><a href ="NSTask.html#method-2">-currentDirectoryPath</a>
|
||||||
<li><a href ="NSTask.html#method-3">-environment</a>
|
<li><a href ="NSTask.html#method-3">-environment</a>
|
||||||
<li><a href ="NSTask.html#method-4">-init</a>
|
<li><a href ="NSTask.html#method-4">-interrupt</a>
|
||||||
<li><a href ="NSTask.html#method-5">-interrupt</a>
|
<li><a href ="NSTask.html#method-5">-isRunning</a>
|
||||||
<li><a href ="NSTask.html#method-6">-isRunning</a>
|
<li><a href ="NSTask.html#method-6">-launch</a>
|
||||||
<li><a href ="NSTask.html#method-7">-launch</a>
|
<li><a href ="NSTask.html#method-7">-launchPath</a>
|
||||||
<li><a href ="NSTask.html#method-8">-launchPath</a>
|
<li><a href ="NSTask.html#method-8">-setArguments:</a>
|
||||||
<li><a href ="NSTask.html#method-9">-setArguments:</a>
|
<li><a href ="NSTask.html#method-9">-setCurrentDirectoryPath:</a>
|
||||||
<li><a href ="NSTask.html#method-10">-setCurrentDirectoryPath:</a>
|
<li><a href ="NSTask.html#method-10">-setEnvironment:</a>
|
||||||
<li><a href ="NSTask.html#method-11">-setEnvironment:</a>
|
<li><a href ="NSTask.html#method-11">-setLaunchPath:</a>
|
||||||
<li><a href ="NSTask.html#method-12">-setLaunchPath:</a>
|
<li><a href ="NSTask.html#method-12">-setStandardError:</a>
|
||||||
<li><a href ="NSTask.html#method-13">-setStandardError:</a>
|
<li><a href ="NSTask.html#method-13">-setStandardInput:</a>
|
||||||
<li><a href ="NSTask.html#method-14">-setStandardInput:</a>
|
<li><a href ="NSTask.html#method-14">-setStandardOutput:</a>
|
||||||
<li><a href ="NSTask.html#method-15">-setStandardOutput:</a>
|
<li><a href ="NSTask.html#method-15">-standardError</a>
|
||||||
<li><a href ="NSTask.html#method-16">-standardError</a>
|
<li><a href ="NSTask.html#method-16">-standardInput</a>
|
||||||
<li><a href ="NSTask.html#method-17">-standardInput</a>
|
<li><a href ="NSTask.html#method-17">-standardOutput</a>
|
||||||
<li><a href ="NSTask.html#method-18">-standardOutput</a>
|
<li><a href ="NSTask.html#method-18">-terminate</a>
|
||||||
<li><a href ="NSTask.html#method-19">-terminate</a>
|
<li><a href ="NSTask.html#method-19">-terminationStatus</a>
|
||||||
<li><a href ="NSTask.html#method-20">-terminationStatus</a>
|
<li><a href ="NSTask.html#method-20">-usePseudoTerminal</a>
|
||||||
<li><a href ="NSTask.html#method-21">-waitUntilExit</a>
|
<li><a href ="NSTask.html#method-21">-waitUntilExit</a>
|
||||||
</ul>
|
</ul>
|
||||||
<hr><h2>Class Methods </h2>
|
<hr><h2>Class Methods </h2>
|
||||||
<h3><a name ="method-0">launchedTaskWithLaunchPath:arguments:</a></h3>
|
<h3><a name ="method-0">launchedTaskWithLaunchPath:arguments:</a></h3>
|
||||||
+ (NSTask*) <b>launchedTaskWithLaunchPath:</b> (NSString*)path <b>arguments:</b> (NSArray*)arguments;<br>
|
+ (NSTask*) <b>launchedTaskWithLaunchPath:</b> (NSString*)path <b>arguments:</b> (NSArray*)arguments;<br>
|
||||||
|
|
||||||
|
Creates and launches a task, returning an autoreleased task object.
|
||||||
|
Supplies the path to the executable and an array of argument.
|
||||||
|
The task inherits the parents environment and I/O.
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
<hr><h2>Instances Methods </h2>
|
<hr><h2>Instances Methods </h2>
|
||||||
<h3><a name ="method-1">arguments</a></h3>
|
<h3><a name ="method-1">arguments</a></h3>
|
||||||
- (NSArray*) <b>arguments</b>;<br>
|
- (NSArray*) <b>arguments</b>;<br>
|
||||||
|
|
||||||
|
Returns the arguments set for the task.
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
<h3><a name ="method-2">currentDirectoryPath</a></h3>
|
<h3><a name ="method-2">currentDirectoryPath</a></h3>
|
||||||
- (NSString*) <b>currentDirectoryPath</b>;<br>
|
- (NSString*) <b>currentDirectoryPath</b>;<br>
|
||||||
|
|
||||||
|
Returns the working directory set for the task.
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
<h3><a name ="method-3">environment</a></h3>
|
<h3><a name ="method-3">environment</a></h3>
|
||||||
- (NSDictionary*) <b>environment</b>;<br>
|
- (NSDictionary*) <b>environment</b>;<br>
|
||||||
|
|
||||||
|
Returns the environment set for the task.
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
<h3><a name ="method-4">init</a></h3>
|
<h3><a name ="method-4">interrupt</a></h3>
|
||||||
- (id) <b>init</b>;<br>
|
|
||||||
|
|
||||||
|
|
||||||
<hr>
|
|
||||||
<h3><a name ="method-5">interrupt</a></h3>
|
|
||||||
- (void) <b>interrupt</b>;<br>
|
- (void) <b>interrupt</b>;<br>
|
||||||
|
|
||||||
|
Sends an interrupt signal to the receiver and any subtasks.<br>
|
||||||
|
If the task has not been launched, raises an
|
||||||
|
NSInvalidArgumentException.<br>
|
||||||
|
Has no effect on a task that has already terminated.<br>
|
||||||
|
This is rather like the terminate method, but the child
|
||||||
|
process may not choose to terminate in response to an interrupt.
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
<h3><a name ="method-6">isRunning</a></h3>
|
<h3><a name ="method-5">isRunning</a></h3>
|
||||||
- (BOOL) <b>isRunning</b>;<br>
|
- (BOOL) <b>isRunning</b>;<br>
|
||||||
|
|
||||||
|
Checks to see if the task is currently running.
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
<h3><a name ="method-7">launch</a></h3>
|
<h3><a name ="method-6">launch</a></h3>
|
||||||
- (void) <b>launch</b>;<br>
|
- (void) <b>launch</b>;<br>
|
||||||
|
|
||||||
|
Launches the task.<br>
|
||||||
|
Raises an NSInvalidArgumentException if the launch path is not
|
||||||
|
set or if the subtask cannot be started for some reason
|
||||||
|
(eg. the executable does not exist).
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
<h3><a name ="method-8">launchPath</a></h3>
|
<h3><a name ="method-7">launchPath</a></h3>
|
||||||
- (NSString*) <b>launchPath</b>;<br>
|
- (NSString*) <b>launchPath</b>;<br>
|
||||||
|
|
||||||
|
Returns the launch path set for the task.
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
<h3><a name ="method-9">setArguments:</a></h3>
|
<h3><a name ="method-8">setArguments:</a></h3>
|
||||||
- (void) <b>setArguments:</b> (NSArray*)arguments;<br>
|
- (void) <b>setArguments:</b> (NSArray*)arguments;<br>
|
||||||
|
|
||||||
|
Sets an array of arguments to be supplied to the task when it
|
||||||
|
is launched. The default is an empty array. This method cannot
|
||||||
|
be used after a task is launched ...
|
||||||
|
it raises an NSInvalidArgumentException.
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
<h3><a name ="method-10">setCurrentDirectoryPath:</a></h3>
|
<h3><a name ="method-9">setCurrentDirectoryPath:</a></h3>
|
||||||
- (void) <b>setCurrentDirectoryPath:</b> (NSString*)path;<br>
|
- (void) <b>setCurrentDirectoryPath:</b> (NSString*)path;<br>
|
||||||
|
|
||||||
|
Sets the home directory in which the task is to be run.
|
||||||
|
The default is the parent processes directory.
|
||||||
|
This method cannot be used after a task is launched ...
|
||||||
|
it raises an NSInvalidArgumentException.
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
<h3><a name ="method-11">setEnvironment:</a></h3>
|
<h3><a name ="method-10">setEnvironment:</a></h3>
|
||||||
- (void) <b>setEnvironment:</b> (NSDictionary*)environmentDictionary;<br>
|
- (void) <b>setEnvironment:</b> (NSDictionary*)environmentDictionary;<br>
|
||||||
|
|
||||||
|
Sets the environment variables for the task to be run.
|
||||||
|
The default is the parent processes environment.
|
||||||
|
This method cannot be used after a task is launched ...
|
||||||
|
it raises an NSInvalidArgumentException.
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
<h3><a name ="method-12">setLaunchPath:</a></h3>
|
<h3><a name ="method-11">setLaunchPath:</a></h3>
|
||||||
- (void) <b>setLaunchPath:</b> (NSString*)path;<br>
|
- (void) <b>setLaunchPath:</b> (NSString*)path;<br>
|
||||||
|
|
||||||
|
Sets the path to the executable file to be run.
|
||||||
|
There is no default for this - you must set the launch path.
|
||||||
|
This method cannot be used after a task is launched ...
|
||||||
|
it raises an NSInvalidArgumentException.
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
<h3><a name ="method-13">setStandardError:</a></h3>
|
<h3><a name ="method-12">setStandardError:</a></h3>
|
||||||
- (void) <b>setStandardError:</b> (id)file;<br>
|
- (void) <b>setStandardError:</b> (id)file;<br>
|
||||||
|
|
||||||
|
Sets the standard error stream for the task.<br>
|
||||||
|
This is normally a writable NSFileHandle object.
|
||||||
|
If this is an NSPipe, the write end of the pipe is
|
||||||
|
automatically closed on launching.<br>
|
||||||
|
The default behavior is to inherit the parent processes
|
||||||
|
stderr output.<br>
|
||||||
|
This method cannot be used after a task is launched ...
|
||||||
|
it raises an NSInvalidArgumentException.
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
<h3><a name ="method-14">setStandardInput:</a></h3>
|
<h3><a name ="method-13">setStandardInput:</a></h3>
|
||||||
- (void) <b>setStandardInput:</b> (id)file;<br>
|
- (void) <b>setStandardInput:</b> (id)file;<br>
|
||||||
|
|
||||||
|
Sets the standard input stream for the task.<br>
|
||||||
|
This is normally a readable NSFileHandle object.
|
||||||
|
If this is an NSPipe, the read end of the pipe is
|
||||||
|
automatically closed on launching.<br>
|
||||||
|
The default behavior is to inherit the parent processes
|
||||||
|
stdin stream.<br>
|
||||||
|
This method cannot be used after a task is launched ...
|
||||||
|
it raises an NSInvalidArgumentException.
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
<h3><a name ="method-15">setStandardOutput:</a></h3>
|
<h3><a name ="method-14">setStandardOutput:</a></h3>
|
||||||
- (void) <b>setStandardOutput:</b> (id)file;<br>
|
- (void) <b>setStandardOutput:</b> (id)file;<br>
|
||||||
|
|
||||||
|
Sets the standard output stream for the task.<br>
|
||||||
|
This is normally a writable NSFileHandle object.
|
||||||
|
If this is an NSPipe, the write end of the pipe is
|
||||||
|
automatically closed on launching.<br>
|
||||||
|
The default behavior is to inherit the parent processes
|
||||||
|
stdout stream.<br>
|
||||||
|
This method cannot be used after a task is launched ...
|
||||||
|
it raises an NSInvalidArgumentException.
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
<h3><a name ="method-16">standardError</a></h3>
|
<h3><a name ="method-15">standardError</a></h3>
|
||||||
- (id) <b>standardError</b>;<br>
|
- (id) <b>standardError</b>;<br>
|
||||||
|
|
||||||
|
Returns the standard error stream for the task - an NSFileHandle
|
||||||
|
unless an NSPipe was passed to setStandardError:
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
<h3><a name ="method-17">standardInput</a></h3>
|
<h3><a name ="method-16">standardInput</a></h3>
|
||||||
- (id) <b>standardInput</b>;<br>
|
- (id) <b>standardInput</b>;<br>
|
||||||
|
|
||||||
|
Returns the standard input stream for the task - an NSFileHandle
|
||||||
|
unless an NSPipe was passed to setStandardInput:
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
<h3><a name ="method-18">standardOutput</a></h3>
|
<h3><a name ="method-17">standardOutput</a></h3>
|
||||||
- (id) <b>standardOutput</b>;<br>
|
- (id) <b>standardOutput</b>;<br>
|
||||||
|
|
||||||
|
Returns the standard output stream for the task - an NSFileHandle
|
||||||
|
unless an NSPipe was passed to setStandardOutput:
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
<h3><a name ="method-19">terminate</a></h3>
|
<h3><a name ="method-18">terminate</a></h3>
|
||||||
- (void) <b>terminate</b>;<br>
|
- (void) <b>terminate</b>;<br>
|
||||||
|
|
||||||
|
Sends a terminate signal to the receiver and any subtasks.<br>
|
||||||
|
If the task has not been launched, raises an
|
||||||
|
NSInvalidArgumentException.<br>
|
||||||
|
Has no effect on a task that has already terminated.<br>
|
||||||
|
When a task temrinates, either due to this method being called,
|
||||||
|
or normal termination, an NSTaskDidTerminateNotification is
|
||||||
|
posted.
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
<h3><a name ="method-20">terminationStatus</a></h3>
|
<h3><a name ="method-19">terminationStatus</a></h3>
|
||||||
- (int) <b>terminationStatus</b>;<br>
|
- (int) <b>terminationStatus</b>;<br>
|
||||||
|
|
||||||
|
Returns the termination status of the task.<br>
|
||||||
|
If the task has not completed running, raises an
|
||||||
|
NSInvalidArgumentException.
|
||||||
|
|
||||||
|
<hr>
|
||||||
|
<h3><a name ="method-20">usePseudoTerminal</a></h3>
|
||||||
|
- (BOOL) <b>usePseudoTerminal</b>;<br>
|
||||||
|
Standards: GNUstep NotMacOS-X NotOpenStep<br>
|
||||||
|
|
||||||
|
If the system supports it, this method sets the standard
|
||||||
|
input, output, and error streams to a pseudo-terminal so
|
||||||
|
that, when launched, the child task will act as if it was
|
||||||
|
running interactively on a terminal. The file handles
|
||||||
|
can then be used to communicate with the child.<br>
|
||||||
|
This method cannot be used after a task is launched ...
|
||||||
|
it raises an NSInvalidArgumentException.<br>
|
||||||
|
The standard input, output and error streams cannot be
|
||||||
|
changed after calling this method.<br>
|
||||||
|
The method returns YES on success, NO on failure.
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
<h3><a name ="method-21">waitUntilExit</a></h3>
|
<h3><a name ="method-21">waitUntilExit</a></h3>
|
||||||
- (void) <b>waitUntilExit</b>;<br>
|
- (void) <b>waitUntilExit</b>;<br>
|
||||||
|
|
||||||
|
Suspends the current thread until the task terminates, by
|
||||||
|
waiting in NSRunLoop (NSDefaultRunLoopMode) for the task
|
||||||
|
termination.<br>
|
||||||
|
Returns immediately if the task is not running.
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -85,7 +85,7 @@ static void handleSignal(int sig)
|
||||||
@interface NSConcreteUnixTask : NSTask
|
@interface NSConcreteUnixTask : NSTask
|
||||||
{
|
{
|
||||||
char slave_name[32];
|
char slave_name[32];
|
||||||
BOOL usePseudoTerminal;
|
BOOL _usePseudoTerminal;
|
||||||
}
|
}
|
||||||
@end
|
@end
|
||||||
#define NSConcreteTask NSConcreteUnixTask
|
#define NSConcreteTask NSConcreteUnixTask
|
||||||
|
@ -781,7 +781,7 @@ GSCheckTasks()
|
||||||
{
|
{
|
||||||
BOOL found = NO;
|
BOOL found = NO;
|
||||||
|
|
||||||
if (hadChildSignal)
|
if (hadChildSignal == YES)
|
||||||
{
|
{
|
||||||
int result;
|
int result;
|
||||||
int status;
|
int status;
|
||||||
|
@ -792,33 +792,24 @@ GSCheckTasks()
|
||||||
{
|
{
|
||||||
result = waitpid(-1, &status, WNOHANG);
|
result = waitpid(-1, &status, WNOHANG);
|
||||||
if (result > 0)
|
if (result > 0)
|
||||||
{
|
|
||||||
if (WIFEXITED(status))
|
|
||||||
{
|
{
|
||||||
NSTask *t;
|
NSTask *t;
|
||||||
|
|
||||||
[tasksLock lock];
|
[tasksLock lock];
|
||||||
t = (NSTask*)NSMapGet(activeTasks, (void*)result);
|
t = (NSTask*)NSMapGet(activeTasks, (void*)result);
|
||||||
[tasksLock unlock];
|
[tasksLock unlock];
|
||||||
if (t)
|
if (t != nil)
|
||||||
|
{
|
||||||
|
if (WIFEXITED(status))
|
||||||
{
|
{
|
||||||
[t _terminatedChild: WEXITSTATUS(status)];
|
[t _terminatedChild: WEXITSTATUS(status)];
|
||||||
found = YES;
|
found = YES;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else if (WIFSIGNALED(status))
|
else if (WIFSIGNALED(status))
|
||||||
{
|
|
||||||
NSTask *t;
|
|
||||||
|
|
||||||
[tasksLock lock];
|
|
||||||
t = (NSTask*)NSMapGet(activeTasks, (void*)result);
|
|
||||||
[tasksLock unlock];
|
|
||||||
if (t)
|
|
||||||
{
|
{
|
||||||
[t _terminatedChild: WTERMSIG(status)];
|
[t _terminatedChild: WTERMSIG(status)];
|
||||||
found = YES;
|
found = YES;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
NSLog(@"Warning ... task %d neither exited nor signalled",
|
NSLog(@"Warning ... task %d neither exited nor signalled",
|
||||||
|
@ -826,6 +817,7 @@ GSCheckTasks()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
while (result > 0);
|
while (result > 0);
|
||||||
}
|
}
|
||||||
return found;
|
return found;
|
||||||
|
@ -955,7 +947,7 @@ GSCheckTasks()
|
||||||
#endif
|
#endif
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (usePseudoTerminal == YES)
|
if (_usePseudoTerminal == YES)
|
||||||
{
|
{
|
||||||
int s;
|
int s;
|
||||||
|
|
||||||
|
@ -1049,6 +1041,36 @@ GSCheckTasks()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void) setStandardError: (id)hdl
|
||||||
|
{
|
||||||
|
if (_usePseudoTerminal == YES)
|
||||||
|
{
|
||||||
|
[NSException raise: NSInvalidArgumentException
|
||||||
|
format: @"NSTask - set error for task on pseudo terminal"];
|
||||||
|
}
|
||||||
|
[super setStandardError: hdl];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void) setStandardInput: (id)hdl
|
||||||
|
{
|
||||||
|
if (_usePseudoTerminal == YES)
|
||||||
|
{
|
||||||
|
[NSException raise: NSInvalidArgumentException
|
||||||
|
format: @"NSTask - set input for task on pseudo terminal"];
|
||||||
|
}
|
||||||
|
[super setStandardInput: hdl];
|
||||||
|
}
|
||||||
|
|
||||||
|
- (void) setStandardOutput: (id)hdl
|
||||||
|
{
|
||||||
|
if (_usePseudoTerminal == YES)
|
||||||
|
{
|
||||||
|
[NSException raise: NSInvalidArgumentException
|
||||||
|
format: @"NSTask - set output for task on pseudo terminal"];
|
||||||
|
}
|
||||||
|
[super setStandardOutput: hdl];
|
||||||
|
}
|
||||||
|
|
||||||
- (void) _collectChild
|
- (void) _collectChild
|
||||||
{
|
{
|
||||||
if (_hasCollected == NO)
|
if (_hasCollected == NO)
|
||||||
|
@ -1100,7 +1122,7 @@ GSCheckTasks()
|
||||||
int master;
|
int master;
|
||||||
NSFileHandle *fh;
|
NSFileHandle *fh;
|
||||||
|
|
||||||
if (usePseudoTerminal == YES)
|
if (_usePseudoTerminal == YES)
|
||||||
{
|
{
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
@ -1123,7 +1145,7 @@ GSCheckTasks()
|
||||||
closeOnDealloc: YES];
|
closeOnDealloc: YES];
|
||||||
[self setStandardError: fh];
|
[self setStandardError: fh];
|
||||||
RELEASE(fh);
|
RELEASE(fh);
|
||||||
usePseudoTerminal = YES;
|
_usePseudoTerminal = YES;
|
||||||
return YES;
|
return YES;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue