1
0
mirror of https://github.com/Rogiel/httpchannel synced 2025-12-06 07:32:50 +00:00

Implements SeekableDownloadChannel

The SeekableDownloadChannel implements SeekableByteChannel and allows to
set the position of the channel and read data from that point onward.
The SeekableDownloadChannel implementation, creates a new internal
DownloadChannel at every call to position(long), that will create a
resume channel onto that point, thus, not all service implementations
are supported.
This commit is contained in:
2012-01-17 23:11:03 -02:00
parent ff4abca387
commit a4c569f10e
29 changed files with 581 additions and 61 deletions

View File

@@ -0,0 +1,232 @@
/**
*
*/
package com.rogiel.httpchannel.channel;
import java.io.IOException;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.channels.NonWritableChannelException;
import java.nio.channels.SeekableByteChannel;
import com.rogiel.httpchannel.service.DownloadChannel;
import com.rogiel.httpchannel.service.DownloadService;
import com.rogiel.httpchannel.service.Downloader;
import com.rogiel.httpchannel.service.DownloaderCapability;
/**
* Creates a pseudo-seekable {@link DownloadChannel}. This implementations opens
* a new connection on every call to {@link #position(long)} and thus might
* consume a lot of bandwidth to start downloads. Also, some services do not
* support download resuming, those services are not supported by
* {@link SeekableDownloadChannel}.
* <p>
* You can use {@link #isSupported(DownloadChannel)} or
* {@link #isSupported(DownloadService)} to check whether an channel or a
* service is supported.
*
* @author <a href="http://www.rogiel.com">Rogiel</a>
* @see SeekableDownloadChannel#isSupported(DownloadChannel)
* @see SeekableDownloadChannel#isSupported(DownloadService))
*/
public class SeekableDownloadChannel implements DownloadChannel,
SeekableByteChannel {
/**
* The current opened channel.
* <p>
* This channel will be swapped at every call to {@link #position(long)}.
*/
private DownloadChannel channel;
/**
* The current channel position
*/
private long position = 0;
/**
* Creates a new {@link SeekableDownloadChannel} using an base
* {@link DownloadChannel}
*
* @param channel
* the base {@link DownloadChannel}
* @throws IOException
* if the channel is not supported
* @see SeekableDownloadChannel#isSupported(DownloadChannel)
*/
private SeekableDownloadChannel(DownloadChannel channel,
boolean closeIfNotSupported) throws IOException {
if (!isSupported(channel)) {
if (closeIfNotSupported)
channel.close();
throw new IOException("This channel is not supported");
}
this.channel = channel;
}
/**
* Creates a new {@link SeekableDownloadChannel} using an base
* {@link DownloadChannel}. If not supported, an {@link IOException} is
* thrown and the channel is <b>not</b> closed.
*
* @param channel
* the base {@link DownloadChannel}
* @throws IOException
* if the channel is not supported
*/
public SeekableDownloadChannel(DownloadChannel channel) throws IOException {
this(channel, false);
}
/**
* Creates a new {@link SeekableDownloadChannel} using an base
* {@link Downloader}
*
* @param downloader
* the base {@link Downloader}
* @throws IOException
* if any exception occur while opening the channel or if the
* channel is not supported
*/
public SeekableDownloadChannel(Downloader<?> downloader) throws IOException {
this(downloader.openChannel(), true);
}
/**
* Creates a new {@link SeekableDownloadChannel} using an base
* {@link DownloadService} and an {@link URI}.
*
* @param service
* the base {@link DownloadService}
* @param uri
* the base {@link URI}
* @throws IOException
* if any exception occur while opening the channel or if the
* channel is not supported
*/
public SeekableDownloadChannel(DownloadService<?> service, URI uri)
throws IOException {
this(service.getDownloader(uri));
}
@Override
public long size() throws IOException {
return channel.size();
}
@Override
public String filename() throws IOException {
return channel.filename();
}
@Override
public DownloadService<?> getService() {
return channel.getService();
}
@Override
public Downloader<?> getDownloader() {
return channel.getDownloader();
}
@Override
public boolean isOpen() {
return channel.isOpen();
}
@Override
public void close() throws IOException {
channel.close();
channel = null;
position = 0;
}
@Override
public int read(ByteBuffer dst) throws IOException {
int read = channel.read(dst);
position += read;
return read;
}
/**
* This implementation always throws an {@link NonWritableChannelException}.
* <p>
*
* {@inheritDoc}
*
* @throws NonWritableChannelException
* always (download channels are read-only)
*/
@Override
public int write(ByteBuffer src) throws IOException {
throw new NonWritableChannelException();
}
@Override
public long position() throws IOException {
return position;
}
@Override
public SeekableDownloadChannel position(long newPosition)
throws IOException {
// closes the current channel
channel.close();
// now open a new channel
this.position = newPosition;
channel = channel.getDownloader().openChannel(position);
return this;
}
/**
* Always throws an {@link NonWritableChannelException}
*
* @throws NonWritableChannelException
* always (download channels are read-only)
*/
@Override
public SeekableDownloadChannel truncate(long size) throws IOException {
throw new NonWritableChannelException();
}
/**
* @return the current {@link DownloadChannel}
*/
public DownloadChannel channel() {
return channel;
}
/**
* Checks whether the given <code>service</code> supports
* {@link SeekableDownloadChannel}
*
* @param service
* the service
* @return <code>true</code> if the service is supported
*/
public static boolean isSupported(DownloadService<?> service) {
switch (service.getServiceMode()) {
case UNAUTHENTICATED:
return service.getDownloadCapabilities().has(
DownloaderCapability.UNAUTHENTICATED_RESUME);
case NON_PREMIUM:
return service.getDownloadCapabilities().has(
DownloaderCapability.NON_PREMIUM_ACCOUNT_RESUME);
case PREMIUM:
return service.getDownloadCapabilities().has(
DownloaderCapability.PREMIUM_ACCOUNT_RESUME);
default:
return false;
}
}
/**
* Checks whether the given <code>channel</code> supports
* {@link SeekableDownloadChannel}
*
* @param channel
* the channelO
* @return <code>true</code> if the channel is supported
*/
public static boolean isSupported(DownloadChannel channel) {
return isSupported(channel.getService());
}
}

