package com.dreikraft.axbo.task;
import com.dreikraft.axbo.Axbo;
import com.dreikraft.axbo.data.AxboCommandUtil;
import static com.dreikraft.axbo.data.AxboCommandUtil.MEM_PAGE_SIZE;
import com.dreikraft.axbo.data.AxboDataParser;
import com.dreikraft.axbo.data.DataInterfaceException;
import com.dreikraft.axbo.data.DeviceContext;
import com.dreikraft.axbo.events.SoundUpload;
import com.dreikraft.axbo.sound.Sound;
import com.dreikraft.axbo.sound.SoundPackage;
import com.dreikraft.axbo.sound.SoundPackageException;
import com.dreikraft.axbo.sound.SoundPackageUtil;
import com.dreikraft.axbo.util.BundleUtil;
import com.dreikraft.events.ApplicationEventDispatcher;
import java.io.BufferedInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ExecutionException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
/**
* Background task for uploading a SoundPackage from file system to aXbo.
*
* @author jan.illetschko@3kraft.com
*/
public class SoundPackageUploadTask extends AxboTask<SoundPackage, String> {
private static final Log log = LogFactory.getLog(SoundPackageUploadTask.class);
private final File soundPackageFile;
/**
* Creates a new upload task.
*
* @param soundPackageFile an aXbo sound package file
*/
public SoundPackageUploadTask(final File soundPackageFile) {
this.soundPackageFile = soundPackageFile;
}
/**
* Uploads a sound package from file to aXbo in background.
*
* @return the sound package
* @throws Exception if the upload fails.
*/
@Override
protected SoundPackage doInBackground() throws Exception {
log.info("performing task" + getClass().getSimpleName() + " ...");
// read sound package meta data
final SoundPackage soundPackage = SoundPackageUtil.readPackageInfo(
SoundPackageUtil.getPackageEntryStream(soundPackageFile,
SoundPackageUtil.PACKAGE_INFO));
soundPackage.setPackageFile(soundPackageFile);
// synchronize state between axbo and PC
final String portName = Axbo.getPortName();
AxboCommandUtil.syncInterface(Axbo.getPortName());
// clear the aXbo memory header
AxboCommandUtil.clearHeader(portName);
// reset mem params
int startPage = 1;
int step = 0;
final int stepCount = soundPackage.getSounds().size();
final float stepRate = 100 / (float) stepCount;
// for each sound
for (Sound sound : soundPackage.getSounds()) {
InputStream soundIn = null;
ByteArrayOutputStream byteOut;
ByteArrayOutputStream headerOut;
try {
if (log.isDebugEnabled()) {
log.debug("soundpackage upload progress: " + getProgress());
}
// first remove wav header header and tail
// aXbo only uses the raw data of the ulaw encoded wav file
soundIn =
new BufferedInputStream(SoundPackageUtil.getPackageEntryStream(
soundPackage.getPackageFile(),
SoundPackageUtil.SOUNDS_PATH_PREFIX + SoundPackageUtil.SL + sound.
getAxboFile().getPath()), AxboCommandUtil.BUF_SIZE);
// write sound file to byte array
byteOut = new ByteArrayOutputStream();
headerOut = new ByteArrayOutputStream();
int pos = 0;
int b;
int dataLen = Integer.MAX_VALUE;
boolean isData = false;
// skip tail
while (((b = soundIn.read()) != -1) && pos < dataLen) {
// only write data
if (isData) {
byteOut.write(b);
pos++;
} else if (containsDataKeyword(headerOut)) {
// get sound data length
dataLen = b + soundIn.read() * 256 + soundIn.read() * 256 * 256
+ soundIn.
read() * 256 * 256 * 256;
isData = true;
} else {
// skip header
headerOut.write(b);
}
}
byteOut.flush();
// fill the last page with 0xFF
int pageSize = MEM_PAGE_SIZE * AxboDataParser.INSTANCE.getMemSize();
int pageCount = pos / pageSize;
final int rest = pos - (pageCount * pageSize);
if (rest > 0) {
byte[] fillBytes = new byte[pageSize - rest];
Arrays.fill(fillBytes, (byte) 0xFF);
byteOut.write(fillBytes);
pageCount++;
}
// write sound data
byte[] soundData = byteOut.toByteArray();
sound.setData(soundData);
sound.setStartPage(startPage);
sound.setPageCount(pageCount);
publish(sound.getName());
writeSoundData(portName, sound, step, stepRate);
// new startFrame
startPage += pageCount;
step++;
setProgress((int) (step * stepRate));
} catch (IOException | SoundPackageException ex) {
throw new DataInterfaceException(ex.getMessage(), ex);
} finally {
try {
if (soundIn != null) {
soundIn.close();
}
} catch (IOException ex) {
log.warn(ex.getMessage(), ex);
}
}
}
// write the sounds header table into the first axbo memory page
publish(BundleUtil.getMessage("sound.header"));
AxboCommandUtil.writeHeader(portName, soundPackage);
setProgress(100);
return soundPackage;
}
private boolean containsDataKeyword(ByteArrayOutputStream headerOut)
throws UnsupportedEncodingException, IOException {
headerOut.flush();
return headerOut.toString("US-ASCII").contains("data");
}
private void writeSoundData(final String portName, final Sound sound,
final int step, final float stepRate)
throws DataInterfaceException {
// calculate frame count (use half sized frames, because of extra info bytes)
for (int page = 0; page < sound.getPageCount(); page++) {
if (log.isDebugEnabled()) {
log.debug("writing sound: " + sound.getName() + ", page: " + (sound.
getStartPage() + page));
}
setProgress((int) (step * stepRate
+ stepRate * page / sound.getPageCount()));
// write complete page into buffer
AxboCommandUtil.writePage(portName, sound.getData(), page);
// write buffer to flash page
AxboCommandUtil.writeBufferToPage(portName, sound.getStartPage() + page);
}
}
/**
* Wait for task to finish. Notifies the GUI.
*/
@Override
protected void done() {
try {
final SoundPackage soundPackage = get();
log.info("task " + getClass().getSimpleName() + " performed successfully");
setResult(AxboTask.Result.SUCCESS);
} catch (InterruptedException | ExecutionException ex) {
log.error("failed to upload sound package", ex);
setResult(AxboTask.Result.FAILED);
}
DeviceContext.getDeviceType().getDataInterface().stop();
}
/**
* Updates the current progress of the upload and notifies the GUI.
*
* @param sounds the currently processed sounds.
*/
@Override
protected void process(final List<String> sounds) {
for (final String sound : sounds) {
ApplicationEventDispatcher.getInstance().dispatchGUIEvent(new SoundUpload(
this, sound));
}
}
}