Commit c6b80124 authored by Ken Rockot's avatar Ken Rockot Committed by Commit Bot

Migrate cloud print proxy code to public Mojo APIs

Moves cloud print proxy and service process code off of Mojo EDK
APIs and onto new equivalent public Mojo APIs. No functional changes.

Bug: 844763
Change-Id: I8e0fe9cb0bf9a5daff1b63d7c7256e0929a5f540
Test: Build Chrome-branded official mini_installer on Windows and verify that when installed, the cloud print service can log in and manage cloud printers via the proxy.
Reviewed-on: https://chromium-review.googlesource.com/1100166
Commit-Queue: Ken Rockot <rockot@chromium.org>
Reviewed-by: default avatarScott Violet <sky@chromium.org>
Cr-Commit-Position: refs/heads/master@{#567375}
parent cc515750
......@@ -53,10 +53,9 @@
#include "ipc/ipc_channel_mojo.h"
#include "ipc/ipc_channel_proxy.h"
#include "mojo/edk/embedder/embedder.h"
#include "mojo/edk/embedder/named_platform_handle.h"
#include "mojo/edk/embedder/named_platform_handle_utils.h"
#include "mojo/edk/embedder/peer_connection.h"
#include "mojo/edk/embedder/scoped_ipc_support.h"
#include "mojo/public/cpp/platform/named_platform_channel.h"
#include "mojo/public/cpp/system/isolated_connection.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/multiprocess_func_list.h"
......@@ -103,17 +102,15 @@ class TestStartupClientChannelListener : public IPC::Listener {
};
void ConnectAsync(mojo::ScopedMessagePipeHandle handle,
mojo::edk::NamedPlatformHandle os_pipe,
mojo::edk::PeerConnection* peer_connection) {
mojo::edk::ScopedInternalPlatformHandle os_pipe_handle =
mojo::edk::CreateClientHandle(os_pipe);
if (!os_pipe_handle.is_valid())
mojo::NamedPlatformChannel::ServerName server_name,
mojo::IsolatedConnection* mojo_connection) {
mojo::PlatformChannelEndpoint endpoint =
mojo::NamedPlatformChannel::ConnectToServer(server_name);
if (!endpoint.is_valid())
return;
mojo::FuseMessagePipes(
peer_connection->Connect(mojo::edk::ConnectionParams(
mojo::edk::TransportProtocol::kLegacy, std::move(os_pipe_handle))),
std::move(handle));
mojo::FuseMessagePipes(mojo_connection->Connect(std::move(endpoint)),
std::move(handle));
}
const char kProcessChannelID[] = "process-channel-id";
......@@ -245,17 +242,14 @@ int CloudPrintMockService_Main(SetExpectationsCallback set_expectations) {
TestStartupClientChannelListener listener;
EXPECT_TRUE(base::CommandLine::ForCurrentProcess()->HasSwitch(
kProcessChannelID));
std::string startup_channel_name =
auto server_name = mojo::NamedPlatformChannel::ServerNameFromUTF8(
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
kProcessChannelID);
mojo::edk::PeerConnection peer_connection;
kProcessChannelID));
mojo::IsolatedConnection mojo_connection;
std::unique_ptr<IPC::ChannelProxy> startup_channel =
IPC::ChannelProxy::Create(
peer_connection
.Connect(mojo::edk::ConnectionParams(
mojo::edk::TransportProtocol::kLegacy,
mojo::edk::CreateClientHandle(
mojo::edk::NamedPlatformHandle(startup_channel_name))))
mojo_connection
.Connect(mojo::NamedPlatformChannel::ConnectToServer(server_name))
.release(),
IPC::Channel::MODE_CLIENT, &listener,
service_process.io_task_runner(),
......@@ -291,7 +285,7 @@ class CloudPrintProxyPolicyStartupTest : public base::MultiProcessTest,
return BrowserThread::GetTaskRunnerForThread(BrowserThread::IO);
}
base::Process Launch(const std::string& name);
void WaitForConnect(mojo::edk::PeerConnection* peer_connection);
void WaitForConnect(mojo::IsolatedConnection* mojo_connection);
void ShutdownAndWaitForExitWithTimeout(base::Process process);
// IPC::Listener implementation
......@@ -312,8 +306,8 @@ class CloudPrintProxyPolicyStartupTest : public base::MultiProcessTest,
content::TestBrowserThreadBundle thread_bundle_;
base::ScopedTempDir temp_user_data_dir_;
mojo::edk::NamedPlatformHandle startup_channel_handle_;
mojo::edk::PeerConnection peer_connection_;
mojo::NamedPlatformChannel::ServerName startup_server_name_;
mojo::IsolatedConnection mojo_connection_;
std::unique_ptr<IPC::ChannelProxy> startup_channel_;
std::unique_ptr<ChromeContentClient> content_client_;
std::unique_ptr<ChromeContentBrowserClient> browser_content_client_;
......@@ -413,15 +407,15 @@ base::Process CloudPrintProxyPolicyStartupTest::Launch(
const std::string& name) {
EXPECT_FALSE(CheckServiceProcessReady());
startup_channel_handle_ = mojo::edk::NamedPlatformHandle(
startup_server_name_ = mojo::NamedPlatformChannel::ServerNameFromUTF8(
base::StringPrintf("%" CrPRIdPid ".%p.%d", base::GetCurrentProcId(), this,
base::RandInt(0, std::numeric_limits<int>::max())));
mojo::NamedPlatformChannel::Options options;
options.server_name = startup_server_name_;
mojo::NamedPlatformChannel channel_server(options);
startup_channel_ = IPC::ChannelProxy::Create(
peer_connection_
.Connect(mojo::edk::ConnectionParams(
mojo::edk::TransportProtocol::kLegacy,
mojo::edk::CreateServerHandle(startup_channel_handle_)))
.release(),
mojo_connection_.Connect(channel_server.TakeServerEndpoint()).release(),
IPC::Channel::MODE_SERVER, this, IOTaskRunner(),
base::ThreadTaskRunnerHandle::Get());
......@@ -431,7 +425,7 @@ base::Process CloudPrintProxyPolicyStartupTest::Launch(
}
void CloudPrintProxyPolicyStartupTest::WaitForConnect(
mojo::edk::PeerConnection* peer_connection) {
mojo::IsolatedConnection* mojo_connection) {
observer_.Wait();
EXPECT_TRUE(CheckServiceProcessReady());
EXPECT_TRUE(base::ThreadTaskRunnerHandle::Get().get());
......@@ -440,7 +434,7 @@ void CloudPrintProxyPolicyStartupTest::WaitForConnect(
base::PostTaskWithTraits(
FROM_HERE, {base::MayBlock(), base::TaskPriority::BACKGROUND},
base::BindOnce(&ConnectAsync, std::move(pipe.handle1),
GetServiceProcessChannel(), peer_connection));
GetServiceProcessServerName(), mojo_connection));
ServiceProcessControl::GetInstance()->SetMojoHandle(
mojo::MakeProxy(service_manager::mojom::InterfaceProviderPtrInfo(
std::move(pipe.handle0), 0U)));
......@@ -467,7 +461,7 @@ void CloudPrintProxyPolicyStartupTest::OnChannelConnected(int32_t peer_pid) {
base::CommandLine CloudPrintProxyPolicyStartupTest::MakeCmdLine(
const std::string& procname) {
base::CommandLine cl = MultiProcessTest::MakeCmdLine(procname);
cl.AppendSwitchNative(kProcessChannelID, startup_channel_handle_.name);
cl.AppendSwitchNative(kProcessChannelID, startup_server_name_);
#if defined(OS_MACOSX)
cl.AppendSwitchASCII(kTestExecutablePath, executable_path_.value());
#endif
......@@ -482,8 +476,8 @@ TEST_F(CloudPrintProxyPolicyStartupTest, StartAndShutdown) {
base::Process process =
Launch("CloudPrintMockService_StartEnabledWaitForQuit");
mojo::edk::PeerConnection peer_connection;
WaitForConnect(&peer_connection);
mojo::IsolatedConnection mojo_connection;
WaitForConnect(&mojo_connection);
ShutdownAndWaitForExitWithTimeout(std::move(process));
ServiceProcessControl::GetInstance()->Disconnect();
content::RunAllPendingInMessageLoop();
......
......@@ -29,10 +29,8 @@
#include "chrome/common/service_process_util.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/child_process_launcher_utils.h"
#include "mojo/edk/embedder/embedder.h"
#include "mojo/edk/embedder/named_platform_handle.h"
#include "mojo/edk/embedder/named_platform_handle_utils.h"
#include "mojo/edk/embedder/peer_connection.h"
#include "mojo/public/cpp/platform/named_platform_channel.h"
#include "mojo/public/cpp/system/isolated_connection.h"
using content::BrowserThread;
......@@ -48,15 +46,15 @@ constexpr base::TimeDelta kInitialConnectionRetryDelay =
void ConnectAsyncWithBackoff(
service_manager::mojom::InterfaceProviderRequest interface_provider_request,
mojo::edk::NamedPlatformHandle os_pipe,
mojo::NamedPlatformChannel::ServerName server_name,
size_t num_retries_left,
base::TimeDelta retry_delay,
scoped_refptr<base::TaskRunner> response_task_runner,
base::OnceCallback<void(std::unique_ptr<mojo::edk::PeerConnection>)>
base::OnceCallback<void(std::unique_ptr<mojo::IsolatedConnection>)>
response_callback) {
mojo::edk::ScopedInternalPlatformHandle os_pipe_handle =
mojo::edk::CreateClientHandle(os_pipe);
if (!os_pipe_handle.is_valid()) {
mojo::PlatformChannelEndpoint endpoint =
mojo::NamedPlatformChannel::ConnectToServer(server_name);
if (!endpoint.is_valid()) {
if (num_retries_left == 0) {
response_task_runner->PostTask(
FROM_HERE, base::BindOnce(std::move(response_callback), nullptr));
......@@ -65,19 +63,17 @@ void ConnectAsyncWithBackoff(
FROM_HERE, {base::MayBlock(), base::TaskPriority::BACKGROUND},
base::BindOnce(
&ConnectAsyncWithBackoff, std::move(interface_provider_request),
std::move(os_pipe), num_retries_left - 1, retry_delay * 2,
server_name, num_retries_left - 1, retry_delay * 2,
std::move(response_task_runner), std::move(response_callback)),
retry_delay);
}
} else {
auto peer_connection = std::make_unique<mojo::edk::PeerConnection>();
mojo::FuseMessagePipes(
peer_connection->Connect(mojo::edk::ConnectionParams(
mojo::edk::TransportProtocol::kLegacy, std::move(os_pipe_handle))),
interface_provider_request.PassMessagePipe());
auto mojo_connection = std::make_unique<mojo::IsolatedConnection>();
mojo::FuseMessagePipes(mojo_connection->Connect(std::move(endpoint)),
interface_provider_request.PassMessagePipe());
response_task_runner->PostTask(FROM_HERE,
base::BindOnce(std::move(response_callback),
std::move(peer_connection)));
std::move(mojo_connection)));
}
}
......@@ -111,16 +107,16 @@ void ServiceProcessControl::ConnectInternal() {
FROM_HERE, {base::MayBlock(), base::TaskPriority::BACKGROUND},
base::BindOnce(
&ConnectAsyncWithBackoff, std::move(interface_provider_request),
GetServiceProcessChannel(), kMaxConnectionAttempts,
GetServiceProcessServerName(), kMaxConnectionAttempts,
kInitialConnectionRetryDelay, base::ThreadTaskRunnerHandle::Get(),
base::BindOnce(&ServiceProcessControl::OnPeerConnectionComplete,
weak_factory_.GetWeakPtr())));
}
void ServiceProcessControl::OnPeerConnectionComplete(
std::unique_ptr<mojo::edk::PeerConnection> peer_connection) {
std::unique_ptr<mojo::IsolatedConnection> connection) {
// Hold onto the connection object so the connection is kept alive.
peer_connection_ = std::move(peer_connection);
mojo_connection_ = std::move(connection);
}
void ServiceProcessControl::SetMojoHandle(
......@@ -201,7 +197,7 @@ void ServiceProcessControl::Launch(base::OnceClosure success_task,
void ServiceProcessControl::Disconnect() {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
peer_connection_.reset();
mojo_connection_.reset();
remote_interfaces_.Close();
service_process_.reset();
}
......
......@@ -33,9 +33,7 @@ class CommandLine;
}
namespace mojo {
namespace edk {
class PeerConnection;
}
class IsolatedConnection;
}
// A ServiceProcessControl works as a portal between the service process and
......@@ -176,14 +174,14 @@ class ServiceProcessControl : public UpgradeObserver {
// Called when ConnectInternal's async work is done.
void OnPeerConnectionComplete(
std::unique_ptr<mojo::edk::PeerConnection> connection);
std::unique_ptr<mojo::IsolatedConnection> connection);
// Split out for testing.
void SetMojoHandle(service_manager::mojom::InterfaceProviderPtr handle);
static void RunAllTasksHelper(TaskList* task_list);
std::unique_ptr<mojo::edk::PeerConnection> peer_connection_;
std::unique_ptr<mojo::IsolatedConnection> mojo_connection_;
service_manager::InterfaceProvider remote_interfaces_;
chrome::mojom::ServiceProcessPtr service_process_;
......
......@@ -97,7 +97,7 @@ MockLaunchd::MockLaunchd(
bool create_socket,
bool as_service)
: file_(file),
pipe_name_(GetServiceProcessChannel().name),
pipe_name_(GetServiceProcessServerName()),
main_task_runner_(std::move(main_task_runner)),
create_socket_(create_socket),
as_service_(as_service),
......
......@@ -279,8 +279,9 @@ bool ServiceProcessState::CreateSharedData() {
return true;
}
mojo::edk::NamedPlatformHandle ServiceProcessState::GetServiceProcessChannel() {
return ::GetServiceProcessChannel();
mojo::NamedPlatformChannel::ServerName
ServiceProcessState::GetServiceProcessServerName() {
return ::GetServiceProcessServerName();
}
#endif // !OS_MACOSX
......@@ -14,8 +14,8 @@
#include "base/process/process.h"
#include "base/single_thread_task_runner.h"
#include "build/build_config.h"
#include "mojo/edk/embedder/named_platform_handle.h"
#include "mojo/edk/embedder/scoped_platform_handle.h"
#include "mojo/public/cpp/platform/named_platform_channel.h"
#include "mojo/public/cpp/platform/platform_channel_server_endpoint.h"
class MultiProcessLock;
......@@ -32,7 +32,7 @@ class CommandLine;
}
// Return the IPC channel to connect to the service process.
mojo::edk::NamedPlatformHandle GetServiceProcessChannel();
mojo::NamedPlatformChannel::ServerName GetServiceProcessServerName();
// Return a name that is scoped to this instance of the service process. We
// use the user-data-dir as a scoping prefix.
......@@ -106,9 +106,9 @@ class ServiceProcessState {
// Return the channel handle used for communicating with the service.
#if defined(OS_MACOSX)
mojo::edk::ScopedInternalPlatformHandle GetServiceProcessChannel();
mojo::PlatformChannelServerEndpoint GetServiceProcessServerEndpoint();
#else
mojo::edk::NamedPlatformHandle GetServiceProcessChannel();
mojo::NamedPlatformChannel::ServerName GetServiceProcessServerName();
#endif
private:
......
......@@ -51,16 +51,13 @@ bool ForceServiceProcessShutdown(const std::string& version,
// Gets the name of the service process IPC channel.
// Returns an absolute path as required.
mojo::edk::NamedPlatformHandle GetServiceProcessChannel() {
mojo::NamedPlatformChannel::ServerName GetServiceProcessServerName() {
base::FilePath temp_dir;
base::PathService::Get(base::DIR_TEMP, &temp_dir);
std::string pipe_name = GetServiceProcessScopedVersionedName("_service_ipc");
std::string pipe_path = temp_dir.Append(pipe_name).value();
return mojo::edk::NamedPlatformHandle(pipe_path);
return temp_dir.Append(pipe_name).value();
}
bool CheckServiceProcessReady() {
std::unique_ptr<MultiProcessLock> running_lock(TakeServiceRunningLock(false));
return running_lock.get() == NULL;
......
......@@ -4,6 +4,7 @@
#import <Foundation/Foundation.h>
#include <launch.h>
#include <sys/un.h>
#include <memory>
#include <vector>
......@@ -27,7 +28,6 @@
#include "chrome/common/mac/launchd.h"
#include "chrome/common/service_process_util_posix.h"
#include "components/version_info/version_info.h"
#include "mojo/edk/embedder/named_platform_handle_utils.h"
using ::base::FilePathWatcher;
......@@ -86,16 +86,20 @@ base::FilePath GetServiceProcessSocketName() {
base::PathService::Get(base::DIR_TEMP, &socket_name);
std::string pipe_name = GetServiceProcessScopedName("srv");
socket_name = socket_name.Append(pipe_name);
CHECK_LT(socket_name.value().size(), mojo::edk::kMaxSocketNameLength);
// Max allowed on Mac.
constexpr size_t kMaxSocketNameLength = sizeof(sockaddr_un().sun_path);
CHECK_LT(socket_name.value().size(), kMaxSocketNameLength);
return socket_name;
}
} // namespace
mojo::edk::NamedPlatformHandle GetServiceProcessChannel() {
mojo::NamedPlatformChannel::ServerName GetServiceProcessServerName() {
base::FilePath socket_name = GetServiceProcessSocketName();
VLOG(1) << "ServiceProcessChannel: " << socket_name.value();
return mojo::edk::NamedPlatformHandle(socket_name.value());
return socket_name.value();
}
bool ForceServiceProcessShutdown(const std::string& /* version */,
......@@ -173,8 +177,8 @@ bool ServiceProcessState::Initialize() {
return true;
}
mojo::edk::ScopedInternalPlatformHandle
ServiceProcessState::GetServiceProcessChannel() {
mojo::PlatformChannelServerEndpoint
ServiceProcessState::GetServiceProcessServerEndpoint() {
DCHECK(state_);
NSDictionary* ns_launchd_conf = base::mac::CFToNSCast(state_->launchd_conf);
NSDictionary* socket_dict =
......@@ -183,8 +187,8 @@ ServiceProcessState::GetServiceProcessChannel() {
[socket_dict objectForKey:GetServiceProcessLaunchDSocketKey()];
DCHECK_EQ([sockets count], 1U);
int socket = [[sockets objectAtIndex:0] intValue];
return mojo::edk::ScopedInternalPlatformHandle(
mojo::edk::InternalPlatformHandle(socket));
return mojo::PlatformChannelServerEndpoint(
mojo::PlatformHandle(base::ScopedFD(socket)));
}
bool CheckServiceProcessReady() {
......
......@@ -4,6 +4,8 @@
#include "chrome/common/service_process_util.h"
#include <windows.h>
#include <algorithm>
#include <memory>
......@@ -80,8 +82,8 @@ class ServiceProcessTerminateMonitor
} // namespace
// Gets the name of the service process IPC channel.
mojo::edk::NamedPlatformHandle GetServiceProcessChannel() {
return mojo::edk::NamedPlatformHandle(
mojo::NamedPlatformChannel::ServerName GetServiceProcessServerName() {
return mojo::NamedPlatformChannel::ServerNameFromUTF8(
GetServiceProcessScopedVersionedName("_service_ipc"));
}
......
......@@ -46,10 +46,6 @@
#include "components/network_session_configurator/common/network_switches.h"
#include "components/prefs/json_pref_store.h"
#include "mojo/edk/embedder/embedder.h"
#include "mojo/edk/embedder/named_platform_handle.h"
#include "mojo/edk/embedder/named_platform_handle_utils.h"
#include "mojo/edk/embedder/peer_connection.h"
#include "mojo/edk/embedder/platform_handle_utils.h"
#include "mojo/edk/embedder/scoped_ipc_support.h"
#include "net/base/network_change_notifier.h"
#include "net/url_request/url_fetcher.h"
......@@ -328,34 +324,41 @@ bool ServiceProcess::OnIPCClientDisconnect() {
}
mojo::ScopedMessagePipeHandle ServiceProcess::CreateChannelMessagePipe() {
if (!server_handle_.is_valid()) {
#if defined(OS_MACOSX)
mojo::edk::InternalPlatformHandle platform_handle(
service_process_state_->GetServiceProcessChannel().release());
platform_handle.needs_connection = true;
server_handle_.reset(platform_handle);
if (!server_endpoint_.is_valid()) {
server_endpoint_ =
service_process_state_->GetServiceProcessServerEndpoint();
DCHECK(server_endpoint_.is_valid());
}
#elif defined(OS_POSIX)
server_handle_ = mojo::edk::CreateServerHandle(
service_process_state_->GetServiceProcessChannel());
if (!server_endpoint_.is_valid()) {
mojo::NamedPlatformChannel::Options options;
options.server_name = service_process_state_->GetServiceProcessServerName();
mojo::NamedPlatformChannel server_channel(options);
server_endpoint_ = server_channel.TakeServerEndpoint();
DCHECK(server_endpoint_.is_valid());
}
#elif defined(OS_WIN)
server_handle_ = service_process_state_->GetServiceProcessChannel();
#endif
DCHECK(server_handle_.is_valid());
if (server_name_.empty()) {
server_name_ = service_process_state_->GetServiceProcessServerName();
DCHECK(!server_name_.empty());
}
#endif
mojo::edk::ScopedInternalPlatformHandle channel_handle;
mojo::PlatformChannelServerEndpoint server_endpoint;
#if defined(OS_POSIX)
channel_handle = mojo::edk::DuplicatePlatformHandle(server_handle_.get());
server_endpoint = server_endpoint_.Clone();
#elif defined(OS_WIN)
mojo::edk::CreateServerHandleOptions options;
mojo::NamedPlatformChannel::Options options;
options.server_name = server_name_;
options.enforce_uniqueness = false;
channel_handle = mojo::edk::CreateServerHandle(server_handle_, options);
mojo::NamedPlatformChannel server_channel(options);
server_endpoint = server_channel.TakeServerEndpoint();
#endif
CHECK(channel_handle.is_valid());
CHECK(server_endpoint.is_valid());
peer_connection_ = std::make_unique<mojo::edk::PeerConnection>();
return peer_connection_->Connect(mojo::edk::ConnectionParams(
mojo::edk::TransportProtocol::kLegacy, std::move(channel_handle)));
mojo_connection_ = std::make_unique<mojo::IsolatedConnection>();
return mojo_connection_->Connect(std::move(server_endpoint));
}
cloud_print::CloudPrintProxy* ServiceProcess::GetCloudPrintProxy() {
......
......@@ -14,8 +14,9 @@
#include "base/threading/thread.h"
#include "chrome/service/cloud_print/cloud_print_proxy.h"
#include "chrome/service/service_ipc_server.h"
#include "mojo/edk/embedder/named_platform_handle.h"
#include "mojo/edk/embedder/scoped_platform_handle.h"
#include "mojo/public/cpp/platform/named_platform_channel.h"
#include "mojo/public/cpp/platform/platform_channel_server_endpoint.h"
#include "mojo/public/cpp/system/isolated_connection.h"
#include "mojo/public/cpp/system/message_pipe.h"
class ServiceProcessPrefs;
......@@ -28,8 +29,8 @@ class WaitableEvent;
}
namespace mojo {
class IsolatedConnection;
namespace edk {
class PeerConnection;
class ScopedIPCSupport;
}
}
......@@ -117,7 +118,7 @@ class ServiceProcess : public ServiceIPCServer::Client,
std::unique_ptr<ServiceIPCServer> ipc_server_;
std::unique_ptr<ServiceProcessState> service_process_state_;
std::unique_ptr<mojo::edk::ScopedIPCSupport> mojo_ipc_support_;
std::unique_ptr<mojo::edk::PeerConnection> peer_connection_;
std::unique_ptr<mojo::IsolatedConnection> mojo_connection_;
// An event that will be signalled when we shutdown.
base::WaitableEvent shutdown_event_;
......@@ -134,9 +135,9 @@ class ServiceProcess : public ServiceIPCServer::Client,
scoped_refptr<ServiceURLRequestContextGetter> request_context_getter_;
#if defined(OS_POSIX)
mojo::edk::ScopedInternalPlatformHandle server_handle_;
mojo::PlatformChannelServerEndpoint server_endpoint_;
#elif defined(OS_WIN)
mojo::edk::NamedPlatformHandle server_handle_;
mojo::NamedPlatformChannel::ServerName server_name_;
#endif
DISALLOW_COPY_AND_ASSIGN(ServiceProcess);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment