diff options
Diffstat (limited to 'projects/mongrel_service/native')
-rw-r--r-- | projects/mongrel_service/native/_debug.bi | 59 | ||||
-rw-r--r-- | projects/mongrel_service/native/boolean.bi | 18 | ||||
-rw-r--r-- | projects/mongrel_service/native/console_process.bas | 397 | ||||
-rw-r--r-- | projects/mongrel_service/native/console_process.bi | 75 | ||||
-rw-r--r-- | projects/mongrel_service/native/mongrel_service.bas | 179 | ||||
-rw-r--r-- | projects/mongrel_service/native/mongrel_service.bi | 61 |
6 files changed, 0 insertions, 789 deletions
diff --git a/projects/mongrel_service/native/_debug.bi b/projects/mongrel_service/native/_debug.bi deleted file mode 100644 index 277de2e..0000000 --- a/projects/mongrel_service/native/_debug.bi +++ /dev/null @@ -1,59 +0,0 @@ -'##################################################################
-'#
-'# mongrel_service: Win32 native implementation for mongrel
-'# (using ServiceFB and FreeBASIC)
-'#
-'# Copyright (c) 2006 Multimedia systems
-'# (c) and code by Luis Lavena
-'#
-'# mongrel_service (native) and mongrel_service gem_pluing are licensed
-'# in the same terms as mongrel, please review the mongrel license at
-'# http://mongrel.rubyforge.org/license.html
-'#
-'##################################################################
-
-'##################################################################
-'# Requirements:
-'# - FreeBASIC 0.17, Win32 CVS Build (as for November 09, 2006).
-'#
-'##################################################################
-
-#ifndef __Debug_bi__
-#define __Debug_bi__
-
-#ifdef DEBUG_LOG
- #include once "vbcompat.bi"
- #ifndef DEBUG_LOG_FILE
- #define DEBUG_LOG_FILE EXEPATH + "\debug.log"
- #endif
-
- '# this procedure is only used for debugging purposed, will be removed from
- '# final compilation
- private sub debug_to_file(byref message as string, byref file as string, byval linenumber as uinteger, byref func as string)
- dim handle as integer
- static first_time as integer
-
- handle = freefile
- open DEBUG_LOG_FILE for append as #handle
-
- if (first_time = 0) then
- print #handle, "# Logfile created on "; format(now(), "dd/mm/yyyy HH:mm:ss")
- print #handle, ""
- first_time = 1
- end if
-
- '# src/module.bas:123, namespace.function:
- '# message
- '#
- print #handle, file; ":"; str(linenumber); ", "; lcase(func); ":"
- print #handle, space(2); message
- print #handle, ""
-
- close #handle
- end sub
- #define debug(message) debug_to_file(message, __FILE__, __LINE__, __FUNCTION__)
-#else
- #define debug(message)
-#endif '# DEBUG_LOG
-
-#endif '# __Debug_bi__
diff --git a/projects/mongrel_service/native/boolean.bi b/projects/mongrel_service/native/boolean.bi deleted file mode 100644 index 8ca07c7..0000000 --- a/projects/mongrel_service/native/boolean.bi +++ /dev/null @@ -1,18 +0,0 @@ -'#--
-'# Copyright (c) 2006-2007 Luis Lavena, Multimedia systems
-'#
-'# This source code is released under the MIT License.
-'# See MIT-LICENSE file for details
-'#++
-
-#ifndef __BOOLEAN_BI__
-#define __BOOLEAN_BI__
-
-#undef BOOLEAN
-type BOOLEAN as byte
-#undef FALSE
-const FALSE as byte = 0
-#undef TRUE
-const TRUE as byte = not FALSE
-
-#endif ' __BOOLEAN_BI__
\ No newline at end of file diff --git a/projects/mongrel_service/native/console_process.bas b/projects/mongrel_service/native/console_process.bas deleted file mode 100644 index a2bb5c9..0000000 --- a/projects/mongrel_service/native/console_process.bas +++ /dev/null @@ -1,397 +0,0 @@ -'#--
-'# Copyright (c) 2007 Luis Lavena, Multimedia systems
-'#
-'# This source code is released under the MIT License.
-'# See MIT-LICENSE file for details
-'#++
-
-#include once "console_process.bi"
-
-constructor ConsoleProcess(byref new_filename as string = "", byref new_arguments as string = "")
- '# assign filename and arguments
-
- '# if filename contains spaces, automatically quote it!
- if (instr(new_filename, " ") > 0) then
- _filename = !"\"" + new_filename + !"\""
- else
- _filename = new_filename
- endif
-
- _arguments = new_arguments
-end constructor
-
-destructor ConsoleProcess()
- '# in case process still running
- if (running = true) then
- terminate(true)
-
- '# close opened handles
- '# ...
- CloseHandle(_process_info.hProcess)
- CloseHandle(_process_info.hThread)
- end if
-end destructor
-
-property ConsoleProcess.filename() as string
- return _filename
-end property
-
-property ConsoleProcess.filename(byref rhs as string)
- if not (running = true) then
- _filename = rhs
- end if
-end property
-
-property ConsoleProcess.arguments() as string
- return _arguments
-end property
-
-property ConsoleProcess.arguments(byref rhs as string)
- if not (running = true) then
- _arguments = rhs
- end if
-end property
-
-property ConsoleProcess.redirected_stdout() as string
- return _stdout_filename
-end property
-
-property ConsoleProcess.redirected_stderr() as string
- return _stderr_filename
-end property
-
-'# running is a helper which evaluates _pid and exit_code
-property ConsoleProcess.running() as boolean
- dim result as boolean
-
- '# presume not running
- result = false
-
- if not (_pid = 0) then
- '# that means the process is/was running.
- '# now evaluate if exit_code = STILL_ACTIVE
- result = (exit_code = STILL_ACTIVE)
- end if
-
- return result
-end property
-
-property ConsoleProcess.pid() as uinteger
- return _pid
-end property
-
-property ConsoleProcess.exit_code() as uinteger
- static previous_code as uinteger
- dim result as uinteger
-
- result = 0
-
- '# is _pid valid?
- if not (_pid = 0) then
- if not (_process_info.hProcess = NULL) then
- '# the process reference is valid, get the exit_code
- if not (GetExitCodeProcess(_process_info.hProcess, @result) = 0) then
- previous_code = result
- '# OK
- '# no error in the query, get result
- if not (result = STILL_ACTIVE) then
- CloseHandle(_process_info.hProcess)
- _process_info.hProcess = NULL
- end if '# (result = STILL_ACTIVE)
- end if '# not (GetExitCodeProcess() = 0)
- else
- result = previous_code
- end if '# not (proc = NULL)
- end if '# not (_pid = 0)
-
- return result
-end property
-
-function ConsoleProcess.redirect(byval target as ProcessStdEnum, byref new_std_filename as string) as boolean
- dim result as boolean
-
- if not (running = true) then
- select case target
- case ProcessStdOut:
- _stdout_filename = new_std_filename
- result = true
-
- case ProcessStdErr:
- _stderr_filename = new_std_filename
- result = true
-
- case ProcessStdBoth:
- _stdout_filename = new_std_filename
- _stderr_filename = new_std_filename
- result = true
-
- end select
- end if
-
- return result
-end function
-
-function ConsoleProcess.start() as boolean
- dim result as boolean
- dim success as boolean
-
- '# API
- '# New Process resources
- dim context as STARTUPINFO
- dim proc_sa as SECURITY_ATTRIBUTES = type(sizeof(SECURITY_ATTRIBUTES), NULL, TRUE)
-
- '# StdIn, StdOut, StdErr Read and Write Pipes.
- dim as HANDLE StdInRd, StdOutRd, StdErrRd
- dim as HANDLE StdInWr, StdOutWr, StdErrWr
- dim merged as boolean
-
- '# cmdline
- dim cmdline as string
-
- '# assume start will fail
- result = false
-
- if (running = false) then
- '# we should create the std* for the new proc!
- '# (like good parents, prepare everything!)
-
- '# to ensure everything will work, we must allocate a console
- '# using AllocConsole, even if it fails.
- '# This solve the problems when running as service.
- '# we discard result of AllocConsole since we ALWAYS will allocate it.
- AllocConsole()
-
- '# assume all the following steps succeed
- success = true
-
- '# StdIn is the only std that will be created using pipes always
- '# StdIn
- if (CreatePipe(@StdInRd, @StdInWr, @proc_sa, 0) = 0) then
- success = false
- end if
-
- '# Ensure the handles to the pipe are not inherited.
- if (SetHandleInformation(StdInWr, HANDLE_FLAG_INHERIT, 0) = 0) then
- success = false
- end if
-
- '# StdOut and StdErr should be redirected?
- if (not _stdout_filename = "") or _
- (not _stderr_filename = "") then
-
- '# out and err are the same? (merged)
- if (_stdout_filename = _stderr_filename) then
- merged = true
- end if
- end if
-
- '# StdOut if stdout_filename
- if not (_stdout_filename = "") then
- StdOutWr = CreateFile(strptr(_stdout_filename), _
- GENERIC_WRITE, _
- FILE_SHARE_READ or FILE_SHARE_WRITE, _
- @proc_sa, _
- OPEN_ALWAYS, _
- FILE_ATTRIBUTE_NORMAL, _
- NULL)
-
- if (StdOutWr = INVALID_HANDLE_VALUE) then
- '# failed to open file
- success = false
- else
- SetFilePointer(StdOutWr, 0, NULL, FILE_END)
- end if
- else
- '# use pipes instead
- '# StdOut
- if (CreatePipe(@StdOutRd, @StdOutWr, @proc_sa, 0) = 0) then
- success = false
- end if
-
- if (SetHandleInformation(StdOutRd, HANDLE_FLAG_INHERIT, 0) = 0) then
- success = false
- end if
- end if 'not (_stdout_filename = "")
-
- '# only create stderr if no merged.
- if (merged = true) then
- StdErrWr = StdOutWr
- else
- '# do the same for StdErr...
- if not (_stderr_filename = "") then
- StdErrWr = CreateFile(strptr(_stderr_filename), _
- GENERIC_WRITE, _
- FILE_SHARE_READ or FILE_SHARE_WRITE, _
- @proc_sa, _
- OPEN_ALWAYS, _
- FILE_ATTRIBUTE_NORMAL, _
- NULL)
-
- if (StdErrWr = INVALID_HANDLE_VALUE) then
- '# failed to open file
- success = false
- else
- SetFilePointer(StdErrWr, 0, NULL, FILE_END)
- end if
- else
- '# use pipes instead
- '# StdOut
- if (CreatePipe(@StdErrRd, @StdErrWr, @proc_sa, 0) = 0) then
- success = false
- end if
-
- if (SetHandleInformation(StdErrRd, HANDLE_FLAG_INHERIT, 0) = 0) then
- success = false
- end if
-
- end if 'not (_stderr_filename = "")
- end if '(merged = true)
-
- '# now we must proceed to create the process
- '# without the pipes, we shouldn't continue!
- if (success = true) then
- '# Set the Std* handles ;-)
- with context
- .cb = sizeof( context )
- .hStdError = StdErrWr
- .hStdOutput = StdOutWr
- .hStdInput = StdInRd
- .dwFlags = STARTF_USESTDHANDLES or STARTF_USESHOWWINDOW
- '# FIXME: .wShowWindow = iif((_show_console = true), SW_SHOW, SW_HIDE)
- .wShowWindow = SW_HIDE
- end with
-
- '# build the command line
- cmdline = _filename + " " + _arguments
-
- '# now creates the process
- if (CreateProcess(NULL, _
- strptr(cmdline), _
- NULL, _
- NULL, _
- 1, _ '# win32 TRUE (1)
- 0, _
- NULL, _
- NULL, _
- @context, _
- @_process_info) = 0) then
- result = false
- else
- '# set the _pid
- _pid = _process_info.dwProcessId
-
- '# OK? yeah, I think so.
- result = true
-
- '# close the Std* handles
- CloseHandle(StdInRd)
- CloseHandle(StdInWr)
- CloseHandle(StdOutRd)
- CloseHandle(StdOutWr)
- CloseHandle(StdErrRd)
- CloseHandle(StdErrWr)
-
- '# close children main Thread handle and
- '# NULLify to avoid issues
- CloseHandle(_process_info.hThread)
- _process_info.hThread = NULL
- end if '# (CreateProcess() = 0)
- else
- result = false
- end if '# (success = TRUE)
- end if
-
- return result
-end function
-
-function ConsoleProcess.terminate(byval force as boolean = false) as boolean
- dim result as boolean
- dim success as boolean
-
- dim proc as HANDLE
- dim code as uinteger
- dim wait_code as uinteger
-
- '# is pid valid?
- if (running = true) then
- '# hook our custom console handler
- if not (SetConsoleCtrlHandler(@_console_handler, 1) = 0) then
- success = true
- end if
-
- if (success = true) then
- '# get a handle to Process
- proc = _process_info.hProcess
- if not (proc = NULL) then
- '# process is valid, perform actions
- success = false
-
- if not (force = true) then
- '# send CTRL_C_EVENT and wait for result
- if not (GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0) = 0) then
- '# it worked, wait 5 seconds terminates.
- wait_code = WaitForSingleObject(proc, 10000)
- if not (wait_code = WAIT_TIMEOUT) then
- success = true
- end if
- else
- success = false
- end if
-
- '# Ctrl-C didn't work, try Ctrl-Break
- if (success = false) then
- '# send CTRL_BREAK_EVENT and wait for result
- if not (GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, 0) = 0) then
- '# it worked, wait 5 seconds terminates.
- wait_code = WaitForSingleObject(proc, 10000)
- if not (wait_code = WAIT_TIMEOUT) then
- success = true
- end if
- else
- success = false
- end if
- end if
-
- '# only do termination if force was set.
- elseif (force = true) and (success = false) then
- '# still no luck? we should do a hard kill then
- if (TerminateProcess(proc, 0) = 0) then
- success = false
- else
- success = true
- end if
- end if
-
- '# now get process exit code
- if (success = true) then
- result = true
- else
- result = false
- end if
- else
- '# invalid process handler
- result = false
- end if
-
- end if '# (success = true)
-
- '# remove hooks
- if not (SetConsoleCtrlHandler(@_console_handler, 0) = 0) then
- success = true
- end if
- end if '# not (pid = 0)
-
- return result
-end function
-
-function ConsoleProcess._console_handler(byval dwCtrlType as DWORD) as BOOL
- dim result as BOOL
-
- if (dwCtrlType = CTRL_C_EVENT) then
- result = 1
- elseif (dwCtrlType = CTRL_BREAK_EVENT) then
- result = 1
- end if
-
- return result
-end function
diff --git a/projects/mongrel_service/native/console_process.bi b/projects/mongrel_service/native/console_process.bi deleted file mode 100644 index 3ad49ad..0000000 --- a/projects/mongrel_service/native/console_process.bi +++ /dev/null @@ -1,75 +0,0 @@ -'#--
-'# Copyright (c) 2006-2007 Luis Lavena, Multimedia systems
-'#
-'# This source code is released under the MIT License.
-'# See MIT-LICENSE file for details
-'#++
-
-#ifndef __CONSOLE_PROCESS_BI__
-#define __CONSOLE_PROCESS_BI__
-
-#include once "windows.bi"
-#include once "boolean.bi"
-
-enum ProcessStdEnum
- ProcessStdOut = 1
- ProcessStdErr = 2
- ProcessStdBoth = 3
-end enum
-
-type ConsoleProcess
- '# this class provide basic functionality
- '# to control child processes
-
- '# new ConsoleProcess(Filename, Parameters)
- declare constructor(byref as string = "", byref as string = "")
-
- '# delete
- declare destructor()
-
- '# properties (only getters)
- declare property filename as string
- declare property filename(byref as string)
-
- declare property arguments as string
- declare property arguments(byref as string)
-
- '# stdout and stderr allow you redirect
- '# console output and errors to files
- declare property redirected_stdout as string
- declare property redirected_stderr as string
-
- '# evaluate if the process is running
- declare property running as boolean
-
- '# pid will return the current Process ID, or 0 if no process is running
- declare property pid as uinteger
-
- '# exit_code is the value set by the process prior exiting.
- declare property exit_code as uinteger
-
- '# methods
- declare function redirect(byval as ProcessStdEnum, byref as string) as boolean
- declare function start() as boolean
- declare function terminate(byval as boolean = false) as boolean
-
- private:
- _filename as string
- _arguments as string
- _pid as uinteger
- _process_info as PROCESS_INFORMATION
- _show_console as boolean = false
-
- _redirect_stdout as boolean
- _stdout_filename as string
-
- _redirect_stderr as boolean
- _stderr_filename as string
-
- '# this fake console handler
- '# is used to trap ctrl-c
- declare static function _console_handler(byval as DWORD) as BOOL
-
-end type 'ConsoleProcess
-
-#endif '__CONSOLE_PROCESS_BI__
diff --git a/projects/mongrel_service/native/mongrel_service.bas b/projects/mongrel_service/native/mongrel_service.bas deleted file mode 100644 index 49caa1b..0000000 --- a/projects/mongrel_service/native/mongrel_service.bas +++ /dev/null @@ -1,179 +0,0 @@ -'##################################################################
-'#
-'# mongrel_service: Win32 native implementation for mongrel
-'# (using ServiceFB and FreeBASIC)
-'#
-'# Copyright (c) 2006 Multimedia systems
-'# (c) and code by Luis Lavena
-'#
-'# mongrel_service (native) and mongrel_service gem_pluing are licensed
-'# in the same terms as mongrel, please review the mongrel license at
-'# http://mongrel.rubyforge.org/license.html
-'#
-'##################################################################
-
-'##################################################################
-'# Requirements:
-'# - FreeBASIC 0.18
-'#
-'##################################################################
-
-#include once "mongrel_service.bi"
-#define DEBUG_LOG_FILE EXEPATH + "\mongrel_service.log"
-#include once "_debug.bi"
-
-namespace mongrel_service
- constructor SingleMongrel()
- dim redirect_file as string
-
- with this.__service
- .name = "single"
- .description = "Mongrel Single Process service"
-
- '# disabling shared process
- .shared_process = FALSE
-
- '# TODO: fix inheritance here
- .onInit = @single_onInit
- .onStart = @single_onStart
- .onStop = @single_onStop
- end with
-
- with this.__console
- redirect_file = EXEPATH + "\mongrel.log"
- debug("redirecting to: " + redirect_file)
- .redirect(ProcessStdBoth, redirect_file)
- end with
-
- '# TODO: fix inheritance here
- single_mongrel_ref = @this
- end constructor
-
- destructor SingleMongrel()
- '# TODO: fin inheritance here
- end destructor
-
- function single_onInit(byref self as ServiceProcess) as integer
- dim result as integer
- dim mongrel_cmd as string
-
- debug("single_onInit()")
-
- '# ruby.exe must be in the path, which we guess is already there.
- '# because mongrel_service executable (.exe) is located in the same
- '# folder than mongrel_rails ruby script, we complete the path with
- '# EXEPATH + "\mongrel_rails" to make it work.
- '# FIXED ruby installation outside PATH and inside folders with spaces
- mongrel_cmd = !"\"" + EXEPATH + !"\\ruby.exe" + !"\" " + !"\"" + EXEPATH + !"\\mongrel_rails" + !"\"" + " start"
-
- '# due lack of inheritance, we use single_mongrel_ref as pointer to
- '# SingleMongrel instance. now we should call StillAlive
- self.StillAlive()
- if (len(self.commandline) > 0) then
- '# assign the program
- single_mongrel_ref->__console.filename = mongrel_cmd
- single_mongrel_ref->__console.arguments = self.commandline
-
- '# fix commandline, it currently contains params to be passed to
- '# mongrel_rails, and not ruby.exe nor the script to be run.
- self.commandline = mongrel_cmd + " " + self.commandline
-
- '# now launch the child process
- debug("starting child process with cmdline: " + self.commandline)
- single_mongrel_ref->__child_pid = 0
- if (single_mongrel_ref->__console.start() = true) then
- single_mongrel_ref->__child_pid = single_mongrel_ref->__console.pid
- end if
- self.StillAlive()
-
- '# check if pid is valid
- if (single_mongrel_ref->__child_pid > 0) then
- '# it worked
- debug("child process pid: " + str(single_mongrel_ref->__child_pid))
- result = not FALSE
- end if
- else
- '# if no param, no service!
- debug("no parameters was passed to this service!")
- result = FALSE
- end if
-
- debug("single_onInit() done")
- return result
- end function
-
- sub single_onStart(byref self as ServiceProcess)
- debug("single_onStart()")
-
- do while (self.state = Running) or (self.state = Paused)
- '# instead of sitting idle here, we must monitor the pid
- '# and re-spawn a new process if needed
- if not (single_mongrel_ref->__console.running = true) then
- '# check if we aren't terminating
- if (self.state = Running) or (self.state = Paused) then
- debug("child process terminated!, re-spawning a new one")
-
- single_mongrel_ref->__child_pid = 0
- if (single_mongrel_ref->__console.start() = true) then
- single_mongrel_ref->__child_pid = single_mongrel_ref->__console.pid
- end if
-
- if (single_mongrel_ref->__child_pid > 0) then
- debug("new child process pid: " + str(single_mongrel_ref->__child_pid))
- end if
- end if
- end if
-
- '# wait for 5 seconds
- sleep 5000
- loop
-
- debug("single_onStart() done")
- end sub
-
- sub single_onStop(byref self as ServiceProcess)
- debug("single_onStop()")
-
- '# now terminates the child process
- if not (single_mongrel_ref->__child_pid = 0) then
- debug("trying to kill pid: " + str(single_mongrel_ref->__child_pid))
- if not (single_mongrel_ref->__console.terminate() = true) then
- debug("Terminate() reported a problem when terminating process " + str(single_mongrel_ref->__child_pid))
- else
- debug("child process terminated with success.")
- single_mongrel_ref->__child_pid = 0
- end if
- end if
-
- debug("single_onStop() done")
- end sub
-
- sub application()
- dim simple as SingleMongrel
- dim host as ServiceHost
- dim ctrl as ServiceController = ServiceController("Mongrel Win32 Service", "version " + VERSION, _
- "(c) 2006 The Mongrel development team.")
-
- '# add SingleMongrel (service)
- host.Add(simple.__service)
- select case ctrl.RunMode()
- '# call from Service Control Manager (SCM)
- case RunAsService:
- debug("ServiceHost RunAsService")
- host.Run()
-
- '# call from console, useful for debug purposes.
- case RunAsConsole:
- debug("ServiceController Console")
- ctrl.Console()
-
- case else:
- ctrl.Banner()
- print "mongrel_service is not designed to run form commandline,"
- print "please use mongrel_rails service:: commands to create a win32 service."
- end select
- end sub
-end namespace
-
-'# MAIN: start native mongrel_service here
-mongrel_service.application()
diff --git a/projects/mongrel_service/native/mongrel_service.bi b/projects/mongrel_service/native/mongrel_service.bi deleted file mode 100644 index d5ea0dc..0000000 --- a/projects/mongrel_service/native/mongrel_service.bi +++ /dev/null @@ -1,61 +0,0 @@ -'##################################################################
-'#
-'# mongrel_service: Win32 native implementation for mongrel
-'# (using ServiceFB and FreeBASIC)
-'#
-'# Copyright (c) 2006 Multimedia systems
-'# (c) and code by Luis Lavena
-'#
-'# mongrel_service (native) and mongrel_service gem_pluing are licensed
-'# in the same terms as mongrel, please review the mongrel license at
-'# http://mongrel.rubyforge.org/license.html
-'#
-'##################################################################
-
-'##################################################################
-'# Requirements:
-'# - FreeBASIC 0.18.
-'#
-'##################################################################
-
-#define SERVICEFB_INCLUDE_UTILS
-#include once "lib/ServiceFB/ServiceFB.bi"
-#include once "console_process.bi"
-
-'# use for debug versions
-#if not defined(GEM_VERSION)
- #define GEM_VERSION (debug mode)
-#endif
-
-'# preprocessor stringize
-#define PPSTR(x) #x
-
-namespace mongrel_service
- const VERSION as string = PPSTR(GEM_VERSION)
-
- '# namespace include
- using fb.svc
- using fb.svc.utils
-
- declare function single_onInit(byref as ServiceProcess) as integer
- declare sub single_onStart(byref as ServiceProcess)
- declare sub single_onStop(byref as ServiceProcess)
-
- '# SingleMongrel
- type SingleMongrel
- declare constructor()
- declare destructor()
-
- '# TODO: replace for inheritance here
- 'declare function onInit() as integer
- 'declare sub onStart()
- 'declare sub onStop()
-
- __service as ServiceProcess
- __console as ConsoleProcess
- __child_pid as uinteger
- end type
-
- '# TODO: replace with inheritance here
- dim shared single_mongrel_ref as SingleMongrel ptr
-end namespace
|