Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
erp5
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Levin Zimmermann
erp5
Commits
d25efe94
Commit
d25efe94
authored
Apr 06, 2022
by
Arnaud Fontaine
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
py3: _mysql.string_literal() returns bytes().
parent
246a589a
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
63 additions
and
62 deletions
+63
-62
product/CMFActivity/Activity/SQLBase.py
product/CMFActivity/Activity/SQLBase.py
+50
-50
product/CMFActivity/Activity/SQLDict.py
product/CMFActivity/Activity/SQLDict.py
+11
-11
product/CMFActivity/ActivityTool.py
product/CMFActivity/ActivityTool.py
+2
-1
No files found.
product/CMFActivity/Activity/SQLBase.py
View file @
d25efe94
...
@@ -110,14 +110,14 @@ def SQLLock(db, lock_name, timeout):
...
@@ -110,14 +110,14 @@ def SQLLock(db, lock_name, timeout):
"""
"""
lock_name
=
db
.
string_literal
(
lock_name
)
lock_name
=
db
.
string_literal
(
lock_name
)
query
=
db
.
query
query
=
db
.
query
(
_
,
((
acquired
,
),
))
=
query
(
'SELECT GET_LOCK(%s, %f)'
%
(
lock_name
,
timeout
))
(
_
,
((
acquired
,
),
))
=
query
(
b
'SELECT GET_LOCK(%s, %f)'
%
(
lock_name
,
timeout
))
if
acquired
is
None
:
if
acquired
is
None
:
raise
ValueError
(
'Error acquiring lock'
)
raise
ValueError
(
'Error acquiring lock'
)
try
:
try
:
yield
acquired
yield
acquired
finally
:
finally
:
if
acquired
:
if
acquired
:
query
(
'SELECT RELEASE_LOCK(%s)'
%
(
lock_name
,
))
query
(
b
'SELECT RELEASE_LOCK(%s)'
%
(
lock_name
,
))
# sqltest_dict ({'condition_name': <render_function>}) defines how to render
# sqltest_dict ({'condition_name': <render_function>}) defines how to render
# condition statements in the SQL query used by SQLBase.getMessageList
# condition statements in the SQL query used by SQLBase.getMessageList
def
sqltest_dict
():
def
sqltest_dict
():
...
@@ -161,13 +161,13 @@ def sqltest_dict():
...
@@ -161,13 +161,13 @@ def sqltest_dict():
assert
isinstance
(
priority
,
_SQLTEST_NO_QUOTE_TYPE_SET
)
assert
isinstance
(
priority
,
_SQLTEST_NO_QUOTE_TYPE_SET
)
assert
isinstance
(
uid
,
_SQLTEST_NO_QUOTE_TYPE_SET
)
assert
isinstance
(
uid
,
_SQLTEST_NO_QUOTE_TYPE_SET
)
return
(
return
(
'(priority>%(priority)s OR (priority=%(priority)s
AND '
b'(priority>%(priority)d OR (priority=%(priority)d
AND '
'(date>%(date)s OR (date=%(date)s AND uid>%(uid)s
))'
b'(date>%(date)s OR (date=%(date)s AND uid>%(uid)d
))'
'))'
%
{
b
'))'
%
{
'priority'
:
priority
,
b
'priority'
:
priority
,
# render_datetime raises if "date" lacks date API, so no need to check
# render_datetime raises if "date" lacks date API, so no need to check
'date'
:
render_string
(
render_datetime
(
date
)),
b
'date'
:
render_string
(
render_datetime
(
date
)),
'uid'
:
uid
,
b
'uid'
:
uid
,
}
}
)
)
sqltest_dict
[
'above_priority_date_uid'
]
=
renderAbovePriorityDateUid
sqltest_dict
[
'above_priority_date_uid'
]
=
renderAbovePriorityDateUid
...
@@ -178,7 +178,7 @@ def _validate_after_path_and_method_id(value, render_string):
...
@@ -178,7 +178,7 @@ def _validate_after_path_and_method_id(value, render_string):
path
,
method_id
=
value
path
,
method_id
=
value
return
(
return
(
sqltest_dict
[
'method_id'
](
method_id
,
render_string
)
+
sqltest_dict
[
'method_id'
](
method_id
,
render_string
)
+
' AND '
+
b
' AND '
+
sqltest_dict
[
'path'
](
path
,
render_string
)
sqltest_dict
[
'path'
](
path
,
render_string
)
)
)
...
@@ -186,7 +186,7 @@ def _validate_after_tag_and_method_id(value, render_string):
...
@@ -186,7 +186,7 @@ def _validate_after_tag_and_method_id(value, render_string):
tag
,
method_id
=
value
tag
,
method_id
=
value
return
(
return
(
sqltest_dict
[
'method_id'
](
method_id
,
render_string
)
+
sqltest_dict
[
'method_id'
](
method_id
,
render_string
)
+
' AND '
+
b
' AND '
+
sqltest_dict
[
'tag'
](
tag
,
render_string
)
sqltest_dict
[
'tag'
](
tag
,
render_string
)
)
)
...
@@ -403,17 +403,17 @@ CREATE TABLE %s (
...
@@ -403,17 +403,17 @@ CREATE TABLE %s (
def
hasActivitySQL
(
self
,
quote
,
only_valid
=
False
,
only_invalid
=
False
,
**
kw
):
def
hasActivitySQL
(
self
,
quote
,
only_valid
=
False
,
only_invalid
=
False
,
**
kw
):
where
=
[
sqltest_dict
[
k
](
v
,
quote
)
for
(
k
,
v
)
in
kw
.
items
()
if
v
]
where
=
[
sqltest_dict
[
k
](
v
,
quote
)
for
(
k
,
v
)
in
kw
.
items
()
if
v
]
if
only_valid
:
if
only_valid
:
where
.
append
(
'processing_node > %d'
%
INVOKE_ERROR_STATE
)
where
.
append
(
b
'processing_node > %d'
%
INVOKE_ERROR_STATE
)
if
only_invalid
:
if
only_invalid
:
where
.
append
(
'processing_node <= %d'
%
INVOKE_ERROR_STATE
)
where
.
append
(
b
'processing_node <= %d'
%
INVOKE_ERROR_STATE
)
return
"SELECT 1 FROM %s WHERE %s LIMIT 1"
%
(
return
b
"SELECT 1 FROM %s WHERE %s LIMIT 1"
%
(
self
.
sql_table
,
" AND "
.
join
(
where
)
or
"1"
)
self
.
sql_table
.
encode
(),
b" AND "
.
join
(
where
)
or
b
"1"
)
def
getPriority
(
self
,
activity_tool
,
processing_node
,
node_set
=
None
):
def
getPriority
(
self
,
activity_tool
,
processing_node
,
node_set
=
None
):
if
node_set
is
None
:
if
node_set
is
None
:
q
=
(
"SELECT 3*priority, date FROM %s"
q
=
(
b
"SELECT 3*priority, date FROM %s"
" WHERE processing_node=0 AND date <= UTC_TIMESTAMP(6)"
b
" WHERE processing_node=0 AND date <= UTC_TIMESTAMP(6)"
" ORDER BY priority, date LIMIT 1"
%
self
.
sql_table
)
b" ORDER BY priority, date LIMIT 1"
%
self
.
sql_table
.
encode
()
)
else
:
else
:
subquery
=
(
"(SELECT 3*priority{} as effective_priority, date FROM %s"
subquery
=
(
"(SELECT 3*priority{} as effective_priority, date FROM %s"
" WHERE {} AND processing_node=0 AND date <= UTC_TIMESTAMP(6)"
" WHERE {} AND processing_node=0 AND date <= UTC_TIMESTAMP(6)"
...
@@ -421,12 +421,12 @@ CREATE TABLE %s (
...
@@ -421,12 +421,12 @@ CREATE TABLE %s (
node
=
'node=%s'
%
processing_node
node
=
'node=%s'
%
processing_node
# "ALL" on all but one, to incur deduplication cost only once.
# "ALL" on all but one, to incur deduplication cost only once.
# "UNION ALL" between the two naturally distinct sets.
# "UNION ALL" between the two naturally distinct sets.
q
=
(
"SELECT * FROM (%s UNION ALL %s UNION %s%s) as t"
q
=
(
b
"SELECT * FROM (%s UNION ALL %s UNION %s%s) as t"
" ORDER BY effective_priority, date LIMIT 1"
%
(
b
" ORDER BY effective_priority, date LIMIT 1"
%
(
subquery
(
-
1
,
node
),
subquery
(
-
1
,
node
)
.
encode
()
,
subquery
(
''
,
'node=0'
),
subquery
(
''
,
'node=0'
)
.
encode
()
,
subquery
(
'+IF(node, IF(%s, -1, 1), 0)'
%
node
,
'node>=0'
),
subquery
(
'+IF(node, IF(%s, -1, 1), 0)'
%
node
,
'node>=0'
)
.
encode
()
,
' UNION ALL '
+
subquery
(
-
1
,
'node IN (%s)'
%
','
.
join
(
map
(
str
,
node_set
)))
if
node_set
else
''
,
b' UNION ALL '
+
subquery
(
-
1
,
'node IN (%s)'
%
','
.
join
(
map
(
str
,
node_set
))).
encode
()
if
node_set
else
b
''
,
))
))
result
=
activity_tool
.
getSQLConnection
().
query
(
q
,
0
)[
1
]
result
=
activity_tool
.
getSQLConnection
().
query
(
q
,
0
)[
1
]
if
result
:
if
result
:
...
@@ -599,18 +599,18 @@ CREATE TABLE %s (
...
@@ -599,18 +599,18 @@ CREATE TABLE %s (
if
len
(
column_list
)
==
1
else
if
len
(
column_list
)
==
1
else
_IDENTITY
_IDENTITY
)
)
base_sql_suffix
=
' WHERE processing_node > %i AND (%%s) LIMIT 1)'
%
(
base_sql_suffix
=
b
' WHERE processing_node > %i AND (%%s) LIMIT 1)'
%
(
min_processing_node
,
min_processing_node
,
)
)
sql_suffix_list
=
[
sql_suffix_list
=
[
base_sql_suffix
%
to_sql
(
dependency_value
,
quote
)
base_sql_suffix
%
to_sql
(
dependency_value
,
quote
)
for
dependency_value
in
dependency_value_dict
for
dependency_value
in
dependency_value_dict
]
]
base_sql_prefix
=
'(SELECT %s FROM '
%
(
base_sql_prefix
=
b
'(SELECT %s FROM '
%
(
','
.
join
(
column_list
),
b','
.
join
([
c
.
encode
()
for
c
in
column_list
]
),
)
)
subquery_list
=
[
subquery_list
=
[
base_sql_prefix
+
table_name
+
sql_suffix
base_sql_prefix
+
table_name
.
encode
()
+
sql_suffix
for
table_name
in
table_name_list
for
table_name
in
table_name_list
for
sql_suffix
in
sql_suffix_list
for
sql_suffix
in
sql_suffix_list
]
]
...
@@ -621,7 +621,7 @@ CREATE TABLE %s (
...
@@ -621,7 +621,7 @@ CREATE TABLE %s (
# by the number of activty tables: it is also proportional to the
# by the number of activty tables: it is also proportional to the
# number of distinct values being looked for in the current column.
# number of distinct values being looked for in the current column.
for
row
in
db
.
query
(
for
row
in
db
.
query
(
' UNION '
.
join
(
subquery_list
[
_MAX_DEPENDENCY_UNION_SUBQUERY_COUNT
:]),
b
' UNION '
.
join
(
subquery_list
[
_MAX_DEPENDENCY_UNION_SUBQUERY_COUNT
:]),
max_rows
=
0
,
max_rows
=
0
,
)[
1
]:
)[
1
]:
# Each row is a value which blocks some activities.
# Each row is a value which blocks some activities.
...
@@ -695,9 +695,9 @@ CREATE TABLE %s (
...
@@ -695,9 +695,9 @@ CREATE TABLE %s (
assert
limit
assert
limit
quote
=
db
.
string_literal
quote
=
db
.
string_literal
query
=
db
.
query
query
=
db
.
query
args
=
(
self
.
sql_table
,
sqltest_dict
[
'to_date'
](
date
,
quote
),
args
=
(
self
.
sql_table
.
encode
()
,
sqltest_dict
[
'to_date'
](
date
,
quote
),
' AND group_method_id='
+
quote
(
group_method_id
)
b
' AND group_method_id='
+
quote
(
group_method_id
)
if
group_method_id
else
''
,
limit
)
if
group_method_id
else
b
''
,
limit
)
# Note: Not all write accesses to our table are protected by this lock.
# Note: Not all write accesses to our table are protected by this lock.
# This lock is not here for data consistency reasons, but to avoid wasting
# This lock is not here for data consistency reasons, but to avoid wasting
...
@@ -726,25 +726,25 @@ CREATE TABLE %s (
...
@@ -726,25 +726,25 @@ CREATE TABLE %s (
# time).
# time).
if
node_set
is
None
:
if
node_set
is
None
:
result
=
Results
(
query
(
result
=
Results
(
query
(
"SELECT * FROM %s WHERE processing_node=0 AND %s%s"
b
"SELECT * FROM %s WHERE processing_node=0 AND %s%s"
" ORDER BY priority, date LIMIT %s
FOR UPDATE"
%
args
,
0
))
b" ORDER BY priority, date LIMIT %d
FOR UPDATE"
%
args
,
0
))
else
:
else
:
# We'd like to write
# We'd like to write
# ORDER BY priority, IF(node, IF(node={node}, -1, 1), 0), date
# ORDER BY priority, IF(node, IF(node={node}, -1, 1), 0), date
# but this makes indices inefficient.
# but this makes indices inefficient.
subquery
=
(
"(SELECT *, 3*priority{}
as effective_priority FROM %s"
subquery
=
(
b"(SELECT *, 3*priority%%s
as effective_priority FROM %s"
" WHERE {}
AND processing_node=0 AND %s%s"
b" WHERE %%s
AND processing_node=0 AND %s%s"
" ORDER BY priority, date LIMIT %s FOR UPDATE)"
%
args
).
format
b" ORDER BY priority, date LIMIT %d FOR UPDATE)"
%
args
)
node
=
'node=%s
'
%
processing_node
node
=
b'node=%d
'
%
processing_node
result
=
Results
(
query
(
result
=
Results
(
query
(
# "ALL" on all but one, to incur deduplication cost only once.
# "ALL" on all but one, to incur deduplication cost only once.
# "UNION ALL" between the two naturally distinct sets.
# "UNION ALL" between the two naturally distinct sets.
"SELECT * FROM (%s UNION ALL %s UNION %s%s) as t"
b
"SELECT * FROM (%s UNION ALL %s UNION %s%s) as t"
" ORDER BY effective_priority, date LIMIT %s
"
%
(
b" ORDER BY effective_priority, date LIMIT %d
"
%
(
subquery
(
-
1
,
node
),
subquery
%
(
b'-1'
,
node
),
subquery
(
''
,
'node=0'
),
subquery
%
(
b''
,
b
'node=0'
),
subquery
(
'+IF(node, IF(%s, -1, 1), 0)'
%
node
,
'node>=0'
),
subquery
%
(
b'+IF(node, IF(%s, -1, 1), 0)'
%
node
,
b
'node>=0'
),
' UNION ALL '
+
subquery
(
-
1
,
'node IN (%s)'
%
','
.
join
(
map
(
str
,
node_set
)))
if
node_set
else
''
,
b' UNION ALL '
+
subquery
%
(
str
(
-
1
),
b'node IN (%s)'
%
b','
.
join
(
map
(
str
,
node_set
)).
encode
())
if
node_set
else
b
''
,
limit
),
0
))
limit
),
0
))
if
result
:
if
result
:
# Reserve messages.
# Reserve messages.
...
@@ -807,9 +807,9 @@ CREATE TABLE %s (
...
@@ -807,9 +807,9 @@ CREATE TABLE %s (
# To minimize the probability of deadlocks, we also COMMIT so that a
# To minimize the probability of deadlocks, we also COMMIT so that a
# new transaction starts on the first 'FOR UPDATE' query, which is all
# new transaction starts on the first 'FOR UPDATE' query, which is all
# the more important as the current on started with getPriority().
# the more important as the current on started with getPriority().
result
=
db
.
query
(
"SELECT * FROM %s WHERE processing_node=%s
"
result
=
db
.
query
(
b"SELECT * FROM %s WHERE processing_node=%d
"
" ORDER BY priority, date LIMIT 1
\
0
COMMIT"
%
(
b
" ORDER BY priority, date LIMIT 1
\
0
COMMIT"
%
(
self
.
sql_table
,
processing_node
),
0
)
self
.
sql_table
.
encode
()
,
processing_node
),
0
)
already_assigned
=
result
[
1
]
already_assigned
=
result
[
1
]
if
already_assigned
:
if
already_assigned
:
result
=
Results
(
result
)
result
=
Results
(
result
)
...
@@ -838,10 +838,10 @@ CREATE TABLE %s (
...
@@ -838,10 +838,10 @@ CREATE TABLE %s (
cost
*=
count
cost
*=
count
# Retrieve objects which have the same group method.
# Retrieve objects which have the same group method.
result
=
iter
(
already_assigned
result
=
iter
(
already_assigned
and
Results
(
db
.
query
(
"SELECT * FROM %s"
and
Results
(
db
.
query
(
b
"SELECT * FROM %s"
" WHERE processing_node=%s
AND group_method_id=%s"
b" WHERE processing_node=%d
AND group_method_id=%s"
" ORDER BY priority, date LIMIT %s
"
%
(
b" ORDER BY priority, date LIMIT %d
"
%
(
self
.
sql_table
,
processing_node
,
self
.
sql_table
.
encode
()
,
processing_node
,
db
.
string_literal
(
group_method_id
),
limit
),
0
))
db
.
string_literal
(
group_method_id
),
limit
),
0
))
# Do not optimize rare case: keep the code simple by not
# Do not optimize rare case: keep the code simple by not
# adding more results from getReservedMessageList if the
# adding more results from getReservedMessageList if the
...
...
product/CMFActivity/Activity/SQLDict.py
View file @
d25efe94
...
@@ -86,7 +86,7 @@ class SQLDict(SQLBase):
...
@@ -86,7 +86,7 @@ class SQLDict(SQLBase):
uid
=
line
.
uid
uid
=
line
.
uid
original_uid
=
path_and_method_id_dict
.
get
(
key
)
original_uid
=
path_and_method_id_dict
.
get
(
key
)
if
original_uid
is
None
:
if
original_uid
is
None
:
sql_method_id
=
" AND method_id = %s AND group_method_id = %s"
%
(
sql_method_id
=
b
" AND method_id = %s AND group_method_id = %s"
%
(
quote
(
method_id
),
quote
(
line
.
group_method_id
))
quote
(
method_id
),
quote
(
line
.
group_method_id
))
m
=
Message
.
load
(
line
.
message
,
uid
=
uid
,
line
=
line
)
m
=
Message
.
load
(
line
.
message
,
uid
=
uid
,
line
=
line
)
merge_parent
=
m
.
activity_kw
.
get
(
'merge_parent'
)
merge_parent
=
m
.
activity_kw
.
get
(
'merge_parent'
)
...
@@ -103,11 +103,11 @@ class SQLDict(SQLBase):
...
@@ -103,11 +103,11 @@ class SQLDict(SQLBase):
uid_list
=
[]
uid_list
=
[]
if
path_list
:
if
path_list
:
# Select parent messages.
# Select parent messages.
result
=
Results
(
db
.
query
(
"SELECT * FROM message"
result
=
Results
(
db
.
query
(
b
"SELECT * FROM message"
" WHERE processing_node IN (0, %s
) AND path IN (%s)%s"
b" WHERE processing_node IN (0, %d
) AND path IN (%s)%s"
" ORDER BY path LIMIT 1 FOR UPDATE"
%
(
b
" ORDER BY path LIMIT 1 FOR UPDATE"
%
(
processing_node
,
processing_node
,
','
.
join
(
map
(
quote
,
path_list
)),
b
','
.
join
(
map
(
quote
,
path_list
)),
sql_method_id
,
sql_method_id
,
),
0
))
),
0
))
if
result
:
# found a parent
if
result
:
# found a parent
...
@@ -120,11 +120,11 @@ class SQLDict(SQLBase):
...
@@ -120,11 +120,11 @@ class SQLDict(SQLBase):
m
=
Message
.
load
(
line
.
message
,
uid
=
uid
,
line
=
line
)
m
=
Message
.
load
(
line
.
message
,
uid
=
uid
,
line
=
line
)
# return unreserved similar children
# return unreserved similar children
path
=
line
.
path
path
=
line
.
path
result
=
db
.
query
(
"SELECT uid FROM message"
result
=
db
.
query
(
b
"SELECT uid FROM message"
" WHERE processing_node = 0 AND (path = %s OR path LIKE %s)"
b
" WHERE processing_node = 0 AND (path = %s OR path LIKE %s)"
"%s FOR UPDATE"
%
(
b
"%s FOR UPDATE"
%
(
quote
(
path
),
quote
(
path
.
replace
(
'_'
,
r'\
_
') + '
/%
'),
quote
(
path
),
quote
(
path
.
replace
(
'_'
,
r'\
_
') + '
/%
'),
sql_method_id,
sql_method_id
.encode()
,
), 0)[1]
), 0)[1]
reserve_uid_list = [x for x, in result]
reserve_uid_list = [x for x, in result]
uid_list += reserve_uid_list
uid_list += reserve_uid_list
...
@@ -133,8 +133,8 @@ class SQLDict(SQLBase):
...
@@ -133,8 +133,8 @@ class SQLDict(SQLBase):
reserve_uid_list.append(uid)
reserve_uid_list.append(uid)
else:
else:
# Select duplicates.
# Select duplicates.
result = db.query("SELECT uid FROM message"
result = db.query(
b
"SELECT uid FROM message"
" WHERE processing_node = 0 AND path = %s%s FOR UPDATE" % (
b
" WHERE processing_node = 0 AND path = %s%s FOR UPDATE" % (
quote(path), sql_method_id,
quote(path), sql_method_id,
), 0)[1]
), 0)[1]
reserve_uid_list = uid_list = [x for x, in result]
reserve_uid_list = uid_list = [x for x, in result]
...
...
product/CMFActivity/ActivityTool.py
View file @
d25efe94
...
@@ -1321,6 +1321,7 @@ class ActivityTool (BaseTool):
...
@@ -1321,6 +1321,7 @@ class ActivityTool (BaseTool):
# Catch ALL exception to avoid killing timerserver.
# Catch ALL exception to avoid killing timerserver.
LOG
(
'ActivityTool'
,
ERROR
,
'process_timer received an exception'
,
LOG
(
'ActivityTool'
,
ERROR
,
'process_timer received an exception'
,
error
=
True
)
error
=
True
)
import
pdb
;
pdb
.
post_mortem
()
finally
:
finally
:
setSecurityManager
(
old_sm
)
setSecurityManager
(
old_sm
)
finally
:
finally
:
...
@@ -1394,7 +1395,7 @@ class ActivityTool (BaseTool):
...
@@ -1394,7 +1395,7 @@ class ActivityTool (BaseTool):
path
=
None
if
obj
is
None
else
'/'
.
join
(
obj
.
getPhysicalPath
())
path
=
None
if
obj
is
None
else
'/'
.
join
(
obj
.
getPhysicalPath
())
db
=
self
.
getSQLConnection
()
db
=
self
.
getSQLConnection
()
quote
=
db
.
string_literal
quote
=
db
.
string_literal
return
bool
(
db
.
query
(
"(%s)"
%
") UNION ALL ("
.
join
(
return
bool
(
db
.
query
(
b"(%s)"
%
b
") UNION ALL ("
.
join
(
activity
.
hasActivitySQL
(
quote
,
path
=
path
,
**
kw
)
activity
.
hasActivitySQL
(
quote
,
path
=
path
,
**
kw
)
for
activity
in
activity_dict
.
values
()))[
1
])
for
activity
in
activity_dict
.
values
()))[
1
])
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment