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:
@@ -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());
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user