public final long uploadNextChunk(long numBytesToUpload) {
        if(currentUploadFile == null) {
            err = "no upload is in progress";
            return -1;
        }

        long max = sz - offset;
        long maxBytesWeCanUpload = Math.min(max, numBytesToUpload);

        int actualBytes = 0;
        if(maxBytesWeCanUpload > 0) {
            // read in the max number of bytes we can
            try {
                actualBytes = currentUploadFile.read(buffer, 0, (int)maxBytesWeCanUpload);
            } catch (IOException e) {
                haltWithError(e.getMessage());
                return -1;
            }
            if(actualBytes < 0) {
                haltWithError("unexpected end of file");
                return -1;
            }

            // send the bytes
            if(!tryToUploadNextChunk(buffer, actualBytes)) {
                // set a generic error message if tryToUploadNextChunk() didn't
                if(err == null)
                    haltWithError("upload failed");
                return -1;
            }
            offset += actualBytes;
        }

        if(this.isUploadComplete()) {
            if(!finalizeUpload()) {
                if(err==null)
                    haltWithError("couldn't finalize"); // generic error if the subclass didn't set one
                return -1;
            }

            closeFile();
            currentUploadFile = null;
            currentUploadFileObj = null;
            err = null;
        }
        return actualBytes;
    }