diff --git a/src/main/java/net/dv8tion/jda/api/utils/FileUpload.java b/src/main/java/net/dv8tion/jda/api/utils/FileUpload.java index e5ee426d30..6126253b85 100644 --- a/src/main/java/net/dv8tion/jda/api/utils/FileUpload.java +++ b/src/main/java/net/dv8tion/jda/api/utils/FileUpload.java @@ -18,13 +18,16 @@ import net.dv8tion.jda.api.utils.data.DataObject; import net.dv8tion.jda.internal.requests.Requester; -import net.dv8tion.jda.internal.utils.BufferedRequestBody; import net.dv8tion.jda.internal.utils.Checks; import net.dv8tion.jda.internal.utils.EntityString; import net.dv8tion.jda.internal.utils.IOUtil; +import net.dv8tion.jda.internal.utils.requestbody.DataSupplierBody; +import net.dv8tion.jda.internal.utils.requestbody.TypedBody; import okhttp3.MediaType; import okhttp3.MultipartBody; import okhttp3.RequestBody; +import okio.Okio; +import okio.Source; import javax.annotation.Nonnull; import javax.annotation.Nullable; @@ -32,6 +35,7 @@ import java.nio.file.Files; import java.nio.file.OpenOption; import java.nio.file.Path; +import java.util.function.Supplier; /** * Represents a file that is intended to be uploaded to Discord for arbitrary requests. @@ -43,16 +47,80 @@ public class FileUpload implements Closeable, AttachedFile { private final InputStream resource; + private final Supplier resourceSupplier; private String name; - private BufferedRequestBody body; + private TypedBody body; private String description; protected FileUpload(InputStream resource, String name) { this.resource = resource; + this.resourceSupplier = null; this.name = name; } + protected FileUpload(Supplier resourceSupplier, String name) + { + this.resourceSupplier = resourceSupplier; + this.resource = null; + this.name = name; + } + + /** + * Creates a FileUpload that sources its data from the supplier. + *
The supplier must return a new stream on every call. + * + *

The streams are expected to always be at the beginning, when they are taken from the supplier. + * If the supplier returned the same stream instance, the reader would start at the wrong position when re-attempting a request. + * + *

When this supplier factory is used, {@link #getData()} will return a new instance on each call. + * It is the responsibility of the caller to close that stream. + * + * @param name + * The file name + * @param supplier + * The resource supplier, which returns a new stream on each call + * + * @throws IllegalArgumentException + * If null is provided or the name is blank + * + * @return {@link FileUpload} + */ + @Nonnull + public static FileUpload fromStreamSupplier(@Nonnull String name, @Nonnull Supplier supplier) + { + Checks.notNull(supplier, "Supplier"); + return fromSourceSupplier(name, () -> Okio.source(supplier.get())); + } + + /** + * Creates a FileUpload that sources its data from the supplier. + *
The supplier must return a new stream on every call. + * + *

The streams are expected to always be at the beginning, when they are taken from the supplier. + * If the supplier returned the same stream instance, the reader would start at the wrong position when re-attempting a request. + * + *

When this supplier factory is used, {@link #getData()} will return a new instance on each call. + * It is the responsibility of the caller to close that stream. + * + * @param name + * The file name + * @param supplier + * The resource supplier, which returns a new {@link Source} on each call + * + * @throws IllegalArgumentException + * If null is provided or the name is blank + * + * @return {@link FileUpload} + */ + @Nonnull + public static FileUpload fromSourceSupplier(@Nonnull String name, @Nonnull Supplier supplier) + { + Checks.notNull(supplier, "Supplier"); + Checks.notBlank(name, "Name"); + return new FileUpload(supplier, name); + } + /** * Create a new {@link FileUpload} for an input stream. *
This is used to upload data to discord for various purposes. @@ -320,7 +388,10 @@ public String getDescription() @Nonnull public InputStream getData() { - return resource; + if (resource != null) + return resource; + else + return Okio.buffer(resourceSupplier.get()).inputStream(); } /** @@ -343,7 +414,11 @@ public synchronized RequestBody getRequestBody(@Nonnull MediaType type) Checks.notNull(type, "Type"); if (body != null) // This allows FileUpload to be used more than once! return body.withType(type); - return body = IOUtil.createRequestBody(type, resource); + + if (resource == null) + return body = new DataSupplierBody(type, resourceSupplier); + else + return body = IOUtil.createRequestBody(type, resource); } @Override @@ -381,7 +456,7 @@ public void forceClose() throws IOException @SuppressWarnings("deprecation") protected void finalize() { - if (body == null) // Only close if the resource was never used + if (body == null && resource != null) // Only close if the resource was never used IOUtil.silentClose(resource); } diff --git a/src/main/java/net/dv8tion/jda/internal/utils/IOUtil.java b/src/main/java/net/dv8tion/jda/internal/utils/IOUtil.java index 555fd86e5d..9f27ea9dbd 100644 --- a/src/main/java/net/dv8tion/jda/internal/utils/IOUtil.java +++ b/src/main/java/net/dv8tion/jda/internal/utils/IOUtil.java @@ -17,6 +17,7 @@ package net.dv8tion.jda.internal.utils; import com.neovisionaries.ws.client.WebSocketFactory; +import net.dv8tion.jda.internal.utils.requestbody.BufferedRequestBody; import okhttp3.ConnectionPool; import okhttp3.Dispatcher; import okhttp3.MediaType; diff --git a/src/main/java/net/dv8tion/jda/internal/utils/BufferedRequestBody.java b/src/main/java/net/dv8tion/jda/internal/utils/requestbody/BufferedRequestBody.java similarity index 85% rename from src/main/java/net/dv8tion/jda/internal/utils/BufferedRequestBody.java rename to src/main/java/net/dv8tion/jda/internal/utils/requestbody/BufferedRequestBody.java index b6397cd127..1e6f134f36 100644 --- a/src/main/java/net/dv8tion/jda/internal/utils/BufferedRequestBody.java +++ b/src/main/java/net/dv8tion/jda/internal/utils/requestbody/BufferedRequestBody.java @@ -14,29 +14,27 @@ * limitations under the License. */ -package net.dv8tion.jda.internal.utils; +package net.dv8tion.jda.internal.utils.requestbody; +import net.dv8tion.jda.internal.utils.IOUtil; import okhttp3.MediaType; -import okhttp3.RequestBody; import okio.BufferedSink; import okio.BufferedSource; import okio.Okio; import okio.Source; import javax.annotation.Nonnull; -import javax.annotation.Nullable; import java.io.IOException; -public class BufferedRequestBody extends RequestBody +public class BufferedRequestBody extends TypedBody { private final Source source; - private final MediaType type; private byte[] data; public BufferedRequestBody(Source source, MediaType type) { + super(type); this.source = source; - this.type = type; } @Nonnull @@ -52,13 +50,6 @@ public BufferedRequestBody withType(@Nonnull MediaType type) } } - @Nullable - @Override - public MediaType contentType() - { - return type; - } - @Override public void writeTo(@Nonnull BufferedSink sink) throws IOException { diff --git a/src/main/java/net/dv8tion/jda/internal/utils/requestbody/DataSupplierBody.java b/src/main/java/net/dv8tion/jda/internal/utils/requestbody/DataSupplierBody.java new file mode 100644 index 0000000000..f0e7cb4d7e --- /dev/null +++ b/src/main/java/net/dv8tion/jda/internal/utils/requestbody/DataSupplierBody.java @@ -0,0 +1,57 @@ +/* + * Copyright 2015 Austin Keener, Michael Ritter, Florian Spieß, and the JDA contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.dv8tion.jda.internal.utils.requestbody; + +import okhttp3.MediaType; +import okio.BufferedSink; +import okio.Source; + +import javax.annotation.Nonnull; +import java.io.IOException; +import java.util.function.Supplier; + +public class DataSupplierBody extends TypedBody +{ + private final Supplier streamSupply; + + public DataSupplierBody(MediaType type, Supplier streamSupply) + { + super(type); + this.streamSupply = streamSupply; + } + + @Nonnull + @Override + public DataSupplierBody withType(@Nonnull MediaType newType) + { + if (this.type.equals(newType)) + return this; + return new DataSupplierBody(newType, streamSupply); + } + + @Override + public void writeTo(@Nonnull BufferedSink bufferedSink) throws IOException + { + synchronized (streamSupply) + { + try (Source stream = streamSupply.get()) + { + bufferedSink.writeAll(stream); + } + } + } +} diff --git a/src/main/java/net/dv8tion/jda/internal/utils/requestbody/TypedBody.java b/src/main/java/net/dv8tion/jda/internal/utils/requestbody/TypedBody.java new file mode 100644 index 0000000000..22d13bc2ce --- /dev/null +++ b/src/main/java/net/dv8tion/jda/internal/utils/requestbody/TypedBody.java @@ -0,0 +1,43 @@ +/* + * Copyright 2015 Austin Keener, Michael Ritter, Florian Spieß, and the JDA contributors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package net.dv8tion.jda.internal.utils.requestbody; + +import okhttp3.MediaType; +import okhttp3.RequestBody; + +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +public abstract class TypedBody> extends RequestBody +{ + protected final MediaType type; + + protected TypedBody(MediaType type) + { + this.type = type; + } + + @Nonnull + public abstract T withType(@Nonnull MediaType newType); + + @Nullable + @Override + public MediaType contentType() + { + return type; + } +}