提交 7df48563 编写于 作者: S Serge Rider

Merge remote-tracking branch 'origin/devel' into devel

......@@ -44,6 +44,8 @@ command.org.jkiss.dbeaver.core.navigator.set.active.name=Set active object
command.org.jkiss.dbeaver.core.navigator.set.active.description=Set active object
command.org.jkiss.dbeaver.core.navigator.bookmark.add.name=Add Bookmark
command.org.jkiss.dbeaver.core.navigator.bookmark.add.description=Add Bookmark
command.org.jkiss.dbeaver.core.navigator.view.configure.name=Configure
command.org.jkiss.dbeaver.core.navigator.view.configure.description=Configure columns visibility
command.org.jkiss.dbeaver.core.project.create.name=Create Project
command.org.jkiss.dbeaver.core.project.create.description=Create new project
......
......@@ -178,7 +178,9 @@ public class StreamTransferConsumer implements IDataTransferConsumer<StreamConsu
Boolean extractImages = (Boolean) processorProperties.get(StreamConsumerSettings.PROP_EXTRACT_IMAGES);
String fileExt = (extractImages != null && extractImages) ? ".jpg" : ".data";
File lobFile = new File(lobDirectory, outputFile.getName() + "-" + lobCount + fileExt); //$NON-NLS-1$ //$NON-NLS-2$
ContentUtils.saveContentToFile(contents.getContentStream(), lobFile, monitor);
try (InputStream cs = contents.getContentStream()) {
ContentUtils.saveContentToFile(cs, lobFile, monitor);
}
return lobFile;
}
......
......@@ -192,8 +192,8 @@ public class UIUtils {
for (TableColumn column : columns) {
int colWidth = column.getWidth();
if (colWidth > totalWidth / 3) {
// If some columns is too big (more than 33% of total width)
// Then shrink it to 30%
// If some columns are too big (more than 33% of total width)
// Then shrink them to 30%
column.setWidth(totalWidth / 3);
totalWidth -= colWidth;
totalWidth += column.getWidth();
......@@ -201,10 +201,21 @@ public class UIUtils {
}
int extraSpace = totalWidth - clientArea.width;
for (TableColumn tc : columns) {
double ratio = (double) tc.getWidth() / totalWidth;
int newWidth = (int) (tc.getWidth() - extraSpace * ratio);
tc.setWidth(newWidth);
GC gc = new GC(table);
try {
for (TableColumn tc : columns) {
double ratio = (double) tc.getWidth() / totalWidth;
int newWidth = (int) (tc.getWidth() - extraSpace * ratio);
int minWidth = gc.stringExtent(tc.getText()).x;
minWidth += 5;
if (newWidth < minWidth) {
newWidth = minWidth;
}
tc.setWidth(newWidth);
}
}
finally {
gc.dispose();
}
}
if (fit && totalWidth < clientArea.width) {
......@@ -256,10 +267,21 @@ public class UIUtils {
areaWidth -= tree.getVerticalBar().getSize().x;
}
if (totalWidth > areaWidth) {
int extraSpace = totalWidth - areaWidth;
for (TreeColumn tc : columns) {
double ratio = (double) tc.getWidth() / totalWidth;
tc.setWidth((int) (tc.getWidth() - extraSpace * ratio));
GC gc = new GC(tree);
try {
int extraSpace = totalWidth - areaWidth;
for (TreeColumn tc : columns) {
double ratio = (double) tc.getWidth() / totalWidth;
int newWidth = (int) (tc.getWidth() - extraSpace * ratio);
int minWidth = gc.stringExtent(tc.getText()).x;
minWidth += 5;
if (newWidth < minWidth) {
newWidth = minWidth;
}
tc.setWidth((int) newWidth);
}
} finally {
gc.dispose();
}
} else if (totalWidth < areaWidth) {
float extraSpace = areaWidth - totalWidth;
......
......@@ -1027,19 +1027,21 @@ public abstract class ObjectListControl<OBJECT_TYPE> extends ProgressPageControl
}
}
monitor.worked(1);
if (!isDisposed()) {
getDisplay().asyncExec(new Runnable() {
@Override
public void run()
{
if (!isDisposed()) {
itemsViewer.update(element, null);
}
}
});
}
}
monitor.done();
if (!isDisposed()) {
// Make refresh of whole table
// Some other objects could also be updated implicitly with our lazy loader
getDisplay().asyncExec(new Runnable() {
@Override
public void run()
{
if (!isDisposed()) {
itemsViewer.refresh();
}
}
});
}
/*
// Update viewer
......
......@@ -20,27 +20,29 @@ package org.jkiss.dbeaver.ui.data.editors;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IContributionManager;
import org.eclipse.jface.resource.JFaceResources;
import org.jkiss.dbeaver.Log;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.ImageData;
import org.eclipse.swt.graphics.ImageLoader;
import org.eclipse.swt.internal.image.FileFormat;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Text;
import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.core.DBeaverUI;
import org.jkiss.dbeaver.model.DBUtils;
import org.jkiss.dbeaver.model.data.DBDContent;
import org.jkiss.dbeaver.model.data.DBDContentStorage;
import org.jkiss.dbeaver.ui.UIIcon;
import org.jkiss.dbeaver.ui.data.IValueController;
import org.jkiss.dbeaver.ui.data.IValueEditorStandalone;
import org.jkiss.dbeaver.model.impl.BytesContentStorage;
import org.jkiss.dbeaver.model.impl.StringContentStorage;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.model.runtime.DBRRunnableWithProgress;
import org.jkiss.dbeaver.ui.DBeaverIcons;
import org.jkiss.dbeaver.ui.UIIcon;
import org.jkiss.dbeaver.ui.controls.imageview.ImageViewer;
import org.jkiss.dbeaver.ui.data.IValueController;
import org.jkiss.dbeaver.ui.data.IValueEditorStandalone;
import org.jkiss.dbeaver.ui.editors.binary.BinaryContent;
import org.jkiss.dbeaver.ui.editors.binary.HexEditControl;
import org.jkiss.dbeaver.utils.ContentUtils;
......@@ -238,11 +240,16 @@ public class ContentPanelEditor extends BaseValueEditor<Control> implements IVal
{
if (!content.isNull()) {
try {
InputStream contentStream = content.getContents(monitor).getContentStream();
try {
new ImageData(contentStream);
} finally {
ContentUtils.close(contentStream);
try (InputStream contentStream = content.getContents(monitor).getContentStream()) {
/*
FileFormat.load(contentStream, new ImageLoader() {
public ImageData[] load(InputStream stream) {
return null;
}
});
*/
new ImageLoader().load(contentStream);
}
isImage = true;
}
......
......@@ -46,14 +46,32 @@ public class UUIDValueManager extends BaseValueManager {
return new StringInlineEditor(controller) {
@Override
public Object extractEditorValue() {
return UUID.fromString(CommonUtils.toString(super.extractEditorValue()));
String strValue = (String) super.extractEditorValue();
if (strValue == null || strValue.isEmpty()) {
return null;
}
try {
return UUID.fromString(CommonUtils.toString(strValue));
} catch (Exception e) {
log.warn(e);
return null;
}
}
};
case EDITOR:
return new TextViewDialog(controller) {
@Override
public Object extractEditorValue() {
return UUID.fromString(CommonUtils.toString(super.extractEditorValue()));
String strValue = (String) super.extractEditorValue();
if (strValue == null || strValue.isEmpty()) {
return null;
}
try {
return UUID.fromString(CommonUtils.toString(super.extractEditorValue()));
} catch (Exception e) {
log.warn(e);
return null;
}
}
};
default:
......
......@@ -39,6 +39,8 @@ import org.jkiss.dbeaver.utils.GeneralUtils;
import org.jkiss.utils.CommonUtils;
import java.io.File;
import java.io.InputStream;
import java.io.Reader;
import java.lang.reflect.InvocationTargetException;
/**
......@@ -173,18 +175,18 @@ public class DialogUtils {
try {
DBDContentStorage storage = value.getContents(monitor);
if (ContentUtils.isTextContent(value)) {
ContentUtils.saveContentToFile(
storage.getContentReader(),
saveFile,
GeneralUtils.DEFAULT_FILE_CHARSET_NAME,
monitor
);
try (Reader cr = storage.getContentReader()) {
ContentUtils.saveContentToFile(
cr,
saveFile,
GeneralUtils.DEFAULT_FILE_CHARSET_NAME,
monitor
);
}
} else {
ContentUtils.saveContentToFile(
storage.getContentStream(),
saveFile,
monitor
);
try (InputStream cs = storage.getContentStream()) {
ContentUtils.saveContentToFile(cs, saveFile, monitor);
}
}
} catch (Exception e) {
throw new InvocationTargetException(e);
......
......@@ -274,7 +274,9 @@ public class ContentEditorInput implements IPathEditorInput, DBPContextProvider
log.warn("Can't get data from null storage");
return;
}
ContentUtils.copyStreams(storage.getContentStream(), storage.getContentLength(), os, monitor);
try (InputStream is = storage.getContentStream()) {
ContentUtils.copyStreams(is, storage.getContentLength(), os, monitor);
}
}
}
......
......@@ -416,17 +416,17 @@ public class SQLEditor extends SQLEditorBase implements
getSite().setSelectionProvider(new DynamicSelectionProvider());
/*
final StyledText textWidget = getTextViewer().getTextWidget();
// TODO: it is a hack. We change selection on each caret move to trigger toolbar elements update.
// TODO: without this hack toolbar won't update Execute* commands.
// TODO: Need some better solution or workaround.
// Execute commands now always enabled. Easy and dirty.
textWidget.addCaretListener(new CaretListener() {
@Override
public void caretMoved(CaretEvent event) {
((SQLEditorSourceViewer)getSourceViewer()).refreshTextSelection();
}
});
*/
createResultTabs();
......
......@@ -213,7 +213,8 @@ public abstract class BaseTextEditor extends AbstractDecoratedTextEditor impleme
public void run(final DBRProgressMonitor monitor) throws InvocationTargetException, InterruptedException
{
try {
ContentUtils.saveContentToFile(new StringReader(document.get()), saveFile, GeneralUtils.DEFAULT_FILE_CHARSET_NAME, monitor);
StringReader cr = new StringReader(document.get());
ContentUtils.saveContentToFile(cr, saveFile, GeneralUtils.DEFAULT_FILE_CHARSET_NAME, monitor);
} catch (Exception e) {
throw new InvocationTargetException(e);
}
......
......@@ -20,7 +20,11 @@ package org.jkiss.dbeaver.ui.resources;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.ui.IEditorDescriptor;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.part.FileEditorInput;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.core.DBeaverUI;
import org.jkiss.dbeaver.model.navigator.DBNNode;
import org.jkiss.dbeaver.model.navigator.DBNResource;
import org.jkiss.dbeaver.ui.UIUtils;
......@@ -74,7 +78,15 @@ public class DefaultResourceHandlerImpl extends AbstractResourceHandler {
public void openResource(IResource resource) throws CoreException, DBException
{
if (resource instanceof IFile) {
UIUtils.launchProgram(resource.getLocation().toFile().getAbsolutePath());
IEditorDescriptor desc = PlatformUI.getWorkbench().
getEditorRegistry().getDefaultEditor(resource.getName());
if (desc != null) {
DBeaverUI.getActiveWorkbenchWindow().getActivePage().openEditor(
new FileEditorInput((IFile) resource),
desc.getId());
} else {
UIUtils.launchProgram(resource.getLocation().toFile().getAbsolutePath());
}
}
}
......
......@@ -58,15 +58,12 @@ public class OracleContentXML extends JDBCContentXML {
{
try {
if (storage != null) {
InputStream streamReader = storage.getContentStream();
try {
try (InputStream streamReader = storage.getContentStream()) {
final Object xmlObject = createXmlObject(session, streamReader);
preparedStatement.setObject(
paramIndex,
xmlObject);
} finally {
ContentUtils.close(streamReader);
}
} else {
preparedStatement.setNull(paramIndex + 1, java.sql.Types.SQLXML);
......
......@@ -31,8 +31,11 @@ import org.jkiss.dbeaver.model.impl.edit.SQLDatabasePersistAction;
import org.jkiss.dbeaver.model.impl.sql.edit.struct.SQLTableColumnManager;
import org.jkiss.dbeaver.model.struct.DBSDataType;
import org.jkiss.dbeaver.model.struct.DBSObject;
import org.jkiss.utils.CommonUtils;
import java.sql.Types;
import java.util.ArrayList;
import java.util.List;
/**
* Postgre table column manager
......@@ -98,19 +101,6 @@ public class PostgreTableColumnManager extends SQLTableColumnManager<PostgreTabl
{
StringBuilder decl = super.getNestedDeclaration(owner, command);
final PostgreAttribute column = command.getObject();
/*
if (!CommonUtils.isEmpty(column.getExtraInfo())) {
decl.append(" ").append(column.getExtraInfo()); //$NON-NLS-1$
}
if (column.isAutoGenerated() &&
(CommonUtils.isEmpty(column.getExtraInfo()) || !column.getExtraInfo().toLowerCase(Locale.ENGLISH).contains(PostgreConstants.EXTRA_AUTO_INCREMENT)))
{
decl.append(" AUTO_INCREMENT"); //$NON-NLS-1$
}
if (!CommonUtils.isEmpty(column.getComment())) {
decl.append(" COMMENT '").append(escapeComment(column.getComment())).append("'"); //$NON-NLS-1$ //$NON-NLS-2$
}
*/
return decl;
}
......@@ -136,11 +126,25 @@ public class PostgreTableColumnManager extends SQLTableColumnManager<PostgreTabl
protected DBEPersistAction[] makeObjectModifyActions(ObjectChangeCommand command)
{
final PostgreAttribute column = command.getObject();
return new DBEPersistAction[] {
new SQLDatabasePersistAction(
"Modify column",
"ALTER TABLE " + DBUtils.getObjectFullName(column.getTable()) + " ALTER COLUMN " + getNestedDeclaration((PostgreTableBase) column.getTable(), command))}; //$NON-NLS-1$ //$NON-NLS-2$
// PostgreSQL can't perform all changes by one query
// ALTER [ COLUMN ] column [ SET DATA ] TYPE data_type [ COLLATE collation ] [ USING expression ]
// ALTER [ COLUMN ] column SET DEFAULT expression
// ALTER [ COLUMN ] column DROP DEFAULT
// ALTER [ COLUMN ] column { SET | DROP } NOT NULL
// ALTER [ COLUMN ] column SET STATISTICS integer
// ALTER [ COLUMN ] column SET ( attribute_option = value [, ... ] )
// ALTER [ COLUMN ] column RESET ( attribute_option [, ... ] )
// ALTER [ COLUMN ] column SET STORAGE { PLAIN | EXTERNAL | EXTENDED | MAIN }
String prefix = "ALTER TABLE " + DBUtils.getObjectFullName(column.getTable()) + " ALTER COLUMN " + DBUtils.getQuotedIdentifier(column) + " ";
List<SQLDatabasePersistAction> actions = new ArrayList<>();
actions.add(new SQLDatabasePersistAction("Set column type", prefix + "TYPE " + column.getFullQualifiedTypeName()));
actions.add(new SQLDatabasePersistAction("Set column nullability", prefix + (column.isRequired() ? "SET" : "DROP") + " NOT NULL"));
if (CommonUtils.isEmpty(column.getDefaultValue())) {
actions.add(new SQLDatabasePersistAction("Drop column default", prefix + "DROP DEFAULT"));
} else {
actions.add(new SQLDatabasePersistAction("Set column default", prefix + "SET DEFAULT " + column.getDefaultValue()));
}
return actions.toArray(new DBEPersistAction[actions.size()]);
}
@Override
......@@ -156,9 +160,9 @@ public class PostgreTableColumnManager extends SQLTableColumnManager<PostgreTabl
return new DBEPersistAction[] {
new SQLDatabasePersistAction(
"Rename column",
"ALTER TABLE " + DBUtils.getObjectFullName(column.getTable()) + " CHANGE " +
DBUtils.getQuotedIdentifier(column.getDataSource(), command.getOldName()) + " " +
getNestedDeclaration((PostgreTableBase) column.getTable(), command))}; //$NON-NLS-1$ //$NON-NLS-2$
"ALTER TABLE " + DBUtils.getObjectFullName(column.getTable()) + " RENAME COLUMN " +
DBUtils.getQuotedIdentifier(column.getDataSource(), command.getOldName()) + " TO " +
DBUtils.getQuotedIdentifier(column))}; //$NON-NLS-1$ //$NON-NLS-2$
}
}
......@@ -22,6 +22,7 @@ import org.jkiss.code.Nullable;
import org.jkiss.dbeaver.DBException;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.ext.postgresql.PostgreUtils;
import org.jkiss.dbeaver.model.DBPDataKind;
import org.jkiss.dbeaver.model.DBPHiddenObject;
import org.jkiss.dbeaver.model.DBPNamedObject2;
import org.jkiss.dbeaver.model.exec.jdbc.JDBCResultSet;
......@@ -30,6 +31,7 @@ import org.jkiss.dbeaver.model.impl.jdbc.JDBCUtils;
import org.jkiss.dbeaver.model.impl.jdbc.struct.JDBCTableColumn;
import org.jkiss.dbeaver.model.meta.IPropertyValueListProvider;
import org.jkiss.dbeaver.model.meta.Property;
import org.jkiss.dbeaver.model.sql.SQLUtils;
import org.jkiss.dbeaver.model.struct.DBSEntity;
import org.jkiss.dbeaver.model.struct.DBSTypedObjectEx;
......@@ -178,8 +180,15 @@ public abstract class PostgreAttribute<OWNER extends DBSEntity & PostgreObject>
return getOrdinalPosition() < 0;
}
public String getFullTypeName() {
return dataType.getTypeName();
public String getFullQualifiedTypeName() {
String fqtn = dataType.getTypeName();
if (dataType.getDataKind() != DBPDataKind.CONTENT) {
String modifiers = SQLUtils.getColumnTypeModifiers(this, fqtn, dataType.getDataKind());
if (modifiers != null) {
return fqtn + modifiers;
}
}
return fqtn;
}
public static class DataTypeListProvider implements IPropertyValueListProvider<PostgreAttribute> {
......
......@@ -28,6 +28,8 @@ class PostgreDialect extends JDBCSQLDialect {
public PostgreDialect(PostgreDataSource dataSource, JDBCDatabaseMetaData metaData) {
super(dataSource, "PostgreSQL", metaData);
addSQLKeyword("SHOW");
addSQLKeyword("TYPE");
addSQLKeyword("USER");
removeSQLKeyword("PUBLIC");
removeSQLKeyword("LENGTH");
removeSQLKeyword("LANGUAGE");
......
......@@ -20,7 +20,9 @@ package org.jkiss.dbeaver.model.impl;
import org.jkiss.dbeaver.Log;
import org.jkiss.dbeaver.model.data.DBDContentStorage;
import org.jkiss.dbeaver.model.runtime.DBRProgressMonitor;
import org.jkiss.dbeaver.utils.ContentUtils;
import org.jkiss.dbeaver.utils.GeneralUtils;
import org.jkiss.utils.IOUtils;
import java.io.*;
import java.util.Arrays;
......@@ -91,12 +93,12 @@ public class BytesContentStorage implements DBDContentStorage {
if (contentLength > Integer.MAX_VALUE) {
throw new IOException("Too big content length for memory storage: " + contentLength);
}
byte[] data = new byte[(int)contentLength];
int count = stream.read(data);
if (count >= 0 && count != contentLength) {
log.warn("Actual content length (" + count + ") is less than declared: " + contentLength);
data = Arrays.copyOf(data, count);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
IOUtils.copyStream(stream, baos);
byte[] result = baos.toByteArray();
if (result.length != contentLength) {
log.warn("Actual content length (" + result.length + ") is less than declared: " + contentLength);
}
return new BytesContentStorage(data, encoding);
return new BytesContentStorage(result, encoding);
}
}
......@@ -69,11 +69,8 @@ public abstract class DBDDocumentContentProxy implements DBDDocument {
DBDContentStorage contents = content.getContents(monitor);
if (contents != null) {
try {
InputStream contentStream = contents.getContentStream();
try {
try (InputStream contentStream = contents.getContentStream()) {
ContentUtils.copyStreams(contentStream, content.getContentLength(), stream, monitor);
} finally {
ContentUtils.close(contentStream);
}
} catch (IOException e) {
throw new DBException("Error copying content stream", e);
......
......@@ -84,10 +84,12 @@ public class JDBCContentBLOB extends JDBCContentLOB {
DBPApplication application = dataSource.getContainer().getApplication();
if (contentLength < application.getPreferenceStore().getInt(ModelPreferences.MEMORY_CONTENT_MAX_SIZE)) {
try {
storage = BytesContentStorage.createFromStream(
blob.getBinaryStream(),
contentLength,
application.getPreferenceStore().getString(ModelPreferences.CONTENT_HEX_ENCODING));
try (InputStream bs = blob.getBinaryStream()) {
storage = BytesContentStorage.createFromStream(
bs,
contentLength,
application.getPreferenceStore().getString(ModelPreferences.CONTENT_HEX_ENCODING));
}
}
catch (SQLException e) {
throw new DBCException(e, dataSource);
......@@ -104,7 +106,9 @@ public class JDBCContentBLOB extends JDBCContentLOB {
throw new DBCException("Can't create temporary file", e);
}
try (OutputStream os = new FileOutputStream(tempFile)) {
ContentUtils.copyStreams(blob.getBinaryStream(), contentLength, os, monitor);
try (InputStream bs = blob.getBinaryStream()) {
ContentUtils.copyStreams(bs, contentLength, os, monitor);
}
} catch (IOException e) {
ContentUtils.deleteTempFile(tempFile);
throw new DBCException("IO error while copying stream", e);
......
......@@ -99,7 +99,9 @@ public class BasicSQLDialect implements SQLDialect {
for (String keyword : set) {
reservedWords.add(keyword);
DBPKeywordType oldType = allKeywords.get(keyword);
if (oldType == null || oldType.ordinal() < type.ordinal()) {
if (oldType != DBPKeywordType.KEYWORD) {
// We can't mark keywords as functions or types because keywords are reserved and
// if some identifier conflicts with keyword it must be quoted.
allKeywords.put(keyword, type);
}
}
......
......@@ -110,42 +110,31 @@ public class ContentUtils {
public static void saveContentToFile(InputStream contentStream, File file, DBRProgressMonitor monitor)
throws IOException
{
try {
try (OutputStream os = new FileOutputStream(file)) {
copyStreams(contentStream, file.length(), os, monitor);
}
// Check for cancel
if (monitor.isCanceled()) {
// Delete output file
if (!file.delete()) {
log.warn("Can't delete incomplete file '" + file.getAbsolutePath() + "'");
}
try (OutputStream os = new FileOutputStream(file)) {
copyStreams(contentStream, file.length(), os, monitor);
}
// Check for cancel
if (monitor.isCanceled()) {
// Delete output file
if (!file.delete()) {
log.warn("Can't delete incomplete file '" + file.getAbsolutePath() + "'");
}
}
finally {
close(contentStream);
}
}
public static void saveContentToFile(Reader contentReader, File file, String charset, DBRProgressMonitor monitor)
throws IOException
{
try {
try (Writer writer = new OutputStreamWriter(new FileOutputStream(file), charset)) {
copyStreams(contentReader, file.length(), writer, monitor);
}
// Check for cancel
if (monitor.isCanceled()) {
// Delete output file
if (!file.delete()) {
log.warn("Can't delete incomplete file '" + file.getAbsolutePath() + "'");
}
try (Writer writer = new OutputStreamWriter(new FileOutputStream(file), charset)) {
copyStreams(contentReader, file.length(), writer, monitor);
}
// Check for cancel
if (monitor.isCanceled()) {
// Delete output file
if (!file.delete()) {
log.warn("Can't delete incomplete file '" + file.getAbsolutePath() + "'");
}
}
finally {
contentReader.close();
}
}
public static void copyStreams(
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册