Открыть боковую панель
Aurora OS
Kotlin Multiplatform
QtBindings
Коммиты
2705ec3b
Коммит
2705ec3b
создал
Мар 25, 2025
по автору
Ilya Pankratov
Просмотр файлов
Implement Qt runtime
It helps to use QFuture as coroutine result
владелец
292161a3
Изменения
9
Скрыть пробелы
Построчно
Рядом
.clang-format
0 → 100644
Просмотр файла @
2705ec3b
# Based on: https://github.com/qt-creator/qt-creator/blob/master/.clang-format
---
Language: Cpp
AccessModifierOffset: -4
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: None
AlignConsecutiveDeclarations: None
AlignEscapedNewlines: Left
AlignOperands: true
AlignTrailingComments: true
AllowAllArgumentsOnNextLine: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: Empty
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: None
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterReturnType: None
AlwaysBreakBeforeMultilineStrings: false
AlwaysBreakTemplateDeclarations: Yes
BinPackArguments: false
BinPackParameters: false
BraceWrapping:
AfterClass: true
AfterControlStatement: Never
AfterEnum: false
AfterFunction: true
AfterNamespace: false
AfterObjCDeclaration: false
AfterStruct: true
AfterUnion: false
BeforeCatch: true
BeforeElse: false
BeforeLambdaBody: false
IndentBraces: false
SplitEmptyFunction: false
SplitEmptyRecord: false
SplitEmptyNamespace: false
BreakBeforeBinaryOperators: All
BreakBeforeBraces: Custom
BreakBeforeInheritanceComma: false
BreakBeforeTernaryOperators: true
BreakConstructorInitializers: BeforeComma
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: true
ColumnLimit: 100
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: false
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: false
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: false
IncludeIsMainRegex: '(Test)?$'
IndentCaseLabels: false
IndentWidth: 4
IndentWrappedFunctionNames: false
InsertBraces: true
JavaScriptQuotes: Leave
JavaScriptWrapImports: true
KeepEmptyLinesAtTheStartOfBlocks: false
LambdaBodyIndentation: OuterScope
MacroBlockBegin: ""
MacroBlockEnd: ""
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
ObjCBlockIndentWidth: 4
ObjCSpaceAfterProperty: false
ObjCSpaceBeforeProtocolList: true
PackConstructorInitializers: Never
PenaltyBreakAssignment: 150
PenaltyBreakBeforeFirstCallParameter: 300
PenaltyBreakComment: 500
PenaltyBreakFirstLessLess: 400
PenaltyBreakString: 600
PenaltyExcessCharacter: 50
PenaltyReturnTypeOnItsOwnLine: 300
PointerAlignment: Right
ReflowComments: false
SortIncludes: true
SortUsingDeclarations: true
SpaceAfterCStyleCast: true
SpaceAfterTemplateKeyword: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeCtorInitializerColon: false
SpaceBeforeInheritanceColon: false
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: false
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 1
SpacesInAngles: false
SpacesInContainerLiterals: false
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: c++17
TabWidth: 4
UseTab: Never
qtbindings-ksp/src/main/resources/ru/aurora/kmp/qtbindings/CallbackContext.cpp
0 → 100644
Просмотр файла @
2705ec3b
/**
* SPDX-FileCopyrightText: Copyright 2025 Open Mobile Platform LLC <community@omp.ru>
* SPDX-License-Identifier: BSD-3-Clause
*/
#include
"CallbackContext.hpp"
using
CallbackContext
=
Aurora
::
Kmp
::
QtBindings
::
CallbackContext
;
using
OnResultCallback
=
Aurora
::
Kmp
::
QtBindings
::
OnResultCallback
;
using
OnErrorCallback
=
Aurora
::
Kmp
::
QtBindings
::
OnErrorCallback
;
using
OnCancelledCallback
=
Aurora
::
Kmp
::
QtBindings
::
OnCancelledCallback
;
extern
"C"
{
void
onResult
(
void
*
context
,
void
*
result
)
{
auto
callback
=
static_cast
<
CallbackContext
*>
(
context
)
->
onResultCallback
;
callback
(
result
);
}
void
onError
(
void
*
context
,
char
*
errorMessage
)
{
auto
callback
=
static_cast
<
CallbackContext
*>
(
context
)
->
onErrorCallback
;
callback
(
errorMessage
);
}
void
onCancelled
(
void
*
context
,
char
*
errorMessage
)
{
auto
callback
=
static_cast
<
CallbackContext
*>
(
context
)
->
onCancelledCallback
;
callback
(
errorMessage
);
}
}
qtbindings-ksp/src/main/resources/ru/aurora/kmp/qtbindings/CallbackContext.hpp
0 → 100644
Просмотр файла @
2705ec3b
/**
* SPDX-FileCopyrightText: Copyright 2025 Open Mobile Platform LLC <community@omp.ru>
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef RU_AURORA_KMP_QT_BINDINGS_CALLBACK_CONTEXT_HPP
#define RU_AURORA_KMP_QT_BINDINGS_CALLBACK_CONTEXT_HPP
#include
<functional>
extern
"C"
{
void
onResult
(
void
*
context
,
void
*
result
);
void
onError
(
void
*
context
,
char
*
errorMessage
);
void
onCancelled
(
void
*
context
,
char
*
errorMessage
);
}
namespace
Aurora
{
namespace
Kmp
{
namespace
QtBindings
{
using
OnResultCallback
=
std
::
function
<
void
(
void
*
/* Result */
)
>
;
using
OnErrorCallback
=
std
::
function
<
void
(
char
*
/* String */
)
>
;
using
OnCancelledCallback
=
std
::
function
<
void
(
char
*
/* String */
)
>
;
struct
CallbackContext
{
OnResultCallback
onResultCallback
;
OnErrorCallback
onErrorCallback
;
OnCancelledCallback
onCancelledCallback
;
};
}
/* namespace Aurora */
}
/* namespace Kmp */
}
/* namespace QtBindings */
#endif
/* RU_AURORA_KMP_QT_BINDINGS_CALLBACK_CONTEXT_HPP */
qtbindings-ksp/src/main/resources/ru/aurora/kmp/qtbindings/CoroutineException.cpp
0 → 100644
Просмотр файла @
2705ec3b
/**
* SPDX-FileCopyrightText: Copyright 2025 Open Mobile Platform LLC <community@omp.ru>
* SPDX-License-Identifier: BSD-3-Clause
*/
#include
"CoroutineException.hpp"
namespace
Aurora
{
namespace
Kmp
{
namespace
QtBindings
{
CoroutineException
::
CoroutineException
(
const
QString
&
message
)
:
m_message
(
message
)
{}
CoroutineException
::
CoroutineException
(
const
CoroutineException
&
other
)
:
QException
(
other
)
,
m_message
(
other
.
m_message
)
{}
void
CoroutineException
::
raise
()
const
{
throw
*
this
;
}
CoroutineException
*
CoroutineException
::
clone
()
const
{
return
new
CoroutineException
(
*
this
);
}
QString
CoroutineException
::
message
()
const
{
return
m_message
;
}
}
/* namespace Aurora */
}
/* namespace Kmp */
}
/* namespace QtBindings */
qtbindings-ksp/src/main/resources/ru/aurora/kmp/qtbindings/CoroutineException.hpp
0 → 100644
Просмотр файла @
2705ec3b
/**
* SPDX-FileCopyrightText: Copyright 2025 Open Mobile Platform LLC <community@omp.ru>
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef RU_AURORA_KMP_QT_BINDINGS_EXCEPTION_HPP
#define RU_AURORA_KMP_QT_BINDINGS_EXCEPTION_HPP
#include
<QException>
namespace
Aurora
{
namespace
Kmp
{
namespace
QtBindings
{
class
CoroutineException
:
public
QException
{
public:
explicit
CoroutineException
(
const
QString
&
message
);
virtual
~
CoroutineException
()
=
default
;
CoroutineException
(
const
CoroutineException
&
other
);
void
raise
()
const
override
;
CoroutineException
*
clone
()
const
override
;
QString
message
()
const
;
private:
QString
m_message
;
};
}
/* namespace Aurora */
}
/* namespace Kmp */
}
/* namespace QtBindings */
#endif
/* RU_AURORA_KMP_QT_BINDINGS_EXCEPTION_HPP */
qtbindings-ksp/src/main/resources/ru/aurora/kmp/qtbindings/CoroutineLauncher.cpp
0 → 100644
Просмотр файла @
2705ec3b
/**
* SPDX-FileCopyrightText: Copyright 2025 Open Mobile Platform LLC <community@omp.ru>
* SPDX-License-Identifier: BSD-3-Clause
*/
#include
"CoroutineLauncher.hpp"
namespace
Aurora
{
namespace
Kmp
{
namespace
QtBindings
{
CoroutineLauncher
::
CoroutineLauncher
(
KotlinCoroutineLauncher
*
launcher
)
:
d_ptr
(
launcher
)
{}
CoroutineLauncher
::
CoroutineLauncher
(
CoroutineLauncher
&&
other
)
{
d_ptr
=
other
.
d_ptr
;
other
.
d_ptr
=
nullptr
;
}
CoroutineLauncher
::~
CoroutineLauncher
()
{
if
(
d_ptr
)
{
d_ptr
->
free
(
d_ptr
);
}
}
CoroutineLauncher
&
CoroutineLauncher
::
operator
=
(
CoroutineLauncher
&&
other
)
{
if
(
this
!=
&
other
)
{
this
->
d_ptr
=
other
.
d_ptr
;
other
.
d_ptr
=
nullptr
;
return
*
this
;
}
return
*
this
;
}
CoroutineLauncher
::
CancelableCoroutine
CoroutineLauncher
::
launch
(
CallbackContext
*
context
,
COnResultCallback
onResult
,
COnErrorCallback
onError
,
COnCancelledCallback
onCancelled
)
{
return
CancelableCoroutine
{
d_ptr
->
coroutineLauncherFunc
((
void
*
)
context
,
d_ptr
->
kotlinContext
,
onResult
,
onError
,
onCancelled
)};
}
CoroutineLauncher
::
CancelableCoroutine
::
CancelableCoroutine
(
CCancellableCoroutine
*
coroutine
)
:
d_ptr
(
coroutine
)
{}
CoroutineLauncher
::
CancelableCoroutine
::
CancelableCoroutine
(
CancelableCoroutine
&&
other
)
{
d_ptr
=
other
.
d_ptr
;
other
.
d_ptr
=
nullptr
;
}
CoroutineLauncher
::
CancelableCoroutine
::~
CancelableCoroutine
()
{
if
(
d_ptr
)
{
d_ptr
->
free
(
d_ptr
);
}
}
CoroutineLauncher
::
CancelableCoroutine
&
CoroutineLauncher
::
CancelableCoroutine
::
operator
=
(
CancelableCoroutine
&&
other
)
{
if
(
this
!=
&
other
)
{
this
->
d_ptr
=
other
.
d_ptr
;
other
.
d_ptr
=
nullptr
;
return
*
this
;
}
return
*
this
;
}
void
CoroutineLauncher
::
CancelableCoroutine
::
cancel
()
{
d_ptr
->
cancel
(
d_ptr
);
}
}
/* namespace Aurora */
}
/* namespace Kmp */
}
/* namespace QtBindings */
qtbindings-ksp/src/main/resources/ru/aurora/kmp/qtbindings/CoroutineLauncher.hpp
0 → 100644
Просмотр файла @
2705ec3b
/**
* SPDX-FileCopyrightText: Copyright 2025 Open Mobile Platform LLC <community@omp.ru>
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef RU_AURORA_KMP_QT_BINDINGS_COROUTINE_LAUNCHER_HPP
#define RU_AURORA_KMP_QT_BINDINGS_COROUTINE_LAUNCHER_HPP
#include
"CallbackContext.hpp"
#include
"cruntime.h"
namespace
Aurora
{
namespace
Kmp
{
namespace
QtBindings
{
struct
CoroutineLauncher
{
CoroutineLauncher
(
KotlinCoroutineLauncher
*
launcher
);
CoroutineLauncher
(
const
CoroutineLauncher
&
other
)
=
delete
;
CoroutineLauncher
(
CoroutineLauncher
&&
other
);
virtual
~
CoroutineLauncher
();
CoroutineLauncher
&
operator
=
(
const
CoroutineLauncher
&
other
)
=
delete
;
CoroutineLauncher
&
operator
=
(
CoroutineLauncher
&&
other
);
struct
CancelableCoroutine
{
public:
CancelableCoroutine
(
CCancellableCoroutine
*
coroutine
);
CancelableCoroutine
(
const
CancelableCoroutine
&
other
)
=
delete
;
CancelableCoroutine
(
CancelableCoroutine
&&
other
);
virtual
~
CancelableCoroutine
();
CancelableCoroutine
&
operator
=
(
const
CancelableCoroutine
&
other
)
=
delete
;
CancelableCoroutine
&
operator
=
(
CancelableCoroutine
&&
other
);
void
cancel
();
private:
CCancellableCoroutine
*
d_ptr
;
};
CancelableCoroutine
launch
(
CallbackContext
*
context
,
COnResultCallback
onResult
,
COnErrorCallback
onError
,
COnCancelledCallback
onCancelled
);
private:
KotlinCoroutineLauncher
*
d_ptr
;
};
}
/* namespace Aurora */
}
/* namespace Kmp */
}
/* namespace QtBindings */
#endif
/* RU_AURORA_KMP_QT_BINDINGS_COROUTINE_LAUNCHER_HPP */
qtbindings-ksp/src/main/resources/ru/aurora/kmp/qtbindings/CoroutineOperation.hpp
0 → 100644
Просмотр файла @
2705ec3b
/**
* SPDX-FileCopyrightText: Copyright 2025 Open Mobile Platform LLC <community@omp.ru>
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef RU_AURORA_KMP_QT_BINDINGS_COROUTINE_OPERATION_HPP
#define RU_AURORA_KMP_QT_BINDINGS_COROUTINE_OPERATION_HPP
#include
<functional>
#include
<optional>
#include
<QFuture>
#include
<QFutureWatcher>
#include
"CallbackContext.hpp"
#include
"CoroutineException.hpp"
#include
"CoroutineLauncher.hpp"
namespace
Aurora
{
namespace
Kmp
{
namespace
QtBindings
{
template
<
typename
T
>
using
ResultTransformer
=
std
::
function
<
T
(
void
*
)
>
;
template
<
typename
T
>
std
::
enable_if_t
<!
std
::
is_void_v
<
T
>
,
QFuture
<
T
>>
coroutine
(
CoroutineLauncher
launcher
,
const
ResultTransformer
<
T
>
&
transformer
);
template
<
typename
T
>
std
::
enable_if_t
<
std
::
is_void_v
<
T
>
,
QFuture
<
T
>>
coroutine
(
CoroutineLauncher
launcher
);
template
<
typename
T
>
class
CoroutineOperation
{
public:
virtual
~
CoroutineOperation
()
=
default
;
private:
friend
QFuture
<
T
>
coroutine
<
T
>
(
CoroutineLauncher
,
const
ResultTransformer
<
T
>
&
);
CoroutineOperation
(
CoroutineLauncher
launcher
)
:
m_launcher
(
std
::
move
(
launcher
))
,
m_callbackContext
{
nullptr
,
[
this
](
char
*
message
)
{
finishedWithError
(
QString
::
fromUtf8
(
message
));
},
// TODO: Do we need to call m_futureInterface.reportCancelled()?
[](
char
*
)
{}}
{
auto
watcher
=
new
QFutureWatcher
<
T
>
();
watcher
->
setFuture
(
future
());
QObject
::
connect
(
watcher
,
&
QFutureWatcher
<
T
>::
canceled
,
[
=
,
this
]()
{
if
(
m_cancellableCoroutine
)
{
m_cancellableCoroutine
->
cancel
();
m_cancellableCoroutine
.
reset
();
}
});
QObject
::
connect
(
watcher
,
&
QFutureWatcher
<
T
>::
finished
,
[
watcher
]()
{
watcher
->
deleteLater
();
});
}
QFuture
<
T
>
launch
(
const
ResultTransformer
<
T
>
&
transformer
)
{
m_callbackContext
.
onResultCallback
=
[
this
,
transformer
](
void
*
result
)
{
auto
transformedResult
=
transformer
(
result
);
finishedWithResult
(
&
transformedResult
);
};
m_futureInterface
.
reportStarted
();
m_cancellableCoroutine
=
m_launcher
.
launch
(
&
m_callbackContext
,
&
onResult
,
&
onError
,
&
onCancelled
);
return
future
();
}
void
finishedWithResult
(
const
T
*
result
)
{
// If Qfuture has been cancelled, reportFinished will do nothing
m_futureInterface
.
reportFinished
(
result
);
}
void
finishedWithError
(
const
QString
&
error
)
{
// If Qfuture has been cancelled, reportException, reportFinished will do nothing
m_futureInterface
.
reportException
(
CoroutineException
(
error
));
m_futureInterface
.
reportFinished
();
}
QFuture
<
T
>
future
()
{
return
m_futureInterface
.
future
();
}
CoroutineLauncher
m_launcher
;
std
::
optional
<
CoroutineLauncher
::
CancelableCoroutine
>
m_cancellableCoroutine
;
CallbackContext
m_callbackContext
;
// We use the undocumented (but not explicitly marked as private) QFutureInterface class
// because that's the only way to manipulate a QFuture's status
QFutureInterface
<
T
>
m_futureInterface
;
};
template
<
>
class
CoroutineOperation
<
void
>
{
public:
virtual
~
CoroutineOperation
()
=
default
;
private:
friend
QFuture
<
void
>
coroutine
<
void
>
(
CoroutineLauncher
launcher
);
CoroutineOperation
(
CoroutineLauncher
launcher
)
:
m_launcher
(
std
::
move
(
launcher
))
,
m_callbackContext
{
nullptr
,
[
this
](
char
*
message
)
{
finishedWithError
(
QString
::
fromUtf8
(
message
));
},
// TODO: Do we need to call m_futureInterface.reportCancelled()?
[](
char
*
)
{}}
{
auto
watcher
=
new
QFutureWatcher
<
void
>
();
watcher
->
setFuture
(
future
());
QObject
::
connect
(
watcher
,
&
QFutureWatcher
<
void
>::
canceled
,
[
=
]()
{
if
(
m_cancellableCoroutine
)
{
m_cancellableCoroutine
->
cancel
();
m_cancellableCoroutine
.
reset
();
}
});
QObject
::
connect
(
watcher
,
&
QFutureWatcher
<
void
>::
finished
,
[
=
]()
{
watcher
->
deleteLater
();
});
}
QFuture
<
void
>
launch
()
{
m_callbackContext
.
onResultCallback
=
[
this
](
void
*
)
{
finishedWithResult
();
};
m_futureInterface
.
reportStarted
();
m_cancellableCoroutine
=
m_launcher
.
launch
(
&
m_callbackContext
,
&
onResult
,
&
onError
,
&
onCancelled
);
return
future
();
}
void
finishedWithResult
()
{
// If Qfuture has been cancelled, reportFinished will do nothing
m_futureInterface
.
reportFinished
();
}
void
finishedWithError
(
const
QString
&
error
)
{
// If Qfuture has been cancelled, reportException, reportFinished will do nothing
m_futureInterface
.
reportException
(
CoroutineException
(
error
));
m_futureInterface
.
reportFinished
();
}
QFuture
<
void
>
future
()
{
return
m_futureInterface
.
future
();
}
CoroutineLauncher
m_launcher
;
std
::
optional
<
CoroutineLauncher
::
CancelableCoroutine
>
m_cancellableCoroutine
;
CallbackContext
m_callbackContext
;
// We use the undocumented (but not explicitly marked as private) QFutureInterface class
// because that's the only way to manipulate a QFuture's status
QFutureInterface
<
void
>
m_futureInterface
;
};
template
<
typename
T
>
std
::
enable_if_t
<!
std
::
is_void_v
<
T
>
,
QFuture
<
T
>>
coroutine
(
CoroutineLauncher
launcher
,
const
ResultTransformer
<
T
>
&
transformer
)
{
auto
op
=
new
CoroutineOperation
<
T
>
(
std
::
move
(
launcher
));
auto
watcher
=
new
QFutureWatcher
<
T
>
();
watcher
->
setFuture
(
op
->
future
());
QObject
::
connect
(
watcher
,
&
QFutureWatcher
<
T
>::
finished
,
[
=
]()
{
watcher
->
deleteLater
();
delete
op
;
});
return
op
->
launch
(
transformer
);
}
template
<
typename
T
>
std
::
enable_if_t
<
std
::
is_void_v
<
T
>
,
QFuture
<
T
>>
coroutine
(
CoroutineLauncher
launcher
)
{
auto
op
=
new
CoroutineOperation
<
T
>
(
std
::
move
(
launcher
));
auto
watcher
=
new
QFutureWatcher
<
T
>
();
watcher
->
setFuture
(
op
->
future
());
QObject
::
connect
(
watcher
,
&
QFutureWatcher
<
T
>::
finished
,
[
=
]()
{
watcher
->
deleteLater
();
delete
op
;
});
return
op
->
launch
();
}
}
/* namespace Aurora */
}
/* namespace Kmp */
}
/* namespace QtBindings */
#endif
/* RU_AURORA_KMP_QT_BINDINGS_COROUTINE_OPERATION_HPP */
qtbindings-ksp/src/main/resources/ru/aurora/kmp/qtbindings/cruntime.h
0 → 100644
Просмотр файла @
2705ec3b
/**
* SPDX-FileCopyrightText: Copyright 2025 Open Mobile Platform LLC <community@omp.ru>
* SPDX-License-Identifier: BSD-3-Clause
*/
#ifndef RU_AURORA_KMP_QT_BINDINGS_C_RUNTIME_H
#define RU_AURORA_KMP_QT_BINDINGS_C_RUNTIME_H
#ifdef __cplusplus
extern
"C"
{
#endif
struct
CCancellableCoroutine
;
typedef
void
(
*
COnResultCallback
)(
void
*
cContext
,
void
*
result
);
typedef
void
(
*
COnStringCallback
)(
void
*
cContext
,
char
*
message
);
typedef
COnStringCallback
COnErrorCallback
;
typedef
COnStringCallback
COnCancelledCallback
;
typedef
void
(
*
CCancelCoroutine
)(
struct
CCancellableCoroutine
*
coroutine
);
typedef
struct
CCancellableCoroutine
{
void
*
coroutine
;
CCancelCoroutine
cancel
;
void
(
*
free
)(
struct
CCancellableCoroutine
*
thiz
);
}
CCancellableCoroutine
;
typedef
struct
KotlinCoroutineLauncher
{
void
*
kotlinContext
;
CCancellableCoroutine
*
(
*
coroutineLauncherFunc
)(
void
*
cContext
,
void
*
kotlinContext
,
COnResultCallback
onResult
,
COnErrorCallback
onError
,
COnCancelledCallback
onCancelled
);
void
(
*
free
)(
struct
KotlinCoroutineLauncher
*
thiz
);
}
KotlinCoroutineLauncher
;
#ifdef __cplusplus
}
#endif
#endif
/* RU_AURORA_KMP_QT_BINDINGS_C_RUNTIME_H */
Редактирование
Предварительный просмотр
Поддерживает Markdown
0%
Попробовать снова
или
прикрепить новый файл
.
Отмена
You are about to add
0
people
to the discussion. Proceed with caution.
Сначала завершите редактирование этого сообщения!
Отмена
Пожалуйста,
зарегистрируйтесь
или
войдите
чтобы прокомментировать