Commit 706f7425 authored by Matija Obreza's avatar Matija Obreza

Merge branch '26-upload-pause-resume' into 'master'

Resolve "Upload pause-resume"

Closes #26

See merge request !19
parents 85ab4a9a bdfa81df
/*******************************************************************************
* Copyright (c) 2011 Google, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Google, Inc. - initial API and implementation
*******************************************************************************/
/*******************************************************************************
* Copyright (c) 2011 Google, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Google, Inc. - initial API and implementation
*******************************************************************************/
package org.eclipse.wb.swt;
import java.io.FileInputStream;
......
......@@ -17,7 +17,6 @@ package org.genesys2.anno.gui;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
......@@ -29,14 +28,21 @@ import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.StopWatch;
import org.apache.log4j.Level;
import org.apache.log4j.LogManager;
import org.apache.log4j.Logger;
import org.eclipse.core.databinding.DataBindingContext;
import org.eclipse.core.databinding.beans.PojoProperties;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.jface.databinding.swt.WidgetProperties;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.DisposeEvent;
......@@ -44,18 +50,26 @@ import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.FormAttachment;
import org.eclipse.swt.layout.FormData;
import org.eclipse.swt.layout.FormLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Dialog;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.ProgressBar;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.swt.widgets.Text;
import org.eclipse.swt.widgets.ToolBar;
import org.eclipse.swt.widgets.ToolItem;
import org.eclipse.wb.swt.SWTResourceManager;
import org.genesys2.anno.converter.GenesysJSONException;
import org.genesys2.anno.converter.GenesysJSONIncompleteException;
import org.genesys2.anno.converter.RowConverter;
import org.genesys2.anno.model.Column;
import org.genesys2.anno.model.OAuthSettings;
import org.genesys2.anno.model.Settings;
import org.genesys2.anno.parser.RowReader;
......@@ -78,7 +92,6 @@ public class PushDialog extends Dialog {
private static final Logger _log = Logger.getLogger(PushDialog.class);
protected static final int BATCH_SIZE = 50;
protected static final int MAX_UPLOAD_THREADS = 4;
protected static final int MAX_POOL_SIZE = 30;
public static enum GenesysOp {
......@@ -87,6 +100,8 @@ public class PushDialog extends Dialog {
private static Image deleteDataIcon;
private static Image parseDataIcon;
private static Image pauseIcon;
private static Image playIcon;
@Autowired
private Settings settings;
......@@ -97,8 +112,8 @@ public class PushDialog extends Dialog {
@Autowired
protected DataSourceLoader dataSourceLoader;
BlockingQueue<Runnable> linkedBlockingDeque = new LinkedBlockingDeque<Runnable>(MAX_UPLOAD_THREADS * 3);
private ThreadPoolExecutor executorService = new ThreadPoolExecutor(2, MAX_UPLOAD_THREADS + 2, MAX_POOL_SIZE, TimeUnit.SECONDS, linkedBlockingDeque, new ThreadPoolExecutor.CallerRunsPolicy());
BlockingQueue<Runnable> linkedBlockingDeque = new LinkedBlockingDeque<Runnable>(MAX_UPLOAD_THREADS + 2);
private ThreadPoolExecutor executorService = new ThreadPoolExecutor(1, 1 + MAX_UPLOAD_THREADS, 10, TimeUnit.SECONDS, linkedBlockingDeque, new ThreadPoolExecutor.CallerRunsPolicy());
protected Object result;
protected Shell shell;
......@@ -109,11 +124,68 @@ public class PushDialog extends Dialog {
private IDataSource dataSource;
private List<Future<?>> futures = new ArrayList<Future<?>>();
private ToolBar toolBar;
private AtomicBoolean doPushPaused = new AtomicBoolean(true); // paused by default
private AtomicInteger pushRowCount = new AtomicInteger(0); // Number of rows read during push
private AtomicInteger pushAccessionCount = new AtomicInteger(0); // Number of accessions uploaded
private PushProgress pushProgress = new PushProgress();
private Text txtSkipDataRows;
private ProgressBar progressBar;
private Label lblBuckets;
public static class PushProgress {
private int extraSkipRows = 0;
private int sourceRowCount = 0; // Number of rows read during push
private int rowsRead = 0;
private int readProgress = 0; // % (0 - 100)
public PushProgress() {
reset();
}
public void reset() {
rowsRead = 0;
sourceRowCount = 0;
readProgress = 0;
}
public int updateReadProgress(int sourceRowCount, int rowsRead) {
this.sourceRowCount = sourceRowCount;
this.rowsRead = rowsRead;
return this.readProgress = this.sourceRowCount == 0 ? 0 : (int) (100 * ((float) rowsRead / (float) sourceRowCount));
}
public int getReadProgress() {
return readProgress;
}
public void setReadProgress(int readProgress) {
this.readProgress = readProgress;
}
public int getRowsRead() {
return rowsRead;
}
public void setRowsRead(int rowsRead) {
this.rowsRead = rowsRead;
}
public int getExtraSkipRows() {
return extraSkipRows;
}
public void setExtraSkipRows(int extraSkipRows) {
this.extraSkipRows = extraSkipRows;
}
}
static {
deleteDataIcon = ImageDescriptor.createFromURL(PushDialog.class.getResource("/icon/delete.png")).createImage();
parseDataIcon = ImageDescriptor.createFromURL(PushDialog.class.getResource("/icon/parse.png")).createImage();
}
pauseIcon = ImageDescriptor.createFromURL(PushDialog.class.getResource("/icon/pause.png")).createImage();
playIcon = ImageDescriptor.createFromURL(PushDialog.class.getResource("/icon/play.png")).createImage();
}
/**
* Create the dialog.
......@@ -140,8 +212,58 @@ public class PushDialog extends Dialog {
createContents();
logAppender = new SwtLogAppender(this.txtJson);
Composite composite = new Composite(shell, SWT.NO_FOCUS);
composite.setLayoutData(BorderLayout.SOUTH);
FormLayout fl_composite = new FormLayout();
fl_composite.marginBottom = 5;
fl_composite.marginWidth = 10;
composite.setLayout(fl_composite);
Label lblUploaded = new Label(composite, SWT.NONE);
FormData fd_lblUploaded = new FormData();
fd_lblUploaded.top = new FormAttachment(0, 7);
fd_lblUploaded.left = new FormAttachment(0, 5);
lblUploaded.setLayoutData(fd_lblUploaded);
lblUploaded.setText("Uploaded:");
progressBar = new ProgressBar(composite, SWT.NONE);
FormData fd_progressBar = new FormData();
fd_progressBar.top = new FormAttachment(0, 7);
fd_progressBar.left = new FormAttachment(0, 68);
progressBar.setLayoutData(fd_progressBar);
progressBar.setForeground(SWTResourceManager.getColor(SWT.COLOR_DARK_GREEN));
progressBar.setSelection(0);
Label lblUploadStatus = new Label(composite, SWT.NONE);
lblUploadStatus.setAlignment(SWT.RIGHT);
FormData fd_lblUploadStatus = new FormData();
fd_lblUploadStatus.left = new FormAttachment(lblUploaded, 150);
fd_lblUploadStatus.top = new FormAttachment(0, 7);
lblUploadStatus.setLayoutData(fd_lblUploadStatus);
lblUploadStatus.setText("Skip rows:");
txtSkipDataRows = new Text(composite, SWT.BORDER | SWT.RIGHT);
FormData fd_txtSkipDataRows = new FormData();
fd_txtSkipDataRows.width = 100;
fd_txtSkipDataRows.left = new FormAttachment(lblUploadStatus, 5);
fd_txtSkipDataRows.top = new FormAttachment(0, 5);
txtSkipDataRows.setLayoutData(fd_txtSkipDataRows);
txtSkipDataRows.setText("0");
lblBuckets = new Label(composite, SWT.NONE);
lblBuckets.setText("Pending bucket sizes...");
FormData fd_lblBuckets = new FormData();
fd_lblBuckets.width = 1000;
fd_lblBuckets.top = new FormAttachment(lblUploaded, 0, SWT.TOP);
fd_lblBuckets.left = new FormAttachment(txtSkipDataRows, 6);
fd_lblBuckets.right = new FormAttachment(100, -96);
lblBuckets.setLayoutData(fd_lblBuckets);
shell.setTabList(new Control[] { txtJson, toolBar });
logAppender.setConversionPattern("%d{yyyy-MM-dd HH:mm:ss} %t %-5p - %m%n");
initDataBindings();
shell.open();
shell.layout();
......@@ -238,6 +360,20 @@ public class PushDialog extends Dialog {
});
tltmRemove.setText("Remove");
tltmRemove.setImage(PushDialog.deleteDataIcon);
ToolItem tltmPause = new ToolItem(toolBar, SWT.NONE);
tltmPause.setImage(PushDialog.pauseIcon);
tltmPause.setText("Pause");
tltmPause.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
doPushPaused.set(! doPushPaused.get());
_log.warn("Reading data source is " + (doPushPaused.get() ? "paused" : "resumed"));
tltmPause.setText(doPushPaused.get() ? "Go!" : "Pause");
tltmPause.setImage(doPushPaused.get() ? PushDialog.playIcon : PushDialog.pauseIcon);
}
});
ToolItem tltmRadioItemDebug = new ToolItem(toolBar, SWT.RADIO);
tltmRadioItemDebug.addSelectionListener(new SelectionAdapter() {
......@@ -347,31 +483,130 @@ public class PushDialog extends Dialog {
OAuthSettings oauthSettings = settings.getOauthSettings();
_log.info("Starting push to " + oauthSettings.getServerUrl());
final GenesysClient genesysClient = SpringConfig.createGenesysClient(oauthSettings);
this.pushProgress.sourceRowCount = 0;
// Manages pause/resume
doPushPaused.set(false);
pushRowCount.set(0);
pushAccessionCount.set(0);
final int extraSkipRows = this.pushProgress.extraSkipRows;
Future<?> future = executorService.submit(() -> {
final Map<String, TimedList<ObjectNode>> instCodeMap = new HashMap<String, TimedList<ObjectNode>>();
// This thread runs until interrupted by end of upload
// Logs the current line
final Thread uploadMonitor = new Thread(() -> {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
long lastTime = 0;
do {
for (int i = futures.size() - 1; i >= 0; i--) {
Future<?> f = futures.get(i);
if (f.isDone()) {
futures.remove(f);
}
}
int pushedRows = pushRowCount.get();
long currentTime = stopWatch.getTime() / 1000;
if (pushedRows > 0 && (currentTime != lastTime && currentTime % 10 == 0)) {
lastTime = currentTime;
// log every 10s
_log.warn("Read " + pushRowCount + " rows from data source");
}
int readProgress = this.pushProgress.updateReadProgress(this.pushProgress.sourceRowCount, pushedRows);
StringBuffer sb=new StringBuffer();
instCodeMap.keySet().stream().sorted().forEach(bucketInstCode -> {
int pending = instCodeMap.get(bucketInstCode).size();
if (pending > 0) {
if (sb.length() > 0) {
sb.append(" ");
}
sb.append(bucketInstCode + "=" + pending);
}
});
Display.getDefault().asyncExec(() -> {
if (! progressBar.isDisposed()) {
progressBar.setSelection(readProgress);
}
if (! lblBuckets.isDisposed()) {
// if (StringUtils.isNotBlank(sb)) {
// System.err.println(sb);
// }
if (sb.length() > 0) {
sb.insert(0, "Pending: ");
}
if (executorService.getActiveCount() > 1 && futures.size() > 1) {
sb.insert(0, " Queued uploads: " + ((futures.size() - 1) - executorService.getActiveCount()) + " ");
sb.insert(0, "Active uploads: " + (executorService.getActiveCount() - 1));
}
if (StringUtils.isBlank(sb)) {
lblBuckets.setText("Not uploading.");
} else {
lblBuckets.setText(sb.toString());
}
}
});
try {
Thread.sleep(100);
} catch (InterruptedException e) {
_log.warn("Total run time: " + (stopWatch.getTime() / 1000) + "s");
break;
}
} while (true);
}, "UploadMonitor");
uploadMonitor.start();
try {
List<String> instCodesOrder = new ArrayList<>();
try (final RowReader rowReader1 = dataSourceLoader.createRowReader(dataSourceSheet, dataSource)) {
rowReader1.setSkipRows(dataSourceSheet.getHeaderRowIndex() + 1);
_log.warn("Skipping data rows: " + extraSkipRows);
rowReader1.setSkipRows(dataSourceSheet.getHeaderRowIndex() + 1 + extraSkipRows);
Set<String> instCodes = new HashSet<>();
// Read instCodes
List<Object[]> rows = null;
int count = 0;
int instCodeCol = -1;
List<Column> columns = dataSourceSheet.getColumns();
for (int i = 0; i<columns.size(); i++) {
if (RdfMCPD.INSTCODE.equals(columns.get(i).getRdfTerm())) {
instCodeCol = i;
break;
}
}
System.err.println("INSTCODE is at " + instCodeCol);
if (instCodeCol == -1) {
throw new GenesysApiException("INSTCODE is not mapped");
}
do {
while (doPushPaused.get()) {
// Pause reading
Thread.sleep(500);
}
rows = rowReader1.readRows(1000);
for (Object[] row : rows) {
count++;
Map<String, ?> accnMap = RowConverter.toMap(dataSourceSheet, row, columnDefs);
final String accessionInstCode = (String) accnMap.get(RdfMCPD.INSTCODE);
final String accessionInstCode = row[instCodeCol].toString();
if (instCodes.add(accessionInstCode)) {
_log.info("Detected INSTCODE: " + accessionInstCode + " in line " + count);
_log.info("Detected INSTCODE: " + accessionInstCode + " in line " + (extraSkipRows + count));
instCodesOrder.add(accessionInstCode);
}
}
} while (rows != null && rows.size() > 0);
this.pushProgress.sourceRowCount = extraSkipRows + count;
_log.warn("Detected rows: " + this.pushProgress.sourceRowCount);
}
_log.warn("Have instCodes: " + instCodesOrder.stream().sorted().collect(Collectors.toList()));
......@@ -379,111 +614,125 @@ public class PushDialog extends Dialog {
// Ping the server
genesysClient.me();
_log.warn("Queueing upload for " + instCodesOrder.toString());
// Filter only for specific INSTCODES
final Set<String> instCodesFilter = new HashSet<>(); // (instCodesOrder);
final Set<String> instCodesFilter = new HashSet<>();
_log.warn("Running upload for " + instCodesFilter.toString());
try (RowReader rowReader = dataSourceLoader.createRowReader(dataSourceSheet, dataSource)) {
_log.warn("Skipping data rows: " + extraSkipRows);
rowReader.setSkipRows(dataSourceSheet.getHeaderRowIndex() + 1 + extraSkipRows);
pushRowCount.set(extraSkipRows);
try {
do {
if (doPushPaused.get()) {
while (doPushPaused.get()) {
// Pause reading
Thread.sleep(500);
}
try {
genesysClient.me();
} catch (OAuthAuthenticationException e) {
_log.error("Trying reauthentication using refresh token.");
try {
genesysClient.refreshAccessToken();
} catch (OAuthAuthenticationException e1) {
_log.error("Trying Oauth client authentication.");
genesysClient.authenticate();
}
}
}
Future<?> instFuture = executorService.submit(new Runnable() {
final Map<String, List<ObjectNode>> instCodeMap = new HashMap<String, List<ObjectNode>>();
List<Object[]> rows = rowReader.readRows(100);
@Override
public void run() {
_log.warn("Running upload for " + instCodesFilter.toString());
try (RowReader rowReader = dataSourceLoader.createRowReader(dataSourceSheet, dataSource)) {
rowReader.setSkipRows(dataSourceSheet.getHeaderRowIndex() + 1);
if (rows.size() == 0) {
_log.info("Exhausted data source.");
break;
}
int rowCount = 0;
int accessionCount = 0;
try {
do {
List<Object[]> rows = rowReader.readRows(100);
for (Object[] row : rows) {
pushRowCount.incrementAndGet();
_log.trace(pushRowCount.get() + ": " + ArrayUtils.toString(row));
Map<String, ?> accnMap = RowConverter.toMap(dataSourceSheet, row, columnDefs);
final String accessionInstCode = (String) accnMap.get(RdfMCPD.INSTCODE);
if (rows.size() == 0) {
_log.info("Exhausted data source.");
break;
}
// If filtering, check INSTCODE is listed
if (instCodesFilter.size() > 0 && ! instCodesFilter.contains(accessionInstCode)) {
continue;
}
try {
ObjectNode accnJson = RowConverter.toJson(accnMap);
for (Object[] row : rows) {
rowCount++;
_log.trace(rowCount + ": " + ArrayUtils.toString(row));
Map<String, ?> accnMap = RowConverter.toMap(dataSourceSheet, row, columnDefs);
final String accessionInstCode = (String) accnMap.get(RdfMCPD.INSTCODE);
// If filtering, check INSTCODE is listed
if (instCodesFilter.size() > 0 && ! instCodesFilter.contains(accessionInstCode)) {
continue;
}
try {
ObjectNode accnJson = RowConverter.toJson(accnMap);
accessionCount++;
List<ObjectNode> instCodeBatch = null;
synchronized (instCodeMap) {
instCodeBatch = instCodeMap.get(accessionInstCode);
if (instCodeBatch == null) {
instCodeMap.put(accessionInstCode, instCodeBatch = new ArrayList<ObjectNode>());
}
pushAccessionCount.incrementAndGet();
TimedList<ObjectNode> instCodeBatch = null;
synchronized (instCodeMap) {
new ArrayList<>(instCodeMap.keySet()).forEach(pendingInstCode -> {
TimedList<ObjectNode> pendingUpload = instCodeMap.get(pendingInstCode);
if (pendingUpload.isOlderThanSeconds(10)) {
doAsyncPush(operation, genesysClient, pendingInstCode, pendingUpload);
}
});
synchronized (instCodeBatch) {
instCodeBatch.add(accnJson);
if (instCodeBatch.size() >= BATCH_SIZE) {
doAsyncPush(operation, genesysClient, accessionInstCode, instCodeBatch);
}
}
} catch (GenesysJSONIncompleteException e) {
_log.info("Ignoring incomplete accession " + ArrayUtils.toString(row));
instCodeBatch = instCodeMap.get(accessionInstCode);
if (instCodeBatch == null) {
instCodeMap.put(accessionInstCode, instCodeBatch = new TimedList<ObjectNode>());
}
Thread.sleep(1);
}
Thread.sleep(1);
} while (true);
_log.info("Pushing queued data");
for (final String instCode : instCodeMap.keySet()) {
List<ObjectNode> accns = instCodeMap.get(instCode);
if (accns.size() > 0) {
doAsyncPush(operation, genesysClient, instCode, accns);
synchronized (instCodeBatch) {
instCodeBatch.add(accnJson);
if (instCodeBatch.size() >= BATCH_SIZE) {
doAsyncPush(operation, genesysClient, accessionInstCode, instCodeBatch);
}
}
} catch (GenesysJSONIncompleteException e) {
_log.info("Ignoring incomplete accession " + ArrayUtils.toString(row));
}
_log.warn("Done processing upload jobs for " + accessionCount + " accessions");
} catch (GenesysJSONException e) {
_log.error("Genesys JSON conversion failed in batch" + rowCount);
_log.error(e.getMessage(), e);
} catch (InterruptedException e) {
_log.info("Execution was interrupted");
} catch (Throwable e) {
_log.error(e.getMessage(), e);
e.printStackTrace();
} finally {
_log.info("Reader finished.");
Thread.sleep(1);
}
Thread.sleep(1);
} while (true);
_log.info("Pushing queued data");
for (final String instCode : instCodeMap.keySet()) {
List<ObjectNode> accns = instCodeMap.get(instCode);
if (accns.size() > 0) {
doAsyncPush(operation, genesysClient, instCode, accns);
}
}
_log.warn("Done processing upload jobs for " + pushAccessionCount.get() + " accessions");
} catch (GenesysJSONException e) {
_log.error("Genesys JSON conversion failed in batch " + pushRowCount.get());
_log.error(e.getMessage(), e);
} catch (InterruptedException e) {
_log.info("Execution was interrupted");
} catch (Throwable e) {
_log.error(e.getMessage(), e);
e.printStackTrace();
} finally {
_log.info("Reader finished.");
// try {
// rowReader.close();
// } catch (IOException e) {
// _log.error("Failed to close rowReader: " + e.getMessage(), e);
// }
}
} catch (IOException | UnsupportedDataFormatException e) {
_log.error(e.getMessage(), e);
}
}
});
futures.add(instFuture);
} catch (IOException | UnsupportedDataFormatException e) {
_log.error(e.getMessage(), e);
}
// Wait for uploads to finish
do {
......@@ -505,10 +754,13 @@ public class PushDialog extends Dialog {
}
// We're the last job
} while (futures.size() > 1);
} while (futures.size() > 0);
_log.warn("PUSH FINISHED.");
_log.warn("Really.");
// Interrupt the thread so it stops logging
uploadMonitor.interrupt();
} catch (OAuthAuthenticationException e) {
_log.error(e.getMessage(), e);
} catch (PleaseRetryException e) {
......@@ -619,4 +871,13 @@ public class PushDialog extends Dialog {
public void setDataSource(IDataSource dataSource) {
this.dataSource = dataSource;
}
protected DataBindingContext initDataBindings() {
DataBindingContext bindingContext = new DataBindingContext();
//
IObservableValue observeTextTextObserveWidget = WidgetProperties.text(SWT.Modify).observe(txtSkipDataRows);
IObservableValue extraSkipRowsPushProgressObserveValue = PojoProperties.value("extraSkipRows").observe(pushProgress);
bindingContext.bindValue(observeTextTextObserveWidget, extraSkipRowsPushProgressObserveValue, null, null);
//
return bindingContext;
}
}
......@@ -15,16 +15,12 @@
*/
package org.genesys2.anno.gui;
import java.util.concurrent.atomic.AtomicBoolean;
import org.apache.log4j.LogManager;
import org.apache.log4j.PatternLayout;
import org.apache.log4j.WriterAppender;
import org.apache.log4j.spi.LoggingEvent;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.events.PaintEvent;
import org.eclipse.swt.events.PaintListener;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Text;
......@@ -33,9 +29,8 @@ public class SwtLogAppender extends WriterAppender {
private static final String DEFAULT_LAYOUT_PATTERN = "%d{yyyy-MM-dd HH:mm:ss} %t %-5p %c{1}:%L - %m%n";
private Text text;
private Display display;
private int maxBufferSize = 100000;
private int maxBufferSize = 1000000;
private StringBuffer logBuffer = new StringBuffer();
private AtomicBoolean resetText = new AtomicBoolean(true);
public SwtLogAppender(Text text2) {
this.text = text2;
......@@ -45,15 +40,6 @@ public class SwtLogAppender extends WriterAppender {
close();
}
});
text.addPaintListener(new PaintListener() {
@Override
public void paintControl(PaintEvent e) {
if (resetText.getAndSet(false)) {
text.setText(logBuffer.toString());
text.setSelection(logBuffer.length());
}
}
});
display = text.getDisplay();
LogManager.getRootLogger().addAppender(this);
PatternLayout patternLayout = new org.apache.log4j.PatternLayout();
......@@ -98,14 +84,15 @@ public class SwtLogAppender extends WriterAppender {
logBuffer.replace(0, pos + 1, "");
}
}
resetText.set(true);
// System.err.println(mes);
display.asyncExec(new Runnable() {
@Override
public void run() {
if (text != null) {
text.redraw();
display.asyncExec(() -> {
if (text != null) {
int scrollPos = text.getLineCount() - 1;
text.setText(logBuffer.toString());
int topIndex = text.getTopIndex();
if ((scrollPos <= 0 && topIndex == 0) || topIndex >= scrollPos - 3) {