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

This commit is contained in:
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) eventBus.register(UIDownloadResumedEvent.class, downloadManager)
log.info("initializing upload manager") 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") log.info("initializing connection establisher")
connectionEstablisher = new ConnectionEstablisher(eventBus, i2pConnector, props, connectionManager, hostCache) connectionEstablisher = new ConnectionEstablisher(eventBus, i2pConnector, props, connectionManager, hostCache)

View File

@@ -17,6 +17,8 @@ class MuWireSettings {
int trustListInterval int trustListInterval
Set<Persona> trustSubscriptions Set<Persona> trustSubscriptions
int downloadRetryInterval int downloadRetryInterval
int totalUploadSlots
int uploadSlotsPerUser
int updateCheckInterval int updateCheckInterval
boolean autoDownloadUpdate boolean autoDownloadUpdate
String updateType String updateType
@@ -73,6 +75,8 @@ class MuWireSettings {
searchComments = Boolean.valueOf(props.getProperty("searchComments","true")) searchComments = Boolean.valueOf(props.getProperty("searchComments","true"))
browseFiles = Boolean.valueOf(props.getProperty("browseFiles","true")) browseFiles = Boolean.valueOf(props.getProperty("browseFiles","true"))
speedSmoothSeconds = Integer.valueOf(props.getProperty("speedSmoothSeconds","60")) 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") watchedDirectories = readEncodedSet(props, "watchedDirectories")
watchedKeywords = readEncodedSet(props, "watchedKeywords") watchedKeywords = readEncodedSet(props, "watchedKeywords")
@@ -118,6 +122,8 @@ class MuWireSettings {
props.setProperty("searchComments", String.valueOf(searchComments)) props.setProperty("searchComments", String.valueOf(searchComments))
props.setProperty("browseFiles", String.valueOf(browseFiles)) props.setProperty("browseFiles", String.valueOf(browseFiles))
props.setProperty("speedSmoothSeconds", String.valueOf(speedSmoothSeconds)) props.setProperty("speedSmoothSeconds", String.valueOf(speedSmoothSeconds))
props.setProperty("totalUploadSlots", String.valueOf(totalUploadSlots))
props.setProperty("uploadSlotsPerUser", String.valueOf(uploadSlotsPerUser))
writeEncodedSet(watchedDirectories, "watchedDirectories", props) writeEncodedSet(watchedDirectories, "watchedDirectories", props)
writeEncodedSet(watchedKeywords, "watchedKeywords", 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.EventBus
import com.muwire.core.InfoHash import com.muwire.core.InfoHash
import com.muwire.core.MuWireSettings
import com.muwire.core.Persona
import com.muwire.core.SharedFile import com.muwire.core.SharedFile
import com.muwire.core.connection.Endpoint import com.muwire.core.connection.Endpoint
import com.muwire.core.download.DownloadManager import com.muwire.core.download.DownloadManager
@@ -22,15 +24,22 @@ public class UploadManager {
private final FileManager fileManager private final FileManager fileManager
private final MeshManager meshManager private final MeshManager meshManager
private final DownloadManager downloadManager 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() {}
public UploadManager(EventBus eventBus, FileManager fileManager, public UploadManager(EventBus eventBus, FileManager fileManager,
MeshManager meshManager, DownloadManager downloadManager) { MeshManager meshManager, DownloadManager downloadManager,
MuWireSettings props) {
this.eventBus = eventBus this.eventBus = eventBus
this.fileManager = fileManager this.fileManager = fileManager
this.meshManager = meshManager this.meshManager = meshManager
this.downloadManager = downloadManager this.downloadManager = downloadManager
this.props = props
} }
public void processGET(Endpoint e) throws IOException { public void processGET(Endpoint e) throws IOException {
@@ -82,7 +91,15 @@ public class UploadManager {
if (request.have > 0) if (request.have > 0)
eventBus.publish(new SourceDiscoveredEvent(infoHash : request.infoHash, source : request.downloader)) 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 Mesh mesh
File file File file
int pieceSize int pieceSize
@@ -103,6 +120,7 @@ public class UploadManager {
try { try {
uploader.respond() uploader.respond()
} finally { } finally {
decrementUploads(request.downloader)
eventBus.publish(new UploadFinishedEvent(uploader : uploader)) eventBus.publish(new UploadFinishedEvent(uploader : uploader))
} }
} }
@@ -157,12 +175,21 @@ public class UploadManager {
return 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) Uploader uploader = new HashListUploader(e, fullInfoHash, request)
eventBus.publish(new UploadEvent(uploader : uploader)) eventBus.publish(new UploadEvent(uploader : uploader))
try { try {
uploader.respond() uploader.respond()
} finally { } finally {
decrementUploads(request.downloader)
eventBus.publish(new UploadFinishedEvent(uploader : uploader)) 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 text = view.updateField.text
model.updateCheckInterval = text model.updateCheckInterval = text
settings.updateCheckInterval = Integer.valueOf(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() boolean searchComments = view.searchCommentsCheckbox.model.isSelected()
model.searchComments = searchComments model.searchComments = searchComments

View File

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

View File

@@ -42,6 +42,8 @@ class OptionsView {
def searchCommentsCheckbox def searchCommentsCheckbox
def browseFilesCheckbox def browseFilesCheckbox
def speedSmoothSecondsField def speedSmoothSecondsField
def totalUploadSlotsField
def uploadSlotsPerUserField
def inboundLengthField def inboundLengthField
def inboundQuantityField def inboundQuantityField
@@ -107,8 +109,19 @@ class OptionsView {
button(text : "Choose", constraints : gbc(gridx : 2, gridy:2), incompleteLocationAction) 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, 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() gridBagLayout()
label(text : "Share downloaded files", constraints : gbc(gridx : 0, gridy:0, anchor : GridBagConstraints.LINE_START, weightx : 100)) 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)) 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, 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() gridBagLayout()
label(text : "Check for updates every (hours)", constraints : gbc(gridx : 0, gridy: 0, anchor : GridBagConstraints.LINE_START, weightx : 100)) 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)) updateField = textField(text : bind {model.updateCheckInterval }, columns : 2, constraints : gbc(gridx : 1, gridy: 0, weightx: 0))