/*
Copyright 2009 David Revell
This file is part of SwiFTP.
SwiFTP is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
SwiFTP is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with SwiFTP. If not, see <http://www.gnu.org/licenses/>.
*/
package org.swiftp;
/**
* Since STOR and APPE are essentially identical except for append vs truncate,
* the common code is in this class, and inherited by CmdSTOR and CmdAPPE.
*/
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import android.util.Log;
abstract public class CmdAbstractStore extends FtpCmd {
public static final String message = "TEMPLATE!!";
public CmdAbstractStore(SessionThread sessionThread, String input) {
super(sessionThread, CmdAbstractStore.class.toString());
}
public void doStorOrAppe(String param, boolean append) {
myLog.l(Log.DEBUG, "STOR/APPE executing with append=" + append);
File storeFile = inputPathToChrootedFile(sessionThread.getWorkingDir(), param);
String errString = null;
FileOutputStream out = null;
//DedicatedWriter dedicatedWriter = null;
// int origPriority = Thread.currentThread().getPriority();
// myLog.l(Log.DEBUG, "STOR original priority: " + origPriority);
storing: {
// Get a normalized absolute path for the desired file
if(violatesChroot(storeFile)) {
errString = "550 Invalid name or chroot violation\r\n";
break storing;
}
if(storeFile.isDirectory()) {
errString = "451 Can't overwrite a directory\r\n";
break storing;
}
try {
if(storeFile.exists()) {
if(!append) {
if(!storeFile.delete()) {
errString = "451 Couldn't truncate file\r\n";
break storing;
}
// Notify other apps that we just deleted a file
Util.deletedFileNotify(storeFile.getPath());
}
}
out = new FileOutputStream(storeFile, append);
} catch(FileNotFoundException e) {
try {
errString = "451 Couldn't open file \"" + param + "\" aka \"" +
storeFile.getCanonicalPath() + "\" for writing\r\n";
} catch (IOException io_e) {
errString = "451 Couldn't open file, nested exception\r\n";
}
break storing;
}
if(!sessionThread.startUsingDataSocket()) {
errString = "425 Couldn't open data socket\r\n";
break storing;
}
myLog.l(Log.DEBUG, "Data socket ready");
sessionThread.writeString("150 Data socket ready\r\n");
byte[] buffer = new byte[Defaults.getDataChunkSize()];
//dedicatedWriter = new DedicatedWriter(out);
//dedicatedWriter.start(); // start the writer thread executing
//myLog.l(Log.DEBUG, "Started DedicatedWriter");
int numRead;
// Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
// int newPriority = Thread.currentThread().getPriority();
// myLog.l(Log.DEBUG, "New STOR prio: " + newPriority);
if(sessionThread.isBinaryMode() ) {
myLog.d("Mode is binary");
} else {
myLog.d("Mode is ascii");
}
int bytesSinceReopen = 0;
int bytesSinceFlush = 0;
while(true) {
/*if(dedicatedWriter.checkErrorFlag()) {
errString = "451 File IO problem\r\n";
break storing;
}*/
switch(numRead = sessionThread.receiveFromDataSocket(buffer)) {
case -1:
myLog.l(Log.DEBUG, "Returned from final read");
// We're finished reading
break storing;
case 0:
errString = "426 Couldn't receive data\r\n";
break storing;
case -2:
errString = "425 Could not connect data socket\r\n";
break storing;
default:
// myLog.d("Read " + numRead + " bytes from socket");
try {
//myLog.l(Log.DEBUG, "Enqueueing buffer of " + numRead);
//dedicatedWriter.enqueueBuffer(buffer, numRead);
if(sessionThread.isBinaryMode()) {
out.write(buffer, 0, numRead);
} else {
// ASCII mode, substitute \r\n to \n
int startPos=0, endPos;
for(endPos = 0; endPos < numRead; endPos++ ) {
if(buffer[endPos] == '\r') {
// Our hacky method is to drop all \r
out.write(buffer, startPos, endPos-startPos);
startPos = endPos+1;
}
}
// Write last part of buffer as long as there was something
// left after handling the last \r
if(startPos < numRead) {
out.write(buffer, startPos, endPos-startPos);
}
}
// Attempted bugfix for transfer stalls. Reopen file periodically.
//bytesSinceReopen += numRead;
//if(bytesSinceReopen >= Defaults.bytes_between_reopen &&
// Defaults.do_reopen_hack) {
// myLog.d("Closing and reopening file: " + storeFile);
// out.close();
// out = new FileOutputStream(storeFile, true/*append*/);
// bytesSinceReopen = 0;
//}
// Attempted bugfix for transfer stalls. Flush file periodically.
//bytesSinceFlush += numRead;
//if(bytesSinceFlush >= Defaults.bytes_between_flush &&
// Defaults.do_flush_hack) {
// myLog.d("Flushing: " + storeFile);
// out.flush();
// bytesSinceFlush = 0;
//}
// If this transfer fails, a later APPEND operation might be
// received. In that case, we will need to have flushed the
// previous writes in order for the append to work. The
// filesystem on my G1 doesn't seem to recognized unflushed
// data when appending.
out.flush();
} catch (IOException e) {
errString = "451 File IO problem. Device might be full.\r\n";
myLog.d("Exception while storing: " + e);
myLog.d("Message: " + e.getMessage());
myLog.d("Stack trace: ");
StackTraceElement[] traceElems = e.getStackTrace();
for(StackTraceElement elem : traceElems) {
myLog.d(elem.toString());
}
break storing;
}
break;
}
}
}
// // Clean up the dedicated writer thread
// if(dedicatedWriter != null) {
// dedicatedWriter.exit(); // set its exit flag
// dedicatedWriter.interrupt(); // make sure it wakes up to process the flag
// }
// Thread.currentThread().setPriority(origPriority);
try {
// if(dedicatedWriter != null) {
// dedicatedWriter.exit();
// }
if(out != null) {
out.close();
}
} catch (IOException e) {}
if(errString != null) {
myLog.l(Log.INFO, "STOR error: " + errString.trim());
sessionThread.writeString(errString);
} else {
sessionThread.writeString("226 Transmission complete\r\n");
// Notify the music player (and possibly others) that a few file has
// been uploaded.
Util.newFileNotify(storeFile.getPath());
}
sessionThread.closeDataSocket();
myLog.l(Log.DEBUG, "STOR finished");
}
}