Add ability to limit the total number of upload slots, as well as per user

pull/24/head
Zlatin Balevsky 2019-10-28 14:48:38 +00:00
parent 7d652fabcb
commit 8684452848
6 changed files with 97 additions and 5 deletions

View File

@ -276,7 +276,7 @@ public class Core {
eventBus.register(UIDownloadResumedEvent.class, downloadManager)
log.info("initializing upload manager")
uploadManager = new UploadManager(eventBus, fileManager, meshManager, downloadManager)
uploadManager = new UploadManager(eventBus, fileManager, meshManager, downloadManager, props)
log.info("initializing connection establisher")
connectionEstablisher = new ConnectionEstablisher(eventBus, i2pConnector, props, connectionManager, hostCache)

View File

@ -17,6 +17,8 @@ class MuWireSettings {
int trustListInterval
Set<Persona> trustSubscriptions
int downloadRetryInterval
int totalUploadSlots
int uploadSlotsPerUser
int updateCheckInterval
boolean autoDownloadUpdate
String updateType
@ -73,6 +75,8 @@ class MuWireSettings {
searchComments = Boolean.valueOf(props.getProperty("searchComments","true"))
browseFiles = Boolean.valueOf(props.getProperty("browseFiles","true"))
speedSmoothSeconds = Integer.valueOf(props.getProperty("speedSmoothSeconds","60"))
totalUploadSlots = Integer.valueOf(props.getProperty("totalUploadSlots","-1"))
uploadSlotsPerUser = Integer.valueOf(props.getProperty("uploadSlotsPerUser","-1"))
watchedDirectories = readEncodedSet(props, "watchedDirectories")
watchedKeywords = readEncodedSet(props, "watchedKeywords")
@ -118,6 +122,8 @@ class MuWireSettings {
props.setProperty("searchComments", String.valueOf(searchComments))
props.setProperty("browseFiles", String.valueOf(browseFiles))
props.setProperty("speedSmoothSeconds", String.valueOf(speedSmoothSeconds))
props.setProperty("totalUploadSlots", String.valueOf(totalUploadSlots))
props.setProperty("uploadSlotsPerUser", String.valueOf(uploadSlotsPerUser))
writeEncodedSet(watchedDirectories, "watchedDirectories", props)
writeEncodedSet(watchedKeywords, "watchedKeywords", props)

View File

@ -4,6 +4,8 @@ import java.nio.charset.StandardCharsets
import com.muwire.core.EventBus
import com.muwire.core.InfoHash
import com.muwire.core.MuWireSettings
import com.muwire.core.Persona
import com.muwire.core.SharedFile
import com.muwire.core.connection.Endpoint
import com.muwire.core.download.DownloadManager
@ -22,15 +24,22 @@ public class UploadManager {
private final FileManager fileManager
private final MeshManager meshManager
private final DownloadManager downloadManager
private final MuWireSettings props
/** LOCKING: this on both structures */
private int totalUploads
private final Map<Persona, Integer> uploadsPerUser = new HashMap<>()
public UploadManager() {}
public UploadManager(EventBus eventBus, FileManager fileManager,
MeshManager meshManager, DownloadManager downloadManager) {
MeshManager meshManager, DownloadManager downloadManager,
MuWireSettings props) {
this.eventBus = eventBus
this.fileManager = fileManager
this.meshManager = meshManager
this.downloadManager = downloadManager
this.props = props
}
public void processGET(Endpoint e) throws IOException {
@ -82,7 +91,15 @@ public class UploadManager {
if (request.have > 0)
eventBus.publish(new SourceDiscoveredEvent(infoHash : request.infoHash, source : request.downloader))
if (!incrementUploads(request.downloader)) {
log.info("rejecting due to slot limit")
e.getOutputStream().write("429 Too Many Requests\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
e.getOutputStream().flush()
e.close()
return
}
Mesh mesh
File file
int pieceSize
@ -103,6 +120,7 @@ public class UploadManager {
try {
uploader.respond()
} finally {
decrementUploads(request.downloader)
eventBus.publish(new UploadFinishedEvent(uploader : uploader))
}
}
@ -157,12 +175,21 @@ public class UploadManager {
return
}
}
if (!incrementUploads(request.downloader)) {
log.info("rejecting due to slot limit")
e.getOutputStream().write("429 Too Many Requests\r\n\r\n".getBytes(StandardCharsets.US_ASCII))
e.getOutputStream().flush()
e.close()
return
}
Uploader uploader = new HashListUploader(e, fullInfoHash, request)
eventBus.publish(new UploadEvent(uploader : uploader))
try {
uploader.respond()
} finally {
decrementUploads(request.downloader)
eventBus.publish(new UploadFinishedEvent(uploader : uploader))
}
@ -233,5 +260,37 @@ public class UploadManager {
}
}
}
/**
* @param p downloader
* @return true if this upload hasn't hit any slot limits
*/
private synchronized boolean incrementUploads(Persona p) {
if (props.totalUploadSlots >= 0 && totalUploads >= props.totalUploadSlots)
return false
if (props.uploadSlotsPerUser == 0)
return false
Integer currentUploads = uploadsPerUser.get(p)
if (currentUploads == null)
currentUploads = 0
if (props.uploadSlotsPerUser > 0 && currentUploads >= props.uploadSlotsPerUser)
return false
uploadsPerUser.put(p, ++currentUploads)
totalUploads++
true
}
private synchronized void decrementUploads(Persona p) {
totalUploads--
Integer currentUploads = uploadsPerUser.get(p)
if (currentUploads == null || currentUploads == 0)
throw new IllegalStateException()
currentUploads--
if (currentUploads == 0)
uploadsPerUser.remove(p)
else
uploadsPerUser.put(p, currentUploads)
}
}

View File

@ -69,6 +69,16 @@ class OptionsController {
text = view.updateField.text
model.updateCheckInterval = text
settings.updateCheckInterval = Integer.valueOf(text)
text = view.totalUploadSlotsField.text
int totalUploadSlots = Integer.valueOf(text)
model.totalUploadSlots = totalUploadSlots
settings.totalUploadSlots = totalUploadSlots
text = view.uploadSlotsPerUserField.text
int uploadSlotsPerUser = Integer.valueOf(text)
model.uploadSlotsPerUser = uploadSlotsPerUser
settings.uploadSlotsPerUser = uploadSlotsPerUser
boolean searchComments = view.searchCommentsCheckbox.model.isSelected()
model.searchComments = searchComments

View File

@ -19,6 +19,8 @@ class OptionsModel {
@Observable boolean searchComments
@Observable boolean browseFiles
@Observable int speedSmoothSeconds
@Observable int totalUploadSlots
@Observable int uploadSlotsPerUser
// i2p options
@Observable String inboundLength
@ -65,6 +67,8 @@ class OptionsModel {
searchComments = settings.searchComments
browseFiles = settings.browseFiles
speedSmoothSeconds = settings.speedSmoothSeconds
totalUploadSlots = settings.totalUploadSlots
uploadSlotsPerUser = settings.uploadSlotsPerUser
Core core = application.context.get("core")
inboundLength = core.i2pOptions["inbound.length"]

View File

@ -42,6 +42,8 @@ class OptionsView {
def searchCommentsCheckbox
def browseFilesCheckbox
def speedSmoothSecondsField
def totalUploadSlotsField
def uploadSlotsPerUserField
def inboundLengthField
def inboundQuantityField
@ -107,8 +109,19 @@ class OptionsView {
button(text : "Choose", constraints : gbc(gridx : 2, gridy:2), incompleteLocationAction)
}
panel (border : titledBorder(title : "Upload Settings", border : etchedBorder(), titlePosition : TitledBorder.TOP,
constraints : gbc(gridx : 0, gridy:2, fill : GridBagConstraints.HORIZONTAL))) {
gridBagLayout()
label(text : "Total upload slots (-1 means unlimited)", constraints : gbc(gridx: 0, gridy : 0, anchor : GridBagConstraints.LINE_START, weightx: 100))
totalUploadSlotsField = textField(text : bind {model.totalUploadSlots}, columns: 2,
constraints : gbc(gridx : 1, gridy: 0, anchor : GridBagConstraints.LINE_END))
label(text : "Upload slots per user (-1 means unlimited)", constraints : gbc(gridx: 0, gridy : 1, anchor : GridBagConstraints.LINE_START, weightx: 100))
uploadSlotsPerUserField = textField(text : bind {model.uploadSlotsPerUser}, columns: 2,
constraints : gbc(gridx : 1, gridy: 1, anchor : GridBagConstraints.LINE_END))
}
panel (border : titledBorder(title : "Sharing Settings", border : etchedBorder(), titlePosition : TitledBorder.TOP,
constraints : gbc(gridx : 0, gridy : 2, fill : GridBagConstraints.HORIZONTAL))) {
constraints : gbc(gridx : 0, gridy : 3, fill : GridBagConstraints.HORIZONTAL))) {
gridBagLayout()
label(text : "Share downloaded files", constraints : gbc(gridx : 0, gridy:0, anchor : GridBagConstraints.LINE_START, weightx : 100))
shareDownloadedCheckbox = checkBox(selected : bind {model.shareDownloadedFiles}, constraints : gbc(gridx :1, gridy:0, weightx : 0))
@ -118,7 +131,7 @@ class OptionsView {
}
panel (border : titledBorder(title : "Update Settings", border : etchedBorder(), titlePosition : TitledBorder.TOP,
constraints : gbc(gridx : 0, gridy : 3, fill : GridBagConstraints.HORIZONTAL))) {
constraints : gbc(gridx : 0, gridy : 4, fill : GridBagConstraints.HORIZONTAL))) {
gridBagLayout()
label(text : "Check for updates every (hours)", constraints : gbc(gridx : 0, gridy: 0, anchor : GridBagConstraints.LINE_START, weightx : 100))
updateField = textField(text : bind {model.updateCheckInterval }, columns : 2, constraints : gbc(gridx : 1, gridy: 0, weightx: 0))