Skip to content
Projects
Groups
Snippets
Help
This project
Loading...
Sign in / Register
Toggle navigation
W
wine-winehq
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Registry
Registry
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
wine
wine-winehq
Commits
92203f10
Commit
92203f10
authored
Jan 24, 2011
by
Hans Leidekker
Committed by
Alexandre Julliard
Jan 24, 2011
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
msi: Implement the MsiPublishAssemblies and MsiUnpublishAssemblies standard actions.
parent
3eaf33ff
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
237 additions
and
16 deletions
+237
-16
action.c
dlls/msi/action.c
+0
-7
assembly.c
dlls/msi/assembly.c
+235
-4
msipriv.h
dlls/msi/msipriv.h
+1
-0
action.c
dlls/msi/tests/action.c
+1
-5
No files found.
dlls/msi/action.c
View file @
92203f10
...
@@ -7172,13 +7172,6 @@ static UINT ACTION_IsolateComponents( MSIPACKAGE *package )
...
@@ -7172,13 +7172,6 @@ static UINT ACTION_IsolateComponents( MSIPACKAGE *package )
return
msi_unimplemented_action_stub
(
package
,
"IsolateComponents"
,
table
);
return
msi_unimplemented_action_stub
(
package
,
"IsolateComponents"
,
table
);
}
}
static
UINT
ACTION_MsiUnpublishAssemblies
(
MSIPACKAGE
*
package
)
{
static
const
WCHAR
table
[]
=
{
'M'
,
's'
,
'i'
,
'A'
,
's'
,
's'
,
'e'
,
'm'
,
'b'
,
'l'
,
'y'
,
0
};
return
msi_unimplemented_action_stub
(
package
,
"MsiUnpublishAssemblies"
,
table
);
}
static
UINT
ACTION_RMCCPSearch
(
MSIPACKAGE
*
package
)
static
UINT
ACTION_RMCCPSearch
(
MSIPACKAGE
*
package
)
{
{
static
const
WCHAR
table
[]
=
{
'C'
,
'C'
,
'P'
,
'S'
,
'e'
,
'a'
,
'r'
,
'c'
,
'h'
,
0
};
static
const
WCHAR
table
[]
=
{
'C'
,
'C'
,
'P'
,
'S'
,
'e'
,
'a'
,
'r'
,
'c'
,
'h'
,
0
};
...
...
dlls/msi/assembly.c
View file @
92203f10
...
@@ -24,6 +24,7 @@
...
@@ -24,6 +24,7 @@
#include "windef.h"
#include "windef.h"
#include "winbase.h"
#include "winbase.h"
#include "winreg.h"
#include "wine/debug.h"
#include "wine/debug.h"
#include "wine/unicode.h"
#include "wine/unicode.h"
#include "msipriv.h"
#include "msipriv.h"
...
@@ -318,20 +319,250 @@ UINT install_assembly( MSIPACKAGE *package, MSICOMPONENT *comp )
...
@@ -318,20 +319,250 @@ UINT install_assembly( MSIPACKAGE *package, MSICOMPONENT *comp )
return
ERROR_SUCCESS
;
return
ERROR_SUCCESS
;
}
}
static
WCHAR
*
build_local_assembly_path
(
const
WCHAR
*
filename
)
{
UINT
i
;
WCHAR
*
ret
;
if
(
!
(
ret
=
msi_alloc
(
(
strlenW
(
filename
)
+
1
)
*
sizeof
(
WCHAR
)
)))
return
NULL
;
for
(
i
=
0
;
filename
[
i
];
i
++
)
{
if
(
filename
[
i
]
==
'\\'
||
filename
[
i
]
==
'/'
)
ret
[
i
]
=
'|'
;
else
ret
[
i
]
=
filename
[
i
];
}
ret
[
i
]
=
0
;
return
ret
;
}
static
LONG
open_assemblies_key
(
UINT
context
,
BOOL
win32
,
HKEY
*
hkey
)
{
static
const
WCHAR
path_win32
[]
=
{
'S'
,
'o'
,
'f'
,
't'
,
'w'
,
'a'
,
'r'
,
'e'
,
'\\'
,
'M'
,
'i'
,
'c'
,
'r'
,
'o'
,
's'
,
'o'
,
'f'
,
't'
,
'\\'
,
'I'
,
'n'
,
's'
,
't'
,
'a'
,
'l'
,
'l'
,
'e'
,
'r'
,
'\\'
,
'W'
,
'i'
,
'n'
,
'3'
,
'2'
,
'A'
,
's'
,
's'
,
'e'
,
'm'
,
'b'
,
'l'
,
'i'
,
'e'
,
's'
,
'\\'
,
0
};
static
const
WCHAR
path_dotnet
[]
=
{
'S'
,
'o'
,
'f'
,
't'
,
'w'
,
'a'
,
'r'
,
'e'
,
'\\'
,
'M'
,
'i'
,
'c'
,
'r'
,
'o'
,
's'
,
'o'
,
'f'
,
't'
,
'\\'
,
'I'
,
'n'
,
's'
,
't'
,
'a'
,
'l'
,
'l'
,
'e'
,
'r'
,
'\\'
,
'A'
,
's'
,
's'
,
'e'
,
'm'
,
'b'
,
'l'
,
'i'
,
'e'
,
's'
,
'\\'
,
0
};
static
const
WCHAR
classes_path_win32
[]
=
{
'I'
,
'n'
,
's'
,
't'
,
'a'
,
'l'
,
'l'
,
'e'
,
'r'
,
'\\'
,
'W'
,
'i'
,
'n'
,
'3'
,
'2'
,
'A'
,
's'
,
's'
,
'e'
,
'm'
,
'b'
,
'l'
,
'i'
,
'e'
,
's'
,
'\\'
,
0
};
static
const
WCHAR
classes_path_dotnet
[]
=
{
'I'
,
'n'
,
's'
,
't'
,
'a'
,
'l'
,
'l'
,
'e'
,
'r'
,
'\\'
,
'A'
,
's'
,
's'
,
'e'
,
'm'
,
'b'
,
'l'
,
'i'
,
'e'
,
's'
,
'\\'
,
0
};
HKEY
root
;
const
WCHAR
*
path
;
if
(
context
==
MSIINSTALLCONTEXT_MACHINE
)
{
root
=
HKEY_CLASSES_ROOT
;
if
(
win32
)
path
=
classes_path_win32
;
else
path
=
classes_path_dotnet
;
}
else
{
root
=
HKEY_CURRENT_USER
;
if
(
win32
)
path
=
path_win32
;
else
path
=
path_dotnet
;
}
return
RegCreateKeyW
(
root
,
path
,
hkey
);
}
static
LONG
open_local_assembly_key
(
UINT
context
,
BOOL
win32
,
const
WCHAR
*
filename
,
HKEY
*
hkey
)
{
LONG
res
;
HKEY
root
;
WCHAR
*
path
;
if
(
!
(
path
=
build_local_assembly_path
(
filename
)))
return
ERROR_OUTOFMEMORY
;
if
((
res
=
open_assemblies_key
(
context
,
win32
,
&
root
)))
{
msi_free
(
path
);
return
res
;
}
res
=
RegCreateKeyW
(
root
,
path
,
hkey
);
RegCloseKey
(
root
);
msi_free
(
path
);
return
res
;
}
static
LONG
delete_local_assembly_key
(
UINT
context
,
BOOL
win32
,
const
WCHAR
*
filename
)
{
LONG
res
;
HKEY
root
;
WCHAR
*
path
;
if
(
!
(
path
=
build_local_assembly_path
(
filename
)))
return
ERROR_OUTOFMEMORY
;
if
((
res
=
open_assemblies_key
(
context
,
win32
,
&
root
)))
{
msi_free
(
path
);
return
res
;
}
res
=
RegDeleteKeyW
(
root
,
path
);
RegCloseKey
(
root
);
msi_free
(
path
);
return
res
;
}
static
LONG
open_global_assembly_key
(
UINT
context
,
BOOL
win32
,
HKEY
*
hkey
)
{
static
const
WCHAR
path_win32
[]
=
{
'S'
,
'o'
,
'f'
,
't'
,
'w'
,
'a'
,
'r'
,
'e'
,
'\\'
,
'M'
,
'i'
,
'c'
,
'r'
,
'o'
,
's'
,
'o'
,
'f'
,
't'
,
'\\'
,
'I'
,
'n'
,
's'
,
't'
,
'a'
,
'l'
,
'l'
,
'e'
,
'r'
,
'\\'
,
'W'
,
'i'
,
'n'
,
'3'
,
'2'
,
'A'
,
's'
,
's'
,
'e'
,
'm'
,
'b'
,
'l'
,
'i'
,
'e'
,
's'
,
'\\'
,
'G'
,
'l'
,
'o'
,
'b'
,
'a'
,
'l'
,
0
};
static
const
WCHAR
path_dotnet
[]
=
{
'S'
,
'o'
,
'f'
,
't'
,
'w'
,
'a'
,
'r'
,
'e'
,
'\\'
,
'M'
,
'i'
,
'c'
,
'r'
,
'o'
,
's'
,
'o'
,
'f'
,
't'
,
'\\'
,
'I'
,
'n'
,
's'
,
't'
,
'a'
,
'l'
,
'l'
,
'e'
,
'r'
,
'\\'
,
'A'
,
's'
,
's'
,
'e'
,
'm'
,
'b'
,
'l'
,
'i'
,
'e'
,
's'
,
'\\'
,
'G'
,
'l'
,
'o'
,
'b'
,
'a'
,
'l'
,
0
};
static
const
WCHAR
classes_path_win32
[]
=
{
'I'
,
'n'
,
's'
,
't'
,
'a'
,
'l'
,
'l'
,
'e'
,
'r'
,
'\\'
,
'W'
,
'i'
,
'n'
,
'3'
,
'2'
,
'A'
,
's'
,
's'
,
'e'
,
'm'
,
'b'
,
'l'
,
'i'
,
'e'
,
's'
,
'\\'
,
'G'
,
'l'
,
'o'
,
'b'
,
'a'
,
'l'
,
0
};
static
const
WCHAR
classes_path_dotnet
[]
=
{
'I'
,
'n'
,
's'
,
't'
,
'a'
,
'l'
,
'l'
,
'e'
,
'r'
,
'\\'
,
'A'
,
's'
,
's'
,
'e'
,
'm'
,
'b'
,
'l'
,
'i'
,
'e'
,
's'
,
'\\'
,
'G'
,
'l'
,
'o'
,
'b'
,
'a'
,
'l'
,
0
};
HKEY
root
;
const
WCHAR
*
path
;
if
(
context
==
MSIINSTALLCONTEXT_MACHINE
)
{
root
=
HKEY_CLASSES_ROOT
;
if
(
win32
)
path
=
classes_path_win32
;
else
path
=
classes_path_dotnet
;
}
else
{
root
=
HKEY_CURRENT_USER
;
if
(
win32
)
path
=
path_win32
;
else
path
=
path_dotnet
;
}
return
RegCreateKeyW
(
root
,
path
,
hkey
);
}
UINT
ACTION_MsiPublishAssemblies
(
MSIPACKAGE
*
package
)
UINT
ACTION_MsiPublishAssemblies
(
MSIPACKAGE
*
package
)
{
{
MSIRECORD
*
uirow
;
MSICOMPONENT
*
comp
;
MSICOMPONENT
*
comp
;
LIST_FOR_EACH_ENTRY
(
comp
,
&
package
->
components
,
MSICOMPONENT
,
entry
)
LIST_FOR_EACH_ENTRY
(
comp
,
&
package
->
components
,
MSICOMPONENT
,
entry
)
{
{
if
(
!
comp
->
assembly
||
!
comp
->
Enabled
)
LONG
res
;
HKEY
hkey
;
GUID
guid
;
DWORD
size
;
WCHAR
buffer
[
43
];
MSIRECORD
*
uirow
;
MSIASSEMBLY
*
assembly
=
comp
->
assembly
;
BOOL
win32
;
if
(
!
assembly
||
!
comp
->
ComponentId
)
continue
;
if
(
!
comp
->
Enabled
)
{
TRACE
(
"component is disabled: %s
\n
"
,
debugstr_w
(
comp
->
Component
));
continue
;
}
if
(
comp
->
ActionRequest
!=
INSTALLSTATE_LOCAL
)
{
TRACE
(
"Component not scheduled for installation: %s
\n
"
,
debugstr_w
(
comp
->
Component
));
comp
->
Action
=
comp
->
Installed
;
continue
;
}
comp
->
Action
=
INSTALLSTATE_LOCAL
;
TRACE
(
"publishing %s
\n
"
,
debugstr_w
(
comp
->
Component
));
CLSIDFromString
(
package
->
ProductCode
,
&
guid
);
encode_base85_guid
(
&
guid
,
buffer
);
buffer
[
20
]
=
'>'
;
CLSIDFromString
(
comp
->
ComponentId
,
&
guid
);
encode_base85_guid
(
&
guid
,
buffer
+
21
);
buffer
[
42
]
=
0
;
win32
=
assembly
->
attributes
&
msidbAssemblyAttributesWin32
;
if
(
assembly
->
application
)
{
MSIFILE
*
file
=
get_loaded_file
(
package
,
assembly
->
application
);
if
((
res
=
open_local_assembly_key
(
package
->
Context
,
win32
,
file
->
TargetPath
,
&
hkey
)))
{
WARN
(
"failed to open local assembly key %d
\n
"
,
res
);
return
ERROR_FUNCTION_FAILED
;
}
}
else
{
if
((
res
=
open_global_assembly_key
(
package
->
Context
,
win32
,
&
hkey
)))
{
WARN
(
"failed to open global assembly key %d
\n
"
,
res
);
return
ERROR_FUNCTION_FAILED
;
}
}
size
=
sizeof
(
buffer
);
if
((
res
=
RegSetValueExW
(
hkey
,
assembly
->
display_name
,
0
,
REG_MULTI_SZ
,
(
const
BYTE
*
)
buffer
,
size
)))
{
WARN
(
"failed to set assembly value %d
\n
"
,
res
);
}
RegCloseKey
(
hkey
);
uirow
=
MSI_CreateRecord
(
2
);
MSI_RecordSetStringW
(
uirow
,
2
,
assembly
->
display_name
);
ui_actiondata
(
package
,
szMsiPublishAssemblies
,
uirow
);
msiobj_release
(
&
uirow
->
hdr
);
}
return
ERROR_SUCCESS
;
}
UINT
ACTION_MsiUnpublishAssemblies
(
MSIPACKAGE
*
package
)
{
MSICOMPONENT
*
comp
;
LIST_FOR_EACH_ENTRY
(
comp
,
&
package
->
components
,
MSICOMPONENT
,
entry
)
{
LONG
res
;
MSIRECORD
*
uirow
;
MSIASSEMBLY
*
assembly
=
comp
->
assembly
;
BOOL
win32
;
if
(
!
assembly
||
!
comp
->
ComponentId
)
continue
;
if
(
!
comp
->
Enabled
)
{
TRACE
(
"component is disabled: %s
\n
"
,
debugstr_w
(
comp
->
Component
));
continue
;
continue
;
}
if
(
comp
->
ActionRequest
!=
INSTALLSTATE_ABSENT
)
{
TRACE
(
"Component not scheduled for removal: %s
\n
"
,
debugstr_w
(
comp
->
Component
));
comp
->
Action
=
comp
->
Installed
;
continue
;
}
comp
->
Action
=
INSTALLSTATE_ABSENT
;
/* FIXME: write assembly registry values */
TRACE
(
"unpublishing %s
\n
"
,
debugstr_w
(
comp
->
Component
));
win32
=
assembly
->
attributes
&
msidbAssemblyAttributesWin32
;
if
(
assembly
->
application
)
{
MSIFILE
*
file
=
get_loaded_file
(
package
,
assembly
->
application
);
if
((
res
=
delete_local_assembly_key
(
package
->
Context
,
win32
,
file
->
TargetPath
)))
WARN
(
"failed to delete local assembly key %d
\n
"
,
res
);
}
else
{
HKEY
hkey
;
if
((
res
=
open_global_assembly_key
(
package
->
Context
,
win32
,
&
hkey
)))
WARN
(
"failed to delete global assembly key %d
\n
"
,
res
);
else
{
if
((
res
=
RegDeleteValueW
(
hkey
,
assembly
->
display_name
)))
WARN
(
"failed to delete global assembly value %d
\n
"
,
res
);
RegCloseKey
(
hkey
);
}
}
uirow
=
MSI_CreateRecord
(
2
);
uirow
=
MSI_CreateRecord
(
2
);
MSI_RecordSetStringW
(
uirow
,
2
,
comp
->
assembly
->
display_name
);
MSI_RecordSetStringW
(
uirow
,
2
,
assembly
->
display_name
);
ui_actiondata
(
package
,
szMsiPublishAssemblies
,
uirow
);
ui_actiondata
(
package
,
szMsiPublishAssemblies
,
uirow
);
msiobj_release
(
&
uirow
->
hdr
);
msiobj_release
(
&
uirow
->
hdr
);
}
}
...
...
dlls/msi/msipriv.h
View file @
92203f10
...
@@ -922,6 +922,7 @@ extern UINT ACTION_UnregisterFonts(MSIPACKAGE *package);
...
@@ -922,6 +922,7 @@ extern UINT ACTION_UnregisterFonts(MSIPACKAGE *package);
extern
UINT
ACTION_UnregisterMIMEInfo
(
MSIPACKAGE
*
package
);
extern
UINT
ACTION_UnregisterMIMEInfo
(
MSIPACKAGE
*
package
);
extern
UINT
ACTION_UnregisterProgIdInfo
(
MSIPACKAGE
*
package
);
extern
UINT
ACTION_UnregisterProgIdInfo
(
MSIPACKAGE
*
package
);
extern
UINT
ACTION_MsiPublishAssemblies
(
MSIPACKAGE
*
package
);
extern
UINT
ACTION_MsiPublishAssemblies
(
MSIPACKAGE
*
package
);
extern
UINT
ACTION_MsiUnpublishAssemblies
(
MSIPACKAGE
*
package
);
/* Helpers */
/* Helpers */
extern
DWORD
deformat_string
(
MSIPACKAGE
*
package
,
LPCWSTR
ptr
,
WCHAR
**
data
);
extern
DWORD
deformat_string
(
MSIPACKAGE
*
package
,
LPCWSTR
ptr
,
WCHAR
**
data
);
...
...
dlls/msi/tests/action.c
View file @
92203f10
...
@@ -5903,7 +5903,6 @@ static void test_publish_assemblies(void)
...
@@ -5903,7 +5903,6 @@ static void test_publish_assemblies(void)
}
}
ok
(
r
==
ERROR_SUCCESS
,
"Expected ERROR_SUCCESS, got %u
\n
"
,
r
);
ok
(
r
==
ERROR_SUCCESS
,
"Expected ERROR_SUCCESS, got %u
\n
"
,
r
);
todo_wine
{
res
=
RegOpenKeyA
(
HKEY_CURRENT_USER
,
path_dotnet
,
&
hkey
);
res
=
RegOpenKeyA
(
HKEY_CURRENT_USER
,
path_dotnet
,
&
hkey
);
ok
(
res
==
ERROR_SUCCESS
,
"Expected ERROR_SUCCESS, got %d
\n
"
,
res
);
ok
(
res
==
ERROR_SUCCESS
,
"Expected ERROR_SUCCESS, got %d
\n
"
,
res
);
CHECK_REG_STR
(
hkey
,
name_dotnet
,
"rcHQPHq?CA@Uv-XqMI1e>Z'q,T*76M@=YEg6My?~]"
);
CHECK_REG_STR
(
hkey
,
name_dotnet
,
"rcHQPHq?CA@Uv-XqMI1e>Z'q,T*76M@=YEg6My?~]"
);
...
@@ -5925,7 +5924,6 @@ static void test_publish_assemblies(void)
...
@@ -5925,7 +5924,6 @@ static void test_publish_assemblies(void)
ok
(
res
==
ERROR_SUCCESS
,
"Expected ERROR_SUCCESS, got %d
\n
"
,
res
);
ok
(
res
==
ERROR_SUCCESS
,
"Expected ERROR_SUCCESS, got %d
\n
"
,
res
);
CHECK_REG_STR
(
hkey
,
name_win32_local
,
"rcHQPHq?CA@Uv-XqMI1e>C)Uvlj*53A)u(QQ9=)X!"
);
CHECK_REG_STR
(
hkey
,
name_win32_local
,
"rcHQPHq?CA@Uv-XqMI1e>C)Uvlj*53A)u(QQ9=)X!"
);
RegCloseKey
(
hkey
);
RegCloseKey
(
hkey
);
}
r
=
MsiInstallProductA
(
msifile
,
"REMOVE=ALL"
);
r
=
MsiInstallProductA
(
msifile
,
"REMOVE=ALL"
);
ok
(
r
==
ERROR_SUCCESS
,
"Expected ERROR_SUCCESS, got %u
\n
"
,
r
);
ok
(
r
==
ERROR_SUCCESS
,
"Expected ERROR_SUCCESS, got %u
\n
"
,
r
);
...
@@ -5959,7 +5957,6 @@ static void test_publish_assemblies(void)
...
@@ -5959,7 +5957,6 @@ static void test_publish_assemblies(void)
r
=
MsiInstallProductA
(
msifile
,
"ALLUSERS=1"
);
r
=
MsiInstallProductA
(
msifile
,
"ALLUSERS=1"
);
ok
(
r
==
ERROR_SUCCESS
,
"Expected ERROR_SUCCESS, got %u
\n
"
,
r
);
ok
(
r
==
ERROR_SUCCESS
,
"Expected ERROR_SUCCESS, got %u
\n
"
,
r
);
todo_wine
{
res
=
RegOpenKeyA
(
HKEY_CLASSES_ROOT
,
classes_path_dotnet
,
&
hkey
);
res
=
RegOpenKeyA
(
HKEY_CLASSES_ROOT
,
classes_path_dotnet
,
&
hkey
);
ok
(
res
==
ERROR_SUCCESS
,
"Expected ERROR_SUCCESS, got %d
\n
"
,
res
);
ok
(
res
==
ERROR_SUCCESS
,
"Expected ERROR_SUCCESS, got %d
\n
"
,
res
);
CHECK_REG_STR
(
hkey
,
name_dotnet
,
"rcHQPHq?CA@Uv-XqMI1e>Z'q,T*76M@=YEg6My?~]"
);
CHECK_REG_STR
(
hkey
,
name_dotnet
,
"rcHQPHq?CA@Uv-XqMI1e>Z'q,T*76M@=YEg6My?~]"
);
...
@@ -5981,9 +5978,8 @@ static void test_publish_assemblies(void)
...
@@ -5981,9 +5978,8 @@ static void test_publish_assemblies(void)
ok
(
res
==
ERROR_SUCCESS
,
"Expected ERROR_SUCCESS, got %d
\n
"
,
res
);
ok
(
res
==
ERROR_SUCCESS
,
"Expected ERROR_SUCCESS, got %d
\n
"
,
res
);
CHECK_REG_STR
(
hkey
,
name_win32_local
,
"rcHQPHq?CA@Uv-XqMI1e>C)Uvlj*53A)u(QQ9=)X!"
);
CHECK_REG_STR
(
hkey
,
name_win32_local
,
"rcHQPHq?CA@Uv-XqMI1e>C)Uvlj*53A)u(QQ9=)X!"
);
RegCloseKey
(
hkey
);
RegCloseKey
(
hkey
);
}
r
=
MsiInstallProductA
(
msifile
,
"REMOVE=ALL"
);
r
=
MsiInstallProductA
(
msifile
,
"REMOVE=ALL
ALLUSERS=1
"
);
ok
(
r
==
ERROR_SUCCESS
,
"Expected ERROR_SUCCESS, got %u
\n
"
,
r
);
ok
(
r
==
ERROR_SUCCESS
,
"Expected ERROR_SUCCESS, got %u
\n
"
,
r
);
res
=
RegOpenKeyA
(
HKEY_CLASSES_ROOT
,
classes_path_dotnet
,
&
hkey
);
res
=
RegOpenKeyA
(
HKEY_CLASSES_ROOT
,
classes_path_dotnet
,
&
hkey
);
...
...
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