View File

@@ -25,7 +25,9 @@ import com.rogiel.httpchannel.captcha.exception.UnsolvableCaptchaServiceExceptio
import com.rogiel.httpchannel.service.exception.NoCaptchaServiceException;
/**
* This is an abstract {@link Service} implementation.
* This is an abstract {@link Service} implementation. Service implementators
* should try to implement this abstract class instead of directly implementing
* {@link Service}.
*
* @author <a href="http://www.rogiel.com">Rogiel</a>
* @version 1.0
@@ -35,8 +37,22 @@ public abstract class AbstractService implements Service {
* The service {@link Logger} instance
*/
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
/**
* The currently active service mode
*/
protected ServiceMode serviceMode = ServiceMode.UNAUTHENTICATED;
/**
* This service {@link CaptchaService} that is used to resolve CAPTCHAS
*/
protected CaptchaService<Captcha> captchaService;
@Override
public ServiceMode getServiceMode() {
return serviceMode;
}
@Override
public Service clone() {
try {

View File

@@ -32,4 +32,14 @@ import java.nio.channels.ReadableByteChannel;
* @author <a href="http://www.rogiel.com">Rogiel</a>
*/
public interface DownloadChannel extends HttpChannel, ReadableByteChannel {
/**
* @return the service instance providing this download
*/
@Override
DownloadService<?> getService();
/**
* @return the downloader providing this download
*/
Downloader<?> getDownloader();
}

View File

@@ -87,7 +87,7 @@ public interface Downloader<C extends DownloaderConfiguration> {
NoCaptchaServiceException;
/**
* Opens a new {@link DownloadChannel} with not listener. For more details,
* Opens a new {@link DownloadChannel} with no listener. For more details,
* see {@link #openChannel(DownloadListener, long)}
*
* @param position
@@ -157,7 +157,7 @@ public interface Downloader<C extends DownloaderConfiguration> {
NoCaptchaServiceException;
/**
* Opens a new {@link DownloadChannel} with not listener and positioned at
* Opens a new {@link DownloadChannel} with no listener and positioned at
* start. For more details, see {@link #openChannel(DownloadListener, long)}
* <p>
* Note that {@link DownloadNotResumableException} is never thrown because

View File

@@ -4,6 +4,7 @@
package com.rogiel.httpchannel.service;
import java.io.Closeable;
import java.io.IOException;
import java.nio.channels.Channel;
/**
@@ -14,10 +15,15 @@ public interface HttpChannel extends Channel, Closeable {
/**
* @return the file size
*/
long getFilesize();
long size() throws IOException;
/**
* @return the file name
*/
String getFilename();
String filename() throws IOException;
/**
* @return the service providing data to this channel
*/
Service getService();
}

View File

@@ -53,6 +53,23 @@ public interface Service extends Cloneable {
*/
int getMinorVersion();
/**
* Returns the currently active service mode. The mode is not static and can
* be changed with an {@link Authenticator}
*
* @return the service mode
*/
ServiceMode getServiceMode();
/**
* Return the matrix of supported modes for this {@link Service}.
*
* @return {@link CapabilityMatrix} with all supported modes of this
* {@link Service}.
* @see DownloaderCapability
*/
CapabilityMatrix<ServiceMode> getPossibleServiceModes();
/**
* Sets this service captcha service. CaptchaService are safe to be switched
* even after an transfer has begun.

View File

@@ -0,0 +1,14 @@
/**
*
*/
package com.rogiel.httpchannel.service;
/**
* The available modes on the service. Defines if it is using an
* unauthenticated, non-premium or premium mode.
*
* @author <a href="http://www.rogiel.com">Rogiel</a>
*/
public enum ServiceMode {
UNAUTHENTICATED, NON_PREMIUM, PREMIUM;
}

View File

@@ -45,6 +45,17 @@ public interface UploadChannel extends HttpChannel, WritableByteChannel {
*/
URI getDownloadLink();
/**
* @return the service instance providing this upload
*/
@Override
UploadService<?> getService();
/**
* @return the {@link Uploader} providing this upload
*/
Uploader<?> getUploader();
/**
* @throws UploadLinkNotFoundException
* if after the upload, the download link cannot be found

View File

@@ -14,4 +14,7 @@ import com.rogiel.httpchannel.service.Authenticator.AuthenticatorConfiguration;
public final class NullAuthenticatorConfiguration implements
AuthenticatorConfiguration {
public static final NullAuthenticatorConfiguration SHARED_INSTANCE = new NullAuthenticatorConfiguration();
private NullAuthenticatorConfiguration() {
}
}

View File

@@ -13,4 +13,7 @@ import com.rogiel.httpchannel.service.Downloader.DownloaderConfiguration;
public final class NullDownloaderConfiguration implements
DownloaderConfiguration {
public static final NullDownloaderConfiguration SHARED_INSTANCE = new NullDownloaderConfiguration();
private NullDownloaderConfiguration() {
}
}

View File

@@ -12,4 +12,7 @@ import com.rogiel.httpchannel.service.Uploader.UploaderConfiguration;
*/
public final class NullUploaderConfiguration implements UploaderConfiguration {
public static final NullUploaderConfiguration SHARED_INSTANCE = new NullUploaderConfiguration();
private NullUploaderConfiguration() {
}
}

View File

@@ -25,13 +25,41 @@ import com.rogiel.httpchannel.service.Credential;
* @author <a href="http://www.rogiel.com">Rogiel</a>
*/
public class AuthenticationServices {
public static <S extends AuthenticationService<C>, C extends AuthenticatorConfiguration> Authenticator<C> authenticate(
/**
* Creates a new {@link Credential} with <code>username</code> and
* <code>password</code> and creates a new {@link Authenticator} with it and
* <code>configuration</code>. {@link Authenticator#login()} is not called.
*
* @param service
* the service
* @param configuration
* the authenticator configuration
* @param username
* the username
* @param password
* the password
* @return a newly created {@link Authenticator}
*/
public static <S extends AuthenticationService<C>, C extends AuthenticatorConfiguration> Authenticator<C> authenticator(
S service, C configuration, String username, String password) {
return service.getAuthenticator(new Credential(username, password),
configuration);
}
public static <S extends AuthenticationService<C>, C extends AuthenticatorConfiguration> Authenticator<C> authenticate(
/**
* Creates a new {@link Credential} with <code>username</code> and
* <code>password</code> and creates a new {@link Authenticator} with it.
* {@link Authenticator#login()} is not called.
*
* @param service
* the service
* @param username
* the username
* @param password
* the password
* @return a newly created {@link Authenticator}
*/
public static <S extends AuthenticationService<C>, C extends AuthenticatorConfiguration> Authenticator<C> authenticator(
S service, String username, String password) {
return service.getAuthenticator(new Credential(username, password));
}

View File

@@ -25,6 +25,19 @@ import com.rogiel.httpchannel.service.DownloadService;
* @author <a href="http://www.rogiel.com">Rogiel</a>
*/
public class DownloadServices {
/**
* Checks whether the given <code>uri</code> can be downloaded with the
* {@link DownloadService} <code>service</code>.
*
* @param service
* the {@link DownloadService}
* @param uri
* the checking {@link URI}
* @return <code>true</code> if this {@link URI} can be downloaded with
* <code>service</code>
* @throws IOException
* if any exception is thrown while checking
*/
public static boolean canDownload(DownloadService<?> service, URI uri)
throws IOException {
return service.matchURI(uri);

View File

@@ -28,18 +28,58 @@ import com.rogiel.httpchannel.service.Uploader.UploaderConfiguration;
* @author <a href="http://www.rogiel.com">Rogiel</a>
*/
public class UploadServices {
/**
* Creates a new {@link Uploader} for the given NIO {@link Path}, using
* <code>configuration</code> as the {@link Uploader} configuration.
*
* @param service
* the upload service
* @param configuration
* the uploader configuration
* @param path
* the NIO.2 {@link Path}
* @return a newly created {@link Uploader}
* @throws IOException
* if any exception occur while fetching {@link Path}
* information
*/
public static <S extends UploadService<C>, C extends UploaderConfiguration> Uploader<C> upload(
S service, C configuration, Path path) throws IOException {
return service.getUploader(path.getFileName().toString(),
Files.size(path), configuration);
}
/**
* Creates a new {@link Uploader} for the given NIO {@link Path}.
*
* @param service
* the upload service
* @param path
* the NIO.2 {@link Path}
* @return a newly created {@link Uploader}
* @throws IOException
* if any exception occur while fetching {@link Path}
* information
*/
public static <S extends UploadService<C>, C extends UploaderConfiguration> Uploader<C> upload(
S service, Path path) throws IOException {
return service.getUploader(path.getFileName().toString(),
Files.size(path));
}
/**
* Checks whether the given <code>service</code> can upload the file
* represented by <code>path</code>
*
* @param service
* the upload service
* @param path
* the file {@link Path}
* @return <code>true</code> if the upload will be acepted
* @throws IOException
* if any exception occur while fetching {@link Path}
* information
*/
public static boolean canUpload(UploadService<?> service, Path path)
throws IOException {
return service.getMaximumFilesize() >= Files.size(path);