Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/main/java/li/cil/oc/api/internal/TextBuffer.java
Original file line number Diff line number Diff line change
Expand Up @@ -590,7 +590,7 @@ public interface TextBuffer extends ManagedEnvironment, Persistable {
* @param fileContent the content of the file being transferred.
* @param player the player that dropped the file. Pass <tt>null</tt> on the client side.
*/
void dropFile(String fileName, String fileContent, EntityPlayer player);
void dropFile(String fileName, byte[] fileContent, EntityPlayer player);
Comment thread
hinyb marked this conversation as resolved.

/**
* Signals a mouse button down event for the buffer.
Expand Down
7 changes: 7 additions & 0 deletions src/main/resources/application.conf
Original file line number Diff line number Diff line change
Expand Up @@ -1274,6 +1274,13 @@ opencomputers {
# Note: Only applied if enableClipboardBatching = true.
clipboardBatchSize: 256

# The maximum count of files that can be dropped at once.
maxDropFileCount: 16

# The maximum size in bytes for each dropped file.
# Note: Maximum value is 4MB.
maxDropFileSize: 65536

# The TTL (Time-To-Live) upon creation of a network packet. When a packet
# passes through a Relay, its TTL is decremented. If a Relay receives a
# packet with a TTL of 0, the packet is dropped. Minimum value is 5.
Expand Down
4 changes: 4 additions & 0 deletions src/main/resources/assets/opencomputers/lang/en_US.lang
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,10 @@ oc:gui.Switch.QueueSize=Queue size
oc:gui.Switch.TransferRate=Cycle rate
oc:gui.Terminal.InvalidKey=Invalid key, most likely another terminal has been bound to the server.
oc:gui.Terminal.OutOfRange=No signal.
oc:gui.InputBuffer.TooManyFiles=File count exceeds the maximum of %s.
oc:gui.InputBuffer.TooFrequentFiles=File upload frequency exceeds the maximum of %s files per second.
oc:gui.InputBuffer.FileTooLarge=File size exceeds the maximum of %s bytes.
oc:gui.InputBuffer.FileNameTooLong=File name length exceeds the maximum of %s characters.

# Containers
oc:container.AccessPoint=Access Point
Expand Down
8 changes: 7 additions & 1 deletion src/main/scala/li/cil/oc/Localization.scala
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,13 @@ object Localization {
def OutOfRange = localizeLater("gui.Terminal.OutOfRange")
}

object InputBuffer {
def TooManyFiles = localizeLater("gui.InputBuffer.TooManyFiles", Settings.get.maxDropFileCount.toString)
def TooFrequentFiles = localizeLater("gui.InputBuffer.TooFrequentFiles", Settings.get.maxDropFileCount.toString)
def FileTooLarge = localizeLater("gui.InputBuffer.FileTooLarge", Settings.get.maxDropFileSize.toString)
def FileNameTooLong = localizeLater("gui.InputBuffer.FileNameTooLong", Settings.get.maxDropFileNameLength.toString)
}

object Tooltip {
def DiskUsage(used: Long, capacity: Long) = localizeImmediately("tooltip.DiskUsage", used.toString, capacity.toString)

Expand All @@ -184,5 +191,4 @@ object Localization {

def MFULinked(isLinked: Boolean) = localizeImmediately(if (isLinked) "tooltip.UpgradeMF.Linked" else "tooltip.UpgradeMF.Unlinked")
}

}
3 changes: 3 additions & 0 deletions src/main/scala/li/cil/oc/Settings.scala
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,9 @@ class Settings(val config: Config) {
val maxScreenWidth = config.getInt("misc.maxScreenWidth") max 1
val maxScreenHeight = config.getInt("misc.maxScreenHeight") max 1
val inputUsername = config.getBoolean("misc.inputUsername")
val maxDropFileCount = config.getInt("misc.maxDropFileCount") max 0
val maxDropFileNameLength = 128
Comment thread
hinyb marked this conversation as resolved.
val maxDropFileSize = config.getInt("misc.maxDropFileSize") max 0 min 4 * 1024 * 1024
val enableClipboardBatching = config.getBoolean("misc.enableClipboardBatching")
val maxClipboardSize = config.getInt("misc.maxClipboardSize")
val clipboardBatchSize = config.getInt("misc.clipboardBatchSize") max 1
Expand Down
64 changes: 42 additions & 22 deletions src/main/scala/li/cil/oc/client/PacketSender.scala
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package li.cil.oc.client

import li.cil.oc.Settings
import li.cil.oc.common.CompressedPacketBuilder
import li.cil.oc.common.PacketType
import li.cil.oc.common.SimplePacketBuilder
import li.cil.oc.{Localization, Settings}
import li.cil.oc.common.{CompressedPacketBuilder, PacketFlags, PacketType, SimplePacketBuilder}
import li.cil.oc.common.entity.Drone
import li.cil.oc.common.tileentity._
import li.cil.oc.common.tileentity.traits.Computer
Expand All @@ -13,6 +11,9 @@ import net.minecraft.item.ItemStack
import net.minecraft.util.ResourceLocation
import net.minecraftforge.common.util.ForgeDirection

import java.io.ByteArrayOutputStream
import java.util.zip.{Deflater, DeflaterOutputStream}

object PacketSender {
// Timestamp after which the next clipboard message may be sent. Used to
// avoid spamming large packets on key repeat.
Expand Down Expand Up @@ -70,13 +71,16 @@ object PacketSender {
pb.sendToServer()
}

private def playErrorSound(): Unit = {
val player = Minecraft.getMinecraft.thePlayer
val handler = Minecraft.getMinecraft.getSoundHandler
handler.playSound(new PositionedSoundRecord(new ResourceLocation("note.harp"), 1, 1, player.posX.toFloat, player.posY.toFloat, player.posZ.toFloat))
}

def sendClipboard(address: String, value: String) {
if (value != null && !value.isEmpty) {
if (value.length > Settings.get.maxClipboardLength || System.currentTimeMillis() < clipboardCooldown) {
val player = Minecraft.getMinecraft.thePlayer
val handler = Minecraft.getMinecraft.getSoundHandler
handler.playSound(new PositionedSoundRecord(new ResourceLocation("note.harp"), 1, 1, player.posX.toFloat, player.posY.toFloat, player.posZ.toFloat))
}
if (value.length > Settings.get.maxClipboardLength || System.currentTimeMillis() < clipboardCooldown)
playErrorSound()
else {
clipboardCooldown = System.currentTimeMillis() + value.length / 10
for (part <- value.grouped(16 * 1024)) {
Expand All @@ -91,21 +95,37 @@ object PacketSender {
}
}

def sendDropFile(address: String, name: String, content: String): Unit = {
val length = name.length + content.length
if (length > 64 * 1024) {
val player = Minecraft.getMinecraft.thePlayer
val handler = Minecraft.getMinecraft.getSoundHandler
handler.playSound(new PositionedSoundRecord(new ResourceLocation("note.harp"), 1, 1, player.posX.toFloat, player.posY.toFloat, player.posZ.toFloat))
def sendDropFile(address: String, name: String, content: Array[Byte]): Unit = {
if (content.length > Settings.get.maxDropFileSize) {
playErrorSound()
Minecraft.getMinecraft.thePlayer.addChatMessage(Localization.InputBuffer.FileTooLarge)
}
else {
val pb = new CompressedPacketBuilder(PacketType.DropFile)

pb.writeUTF(address)
pb.writeUTF(name)
pb.writeUTF(content)

pb.sendToServer()
val data = new ByteArrayOutputStream()
val stream = new DeflaterOutputStream(data, new Deflater(Deflater.BEST_SPEED))
stream.write(content)
stream.close()
// 1(compress) + 38(address) + 386(name) + 4(size) + 1(flag) + 4(size) = 434
val chunks = data.toByteArray.grouped(31 * 1024).toArray
val size = chunks.map(_.length).sum
for (i <- chunks.indices) {
val chunk = chunks(i)
val pb = new SimplePacketBuilder(PacketType.DropFile)
val flag = PacketFlags.DropFile.Chunk |
(if (i == 0) PacketFlags.DropFile.Start else 0) |
(if (i == chunks.length - 1) PacketFlags.DropFile.End else 0)
pb.writeByte(flag)
if (i == 0) {
pb.writeUTF(address)
pb.writeUTF(name)
pb.writeInt(size)
}
pb.writeShort(chunk.length)
pb.write(chunk)
if (i == chunks.length - 1)
pb.writeInt(content.length)
pb.sendToServer()
}
}
}

Expand Down
42 changes: 35 additions & 7 deletions src/main/scala/li/cil/oc/client/gui/traits/InputBuffer.scala
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
package li.cil.oc.client.gui.traits

import li.cil.oc.{OpenComputers, api}
import li.cil.oc.{Localization, OpenComputers, Settings, api}
import li.cil.oc.client.{KeyBindings, Textures}
import li.cil.oc.common.EventHandler
import li.cil.oc.integration.util.NEI
import li.cil.oc.util.RenderState
import net.minecraft.client.Minecraft
import net.minecraft.client.audio.PositionedSoundRecord
import net.minecraft.client.gui.GuiScreen
import net.minecraft.client.gui.inventory.GuiContainer
import net.minecraft.client.renderer.Tessellator
import net.minecraft.util.ResourceLocation
import org.lwjgl.input.Keyboard
import org.lwjgl.opengl.GL11

Expand Down Expand Up @@ -125,6 +127,7 @@ trait InputBuffer extends DisplayBuffer {
}

private val fileIoContext: ExecutionContext = ExecutionContext.fromExecutor(Executors.newFixedThreadPool(2))

private case class FileResult(relativePath: String, file: File)

private def getFiles(path: String): List[FileResult] = {
Expand All @@ -145,18 +148,43 @@ trait InputBuffer extends DisplayBuffer {
}
}

private def playErrorSound(): Unit = {
val player = this.mc.thePlayer
val handler = this.mc.getSoundHandler
handler.playSound(new PositionedSoundRecord(new ResourceLocation("note.harp"), 1, 1, player.posX.toFloat, player.posY.toFloat, player.posZ.toFloat))
}

def handleDropFile(filePath: String): Unit = {
Future {
getFiles(filePath).foreach {
case FileResult(path, file) =>
if (file.length() < 64 * 1024){
val content = new String(Files.readAllBytes(file.toPath), StandardCharsets.UTF_8)
val allFiles = getFiles(filePath)
if (allFiles.size > Settings.get.maxDropFileCount) {
EventHandler.scheduleClient(() => {
this.mc.thePlayer.addChatMessage(Localization.InputBuffer.TooManyFiles)
playErrorSound()
})
}
else if (allFiles.exists(_.file.length() > Settings.get.maxDropFileSize)) {
EventHandler.scheduleClient(() => {
this.mc.thePlayer.addChatMessage(Localization.InputBuffer.FileTooLarge)
playErrorSound()
})
}
else if (allFiles.exists(_.relativePath.length() > Settings.get.maxDropFileNameLength)) {
EventHandler.scheduleClient(() => {
this.mc.thePlayer.addChatMessage(Localization.InputBuffer.FileNameTooLong)
playErrorSound()
})
}
else {
allFiles.foreach {
case FileResult(path, file) =>
val content = Files.readAllBytes(file.toPath)
EventHandler.scheduleClient(() => {
buffer.dropFile(path, content, null)
})
}
}
}
}(fileIoContext).failed.foreach{ e =>
}(fileIoContext).failed.foreach { e =>
OpenComputers.log.warn("Failed to handle drop file.", e)
}(fileIoContext)
}
Expand Down
3 changes: 2 additions & 1 deletion src/main/scala/li/cil/oc/common/EventHandler.scala
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ import li.cil.oc.server.component.Keyboard
import li.cil.oc.server.machine.Callbacks
import li.cil.oc.server.machine.Machine
import li.cil.oc.server.machine.luac.LuaStateFactory
import li.cil.oc.server.{PacketSender => ServerPacketSender}
import li.cil.oc.server.{DropFileManager, PacketSender => ServerPacketSender}
import li.cil.oc.util.ExtendedWorld._
import li.cil.oc.util._
import net.minecraft.entity.player.EntityPlayer
Expand Down Expand Up @@ -249,6 +249,7 @@ object EventHandler {
@SubscribeEvent
def onPlayerLogout(e: PlayerLoggedOutEvent) {
keyboards.foreach(_.releasePressedKeys(e.player))
DropFileManager.clearSession(e.player.getUniqueID)
}

@SubscribeEvent
Expand Down
9 changes: 9 additions & 0 deletions src/main/scala/li/cil/oc/common/PacketFlags.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package li.cil.oc.common

object PacketFlags {
Comment thread
hinyb marked this conversation as resolved.
object DropFile {
val Start = 1 << 0
val Chunk = 1 << 1
val End = 1 << 2
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ class GpuTextBuffer(val owner: String, val id: Int, val data: li.cil.oc.util.Tex
override def keyDown(character: Char, code: Int, player: EntityPlayer): Unit = {}
override def keyUp(character: Char, code: Int, player: EntityPlayer): Unit = {}
override def clipboard(value: String, player: EntityPlayer): Unit = {}
override def dropFile(fileName: String, fileContent: String, player: EntityPlayer): Unit = {}
override def dropFile(fileName: String, fileContent: Array[Byte], player: EntityPlayer): Unit = {}
override def mouseDown(x: Double, y: Double, button: Int, player: EntityPlayer): Unit = {}
override def mouseDrag(x: Double, y: Double, button: Int, player: EntityPlayer): Unit = {}
override def mouseUp(x: Double, y: Double, button: Int, player: EntityPlayer): Unit = {}
Expand Down
15 changes: 9 additions & 6 deletions src/main/scala/li/cil/oc/common/component/TextBuffer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import net.minecraft.nbt.NBTTagCompound
import net.minecraftforge.event.world.ChunkEvent
import net.minecraftforge.event.world.WorldEvent

import java.nio.charset.StandardCharsets
import scala.collection.convert.WrapAsJava._
import scala.collection.convert.WrapAsScala._
import scala.collection.mutable
Expand Down Expand Up @@ -381,7 +382,7 @@ class TextBuffer(val host: EnvironmentHost) extends prefab.ManagedEnvironment wi
override def clipboard(value: String, player: EntityPlayer): Unit =
proxy.clipboard(value, player)

override def dropFile(fileName: String, fileContent: String, player: EntityPlayer): Unit =
override def dropFile(fileName: String, fileContent: Array[Byte], player: EntityPlayer): Unit =
proxy.dropFile(fileName, fileContent, player)

override def mouseDown(x: Double, y: Double, button: Int, player: EntityPlayer): Unit =
Expand Down Expand Up @@ -603,7 +604,7 @@ object TextBuffer {

def clipboard(value: String, player: EntityPlayer): Unit

def dropFile(fileName: String, fileContent: String, player: EntityPlayer): Unit
def dropFile(fileName: String, fileContent: Array[Byte], player: EntityPlayer): Unit

def mouseDown(x: Double, y: Double, button: Int, player: EntityPlayer): Unit

Expand Down Expand Up @@ -698,7 +699,7 @@ object TextBuffer {
ClientPacketSender.sendClipboard(nodeAddress, value)
}

override def dropFile(fileName: String, fileContent: String, player: EntityPlayer) {
override def dropFile(fileName: String, fileContent: Array[Byte], player: EntityPlayer) {
debug(s"{type = dropFile}")
ClientPacketSender.sendDropFile(nodeAddress, fileName, fileContent)
}
Expand Down Expand Up @@ -841,9 +842,11 @@ object TextBuffer {
sendToKeyboards("keyboard.clipboard", player, value)
}

override def dropFile(fileName: String, fileContent: String, player: EntityPlayer): Unit = {
if (owner.isUseableByPlayer(player))
owner.node.sendToReachable("computer.checked_signal", player, "drop_file", fileName, fileContent)
override def dropFile(fileName: String, fileContent: Array[Byte], player: EntityPlayer): Unit = {
if (owner.isUseableByPlayer(player)) {
val content = new String(fileContent, StandardCharsets.UTF_8)
owner.node.sendToReachable("computer.checked_signal", player, "drop_file", fileName, content)
}
}

override def mouseDown(x: Double, y: Double, button: Int, player: EntityPlayer) {
Expand Down
Loading