SRT edits:
- Increased SRT socket queue from 1 to 100 - Fixed SRT initialization (now clean) - Made output_ts_base.cpp thread-safe - Made Output class thread-safe - SRT TS output can now optionally set open file limit
This commit is contained in:
		
							parent
							
								
									0bd5d742f6
								
							
						
					
					
						commit
						77aa90d48c
					
				
					 8 changed files with 123 additions and 15 deletions
				
			
		|  | @ -82,6 +82,7 @@ namespace Socket{ | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   SRTConnection::SRTConnection(SRTSOCKET alreadyConnected){ |   SRTConnection::SRTConnection(SRTSOCKET alreadyConnected){ | ||||||
|  |     initializeEmpty(); | ||||||
|     sock = alreadyConnected; |     sock = alreadyConnected; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  | @ -238,7 +239,7 @@ namespace Socket{ | ||||||
|         ERROR_MSG("Can't connect SRT Socket: %s", srt_getlasterror_str()); |         ERROR_MSG("Can't connect SRT Socket: %s", srt_getlasterror_str()); | ||||||
|         return; |         return; | ||||||
|       } |       } | ||||||
|       if (srt_listen(sock, 1) == SRT_ERROR){ |       if (srt_listen(sock, 100) == SRT_ERROR){ | ||||||
|         srt_close(sock); |         srt_close(sock); | ||||||
|         sock = -1; |         sock = -1; | ||||||
|         ERROR_MSG("Can not listen on Socket"); |         ERROR_MSG("Can not listen on Socket"); | ||||||
|  | @ -310,7 +311,7 @@ namespace Socket{ | ||||||
|         } |         } | ||||||
|         return; |         return; | ||||||
|       } |       } | ||||||
|       ERROR_MSG("Unable to send data over socket %" PRId32 ": %s", sock, srt_getlasterror_str()); | //      ERROR_MSG("Unable to send data over socket %" PRId32 ": %s", sock, srt_getlasterror_str());
 | ||||||
|       if (srt_getsockstate(sock) != SRTS_CONNECTED){close();} |       if (srt_getsockstate(sock) != SRTS_CONNECTED){close();} | ||||||
|     }else{ |     }else{ | ||||||
|       lastGood = Util::bootMS(); |       lastGood = Util::bootMS(); | ||||||
|  | @ -346,6 +347,7 @@ namespace Socket{ | ||||||
|     outgoing_port = 0; |     outgoing_port = 0; | ||||||
|     chunkTransmitSize = 1316; |     chunkTransmitSize = 1316; | ||||||
|     blocking = false; |     blocking = false; | ||||||
|  |     timeout = 0; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   void SRTConnection::setBlocking(bool _blocking){ |   void SRTConnection::setBlocking(bool _blocking){ | ||||||
|  |  | ||||||
|  | @ -129,7 +129,7 @@ namespace TS{ | ||||||
|   bool Packet::FromPointer(const char *data){ |   bool Packet::FromPointer(const char *data){ | ||||||
|     memcpy((void *)strBuf, (void *)data, 188); |     memcpy((void *)strBuf, (void *)data, 188); | ||||||
|     pos = 188; |     pos = 188; | ||||||
|     return true; |     return strBuf[0] == 0x47; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   /// The deconstructor deletes all space that may be occupied by a Packet.
 |   /// The deconstructor deletes all space that may be occupied by a Packet.
 | ||||||
|  | @ -492,6 +492,39 @@ namespace TS{ | ||||||
|     tmpBuf += (char)(((time & 0x00000007FLL) << 1) | 0x01); |     tmpBuf += (char)(((time & 0x00000007FLL) << 1) | 0x01); | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   /// Generates a PES Lead-in for a video frame.
 | ||||||
|  |   /// Prepends the lead-in to variable toSend, assumes toSend's length is all other data.
 | ||||||
|  |   /// \param len The length of this frame.
 | ||||||
|  |   /// \param PTS The timestamp of the frame.
 | ||||||
|  |   void Packet::getPESVideoLeadIn(std::string &outData, unsigned int len, unsigned long long PTS, | ||||||
|  |                                          unsigned long long offset, bool isAligned, uint64_t bps){ | ||||||
|  |     if (len){len += (offset ? 13 : 8);} | ||||||
|  |     if (bps >= 50){ | ||||||
|  |       if (len){len += 3;} | ||||||
|  |     }else{ | ||||||
|  |       bps = 0; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     outData.append("\000\000\001\340", 4); | ||||||
|  |     outData += (char)((len >> 8) & 0xFF); | ||||||
|  |     outData += (char)(len & 0xFF); | ||||||
|  |     if (isAligned){ | ||||||
|  |       outData.append("\204", 1); | ||||||
|  |     }else{ | ||||||
|  |       outData.append("\200", 1); | ||||||
|  |     } | ||||||
|  |     outData += (char)((offset ? 0xC0 : 0x80) | (bps ? 0x10 : 0)); // PTS/DTS + Flags
 | ||||||
|  |     outData += (char)((offset ? 10 : 5) + (bps ? 3 : 0));         // PESHeaderDataLength
 | ||||||
|  |     encodePESTimestamp(outData, (offset ? 0x30 : 0x20), PTS + offset); | ||||||
|  |     if (offset){encodePESTimestamp(outData, 0x10, PTS);} | ||||||
|  |     if (bps){ | ||||||
|  |       char rate_buf[3]; | ||||||
|  |       Bit::htob24(rate_buf, (bps / 50) | 0x800001); | ||||||
|  |       outData.append(rate_buf, 3); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|  | 
 | ||||||
|   /// Generates a PES Lead-in for a video frame.
 |   /// Generates a PES Lead-in for a video frame.
 | ||||||
|   /// Prepends the lead-in to variable toSend, assumes toSend's length is all other data.
 |   /// Prepends the lead-in to variable toSend, assumes toSend's length is all other data.
 | ||||||
|   /// \param len The length of this frame.
 |   /// \param len The length of this frame.
 | ||||||
|  | @ -527,6 +560,32 @@ namespace TS{ | ||||||
|     return tmpStr; |     return tmpStr; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   /// Generates a PES Lead-in for an audio frame.
 | ||||||
|  |   /// Prepends the lead-in to variable toSend, assumes toSend's length is all other data.
 | ||||||
|  |   /// \param len The length of this frame.
 | ||||||
|  |   /// \param PTS The timestamp of the frame.
 | ||||||
|  |   void Packet::getPESAudioLeadIn(std::string & outData, unsigned int len, unsigned long long PTS, uint64_t bps){ | ||||||
|  |     if (bps >= 50){ | ||||||
|  |       len += 3; | ||||||
|  |     }else{ | ||||||
|  |       bps = 0; | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     len += 8; | ||||||
|  |     outData.append("\000\000\001\300", 4); | ||||||
|  |     outData += (char)((len & 0xFF00) >> 8);     // PES PacketLength
 | ||||||
|  |     outData += (char)(len & 0x00FF);            // PES PacketLength (Cont)
 | ||||||
|  |     outData += (char)0x84;                      // isAligned
 | ||||||
|  |     outData += (char)(0x80 | (bps ? 0x10 : 0)); // PTS/DTS + Flags
 | ||||||
|  |     outData += (char)(5 + (bps ? 3 : 0));       // PESHeaderDataLength
 | ||||||
|  |     encodePESTimestamp(outData, 0x20, PTS); | ||||||
|  |     if (bps){ | ||||||
|  |       char rate_buf[3]; | ||||||
|  |       Bit::htob24(rate_buf, (bps / 50) | 0x800001); | ||||||
|  |       outData.append(rate_buf, 3); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  | 
 | ||||||
|   /// Generates a PES Lead-in for an audio frame.
 |   /// Generates a PES Lead-in for an audio frame.
 | ||||||
|   /// Prepends the lead-in to variable toSend, assumes toSend's length is all other data.
 |   /// Prepends the lead-in to variable toSend, assumes toSend's length is all other data.
 | ||||||
|   /// \param len The length of this frame.
 |   /// \param len The length of this frame.
 | ||||||
|  |  | ||||||
|  | @ -72,8 +72,11 @@ namespace TS{ | ||||||
|     void updPos(unsigned int newPos); |     void updPos(unsigned int newPos); | ||||||
| 
 | 
 | ||||||
|     // PES helpers
 |     // PES helpers
 | ||||||
|  |     static void getPESVideoLeadIn(std::string & outData, unsigned int len, unsigned long long PTS, | ||||||
|  |                                           unsigned long long offset, bool isAligned, uint64_t bps = 0); | ||||||
|     static std::string &getPESVideoLeadIn(unsigned int len, unsigned long long PTS, |     static std::string &getPESVideoLeadIn(unsigned int len, unsigned long long PTS, | ||||||
|                                           unsigned long long offset, bool isAligned, uint64_t bps = 0); |                                           unsigned long long offset, bool isAligned, uint64_t bps = 0); | ||||||
|  |     static void getPESAudioLeadIn(std::string & outData, unsigned int len, unsigned long long PTS, uint64_t bps); | ||||||
|     static std::string &getPESAudioLeadIn(unsigned int len, unsigned long long PTS, uint64_t bps = 0); |     static std::string &getPESAudioLeadIn(unsigned int len, unsigned long long PTS, uint64_t bps = 0); | ||||||
|     static std::string &getPESMetaLeadIn(unsigned int len, unsigned long long PTS, uint64_t bps = 0); |     static std::string &getPESMetaLeadIn(unsigned int len, unsigned long long PTS, uint64_t bps = 0); | ||||||
|     static std::string &getPESPS1LeadIn(unsigned int len, unsigned long long PTS, uint64_t bps = 0); |     static std::string &getPESPS1LeadIn(unsigned int len, unsigned long long PTS, uint64_t bps = 0); | ||||||
|  |  | ||||||
|  | @ -4,6 +4,7 @@ | ||||||
| #include <mist/socket.h> | #include <mist/socket.h> | ||||||
| #include <mist/socket_srt.h> | #include <mist/socket_srt.h> | ||||||
| #include <mist/util.h> | #include <mist/util.h> | ||||||
|  | #include <sys/resource.h> | ||||||
| 
 | 
 | ||||||
| Socket::SRTServer server_socket; | Socket::SRTServer server_socket; | ||||||
| static uint64_t sockCount = 0; | static uint64_t sockCount = 0; | ||||||
|  | @ -52,6 +53,24 @@ static void callThreadCallbackSRT(void *srtPtr){ | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | bool sysSetNrOpenFiles(int n){ | ||||||
|  |     struct rlimit limit; | ||||||
|  |     if (getrlimit(RLIMIT_NOFILE, &limit) != 0) { | ||||||
|  |       FAIL_MSG("Could not get open file limit: %s", strerror(errno)); | ||||||
|  |       return false; | ||||||
|  |     } | ||||||
|  |     int currLimit = limit.rlim_cur; | ||||||
|  |     if(limit.rlim_cur < n){ | ||||||
|  |       limit.rlim_cur = n; | ||||||
|  |       if (setrlimit(RLIMIT_NOFILE, &limit) != 0) { | ||||||
|  |         FAIL_MSG("Could not set open file limit from %d to %d: %s", currLimit, n, strerror(errno)); | ||||||
|  |         return false; | ||||||
|  |       } | ||||||
|  |       HIGH_MSG("Open file limit increased from %d to %d", currLimit, n) | ||||||
|  |     } | ||||||
|  |     return true; | ||||||
|  |   } | ||||||
|  | 
 | ||||||
| int main(int argc, char *argv[]){ | int main(int argc, char *argv[]){ | ||||||
|   DTSC::trackValidMask = TRACK_VALID_EXT_HUMAN; |   DTSC::trackValidMask = TRACK_VALID_EXT_HUMAN; | ||||||
|   Util::redirectLogsIfNeeded(); |   Util::redirectLogsIfNeeded(); | ||||||
|  | @ -64,6 +83,10 @@ int main(int argc, char *argv[]){ | ||||||
|       return -1; |       return -1; | ||||||
|     } |     } | ||||||
|     conf.activate(); |     conf.activate(); | ||||||
|  | 
 | ||||||
|  |     int filelimit = conf.getInteger("filelimit"); | ||||||
|  |     sysSetNrOpenFiles(filelimit); | ||||||
|  | 
 | ||||||
|     if (mistOut::listenMode()){ |     if (mistOut::listenMode()){ | ||||||
|       { |       { | ||||||
|         struct sigaction new_action; |         struct sigaction new_action; | ||||||
|  |  | ||||||
|  | @ -65,6 +65,12 @@ namespace Mist{ | ||||||
|     maxSkipAhead = 7500; |     maxSkipAhead = 7500; | ||||||
|     uaDelay = 10; |     uaDelay = 10; | ||||||
|     realTime = 1000; |     realTime = 1000; | ||||||
|  |     emptyCount = 0; | ||||||
|  |     seekCount = 2; | ||||||
|  |     firstData = true; | ||||||
|  |     newUA = true; | ||||||
|  |     lastPushUpdate = 0; | ||||||
|  | 
 | ||||||
|     lastRecv = Util::bootSecs(); |     lastRecv = Util::bootSecs(); | ||||||
|     if (myConn){ |     if (myConn){ | ||||||
|       setBlocking(true); |       setBlocking(true); | ||||||
|  | @ -174,10 +180,9 @@ namespace Mist{ | ||||||
|   /// May be called recursively because it calls stats() which calls this function.
 |   /// May be called recursively because it calls stats() which calls this function.
 | ||||||
|   /// If this happens, the extra calls to the function return instantly.
 |   /// If this happens, the extra calls to the function return instantly.
 | ||||||
|   void Output::doSync(bool force){ |   void Output::doSync(bool force){ | ||||||
|     static bool recursing = false; |  | ||||||
|     if (!statComm){return;} |     if (!statComm){return;} | ||||||
|     if (recursing){return;} |     if (recursingSync){return;} | ||||||
|     recursing = true; |     recursingSync = true; | ||||||
|     if (statComm.getSync() == 2 || force){ |     if (statComm.getSync() == 2 || force){ | ||||||
|       if (getStatsName() == capa["name"].asStringRef() && Triggers::shouldTrigger("USER_NEW", streamName)){ |       if (getStatsName() == capa["name"].asStringRef() && Triggers::shouldTrigger("USER_NEW", streamName)){ | ||||||
|         // sync byte 0 = no sync yet, wait for sync from controller...
 |         // sync byte 0 = no sync yet, wait for sync from controller...
 | ||||||
|  | @ -252,7 +257,7 @@ namespace Mist{ | ||||||
|         statComm.setSync(10); // auto-accept if no trigger
 |         statComm.setSync(10); // auto-accept if no trigger
 | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|     recursing = false; |     recursingSync = false; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   std::string Output::getConnectedHost(){return myConn.getHost();} |   std::string Output::getConnectedHost(){return myConn.getHost();} | ||||||
|  | @ -1019,7 +1024,6 @@ namespace Mist{ | ||||||
|   /// Aborts if not live, there is no main track or it has no keyframes.
 |   /// Aborts if not live, there is no main track or it has no keyframes.
 | ||||||
|   bool Output::liveSeek(){ |   bool Output::liveSeek(){ | ||||||
|     if (!realTime){return false;}//Makes no sense when playing in turbo mode
 |     if (!realTime){return false;}//Makes no sense when playing in turbo mode
 | ||||||
|     static uint32_t seekCount = 2; |  | ||||||
|     uint64_t seekPos = 0; |     uint64_t seekPos = 0; | ||||||
|     if (!meta.getLive()){return false;} |     if (!meta.getLive()){return false;} | ||||||
|     size_t mainTrack = getMainSelectedTrack(); |     size_t mainTrack = getMainSelectedTrack(); | ||||||
|  | @ -1093,7 +1097,6 @@ namespace Mist{ | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|   void Output::requestHandler(){ |   void Output::requestHandler(){ | ||||||
|     static bool firstData = true; // only the first time, we call onRequest if there's data buffered already.
 |  | ||||||
|     if ((firstData && myConn.Received().size()) || myConn.spool()){ |     if ((firstData && myConn.Received().size()) || myConn.spool()){ | ||||||
|       firstData = false; |       firstData = false; | ||||||
|       DONTEVEN_MSG("onRequest"); |       DONTEVEN_MSG("onRequest"); | ||||||
|  | @ -1442,7 +1445,6 @@ namespace Mist{ | ||||||
|   /// \returns true if thisPacket was filled with the next packet.
 |   /// \returns true if thisPacket was filled with the next packet.
 | ||||||
|   /// \returns false if we could not reliably determine the next packet yet.
 |   /// \returns false if we could not reliably determine the next packet yet.
 | ||||||
|   bool Output::prepareNext(){ |   bool Output::prepareNext(){ | ||||||
|     static size_t emptyCount = 0; |  | ||||||
|     if (!buffer.size()){ |     if (!buffer.size()){ | ||||||
|       thisPacket.null(); |       thisPacket.null(); | ||||||
|       INFO_MSG("Buffer completely played out"); |       INFO_MSG("Buffer completely played out"); | ||||||
|  | @ -1650,7 +1652,10 @@ namespace Mist{ | ||||||
|     if (now == lastStats && !force){return;} |     if (now == lastStats && !force){return;} | ||||||
| 
 | 
 | ||||||
|     if (isRecording()){ |     if (isRecording()){ | ||||||
|       static uint64_t lastPushUpdate = now; |       if(lastPushUpdate == 0){ | ||||||
|  |         lastPushUpdate = now; | ||||||
|  |       } | ||||||
|  | 
 | ||||||
|       if (lastPushUpdate + 5 <= now){ |       if (lastPushUpdate + 5 <= now){ | ||||||
|         JSON::Value pStat; |         JSON::Value pStat; | ||||||
|         pStat["push_status_update"]["id"] = getpid(); |         pStat["push_status_update"]["id"] = getpid(); | ||||||
|  | @ -1692,7 +1697,6 @@ namespace Mist{ | ||||||
| 
 | 
 | ||||||
|     /*LTS-START*/ |     /*LTS-START*/ | ||||||
|     // Tag the session with the user agent
 |     // Tag the session with the user agent
 | ||||||
|     static bool newUA = true; // we only do this once per connection
 |  | ||||||
|     if (newUA && ((now - myConn.connTime()) >= uaDelay || !myConn) && UA.size()){ |     if (newUA && ((now - myConn.connTime()) >= uaDelay || !myConn) && UA.size()){ | ||||||
|       std::string APIcall = |       std::string APIcall = | ||||||
|           "{\"tag_sessid\":{\"" + statComm.getSessId() + "\":" + JSON::string_escape("UA:" + UA) + "}}"; |           "{\"tag_sessid\":{\"" + statComm.getSessId() + "\":" + JSON::string_escape("UA:" + UA) + "}}"; | ||||||
|  |  | ||||||
|  | @ -106,6 +106,12 @@ namespace Mist{ | ||||||
|     bool sought;          ///< If a seek has been done, this is set to true. Used for seeking on
 |     bool sought;          ///< If a seek has been done, this is set to true. Used for seeking on
 | ||||||
|                           ///< prepareNext().
 |                           ///< prepareNext().
 | ||||||
|     std::string prevHost; ///< Old value for getConnectedBinHost, for caching
 |     std::string prevHost; ///< Old value for getConnectedBinHost, for caching
 | ||||||
|  |     size_t emptyCount; | ||||||
|  |     bool recursingSync; | ||||||
|  |     uint32_t seekCount; | ||||||
|  |     bool firstData; | ||||||
|  |     uint64_t lastPushUpdate; | ||||||
|  |     bool newUA; | ||||||
|   protected:              // these are to be messed with by child classes
 |   protected:              // these are to be messed with by child classes
 | ||||||
|     virtual bool inlineRestartCapable() const{ |     virtual bool inlineRestartCapable() const{ | ||||||
|       return false; |       return false; | ||||||
|  |  | ||||||
|  | @ -106,7 +106,8 @@ namespace Mist{ | ||||||
|         uint32_t i = 0; |         uint32_t i = 0; | ||||||
|         uint64_t offset = thisPacket.getInt("offset") * 90; |         uint64_t offset = thisPacket.getInt("offset") * 90; | ||||||
| 
 | 
 | ||||||
|         bs = TS::Packet::getPESVideoLeadIn( |         bs.clear(); | ||||||
|  |         TS::Packet::getPESVideoLeadIn(bs, | ||||||
|             (((dataLen + extraSize) > MAX_PES_SIZE) ? 0 : dataLen + extraSize), |             (((dataLen + extraSize) > MAX_PES_SIZE) ? 0 : dataLen + extraSize), | ||||||
|             packTime, offset, true, M.getBps(thisIdx)); |             packTime, offset, true, M.getBps(thisIdx)); | ||||||
|         fillPacket(bs.data(), bs.size(), firstPack, video, keyframe, pkgPid, contPkg); |         fillPacket(bs.data(), bs.size(), firstPack, video, keyframe, pkgPid, contPkg); | ||||||
|  | @ -143,7 +144,8 @@ namespace Mist{ | ||||||
|         } |         } | ||||||
|       }else{ |       }else{ | ||||||
|         uint64_t offset = thisPacket.getInt("offset") * 90; |         uint64_t offset = thisPacket.getInt("offset") * 90; | ||||||
|         bs = TS::Packet::getPESVideoLeadIn(0, packTime, offset, true, M.getBps(thisIdx)); |         bs.clear(); | ||||||
|  |         TS::Packet::getPESVideoLeadIn(bs, 0, packTime, offset, true, M.getBps(thisIdx)); | ||||||
|         fillPacket(bs.data(), bs.size(), firstPack, video, keyframe, pkgPid, contPkg); |         fillPacket(bs.data(), bs.size(), firstPack, video, keyframe, pkgPid, contPkg); | ||||||
| 
 | 
 | ||||||
|         fillPacket(dataPointer, dataLen, firstPack, video, keyframe, pkgPid, contPkg); |         fillPacket(dataPointer, dataLen, firstPack, video, keyframe, pkgPid, contPkg); | ||||||
|  | @ -171,7 +173,8 @@ namespace Mist{ | ||||||
|         bs.append(1, (char)(dataLen-255*(dataLen/255))); |         bs.append(1, (char)(dataLen-255*(dataLen/255))); | ||||||
|         fillPacket(bs.data(), bs.size(), firstPack, video, keyframe, pkgPid, contPkg); |         fillPacket(bs.data(), bs.size(), firstPack, video, keyframe, pkgPid, contPkg); | ||||||
|       }else{ |       }else{ | ||||||
|         bs = TS::Packet::getPESAudioLeadIn(tempLen, packTime, M.getBps(thisIdx)); |         bs.clear(); | ||||||
|  |         TS::Packet::getPESAudioLeadIn(bs, tempLen, packTime, M.getBps(thisIdx)); | ||||||
|         fillPacket(bs.data(), bs.size(), firstPack, video, keyframe, pkgPid, contPkg); |         fillPacket(bs.data(), bs.size(), firstPack, video, keyframe, pkgPid, contPkg); | ||||||
|         if (codec == "AAC"){ |         if (codec == "AAC"){ | ||||||
|           bs = TS::getAudioHeader(dataLen, M.getInit(thisIdx)); |           bs = TS::getAudioHeader(dataLen, M.getInit(thisIdx)); | ||||||
|  |  | ||||||
|  | @ -148,6 +148,14 @@ namespace Mist{ | ||||||
|     capa["optional"]["streamname"]["short"] = "s"; |     capa["optional"]["streamname"]["short"] = "s"; | ||||||
|     capa["optional"]["streamname"]["default"] = ""; |     capa["optional"]["streamname"]["default"] = ""; | ||||||
| 
 | 
 | ||||||
|  |     capa["optional"]["filelimit"]["name"] = "Open file descriptor limit"; | ||||||
|  |     capa["optional"]["filelimit"]["help"] = "Increase open file descriptor to this value if current system value is lower. A higher value may be needed for handling many concurrent SRT connections."; | ||||||
|  | 
 | ||||||
|  |     capa["optional"]["filelimit"]["type"] = "int"; | ||||||
|  |     capa["optional"]["filelimit"]["option"] = "--filelimit"; | ||||||
|  |     capa["optional"]["filelimit"]["short"] = "l"; | ||||||
|  |     capa["optional"]["filelimit"]["default"] = "1024"; | ||||||
|  | 
 | ||||||
|     capa["optional"]["acceptable"]["name"] = "Acceptable connection types"; |     capa["optional"]["acceptable"]["name"] = "Acceptable connection types"; | ||||||
|     capa["optional"]["acceptable"]["help"] = |     capa["optional"]["acceptable"]["help"] = | ||||||
|         "Whether to allow only incoming pushes (2), only outgoing pulls (1), or both (0, default)"; |         "Whether to allow only incoming pushes (2), only outgoing pulls (1), or both (0, default)"; | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Ramkoemar
						Ramkoemar