Hi,
this is my second parq patch. It should support both active queueing and
PARQ 1.0 now at the client side. Server side support is not implemented
yet.
I still don't know how to include a new file into a patch (I tried cvs
diff -uRN) so parq.[ch] are attached as a seperate file.
Please review (And test if you like).
With regards,
Jeroen Asselman
Index: src/downloads.c
===================================================================
RCS file: /cvsroot/gtk-gnutella/gtk-gnutella-current/src/downloads.c,v
retrieving revision 1.194
diff -u -r1.194 downloads.c
--- src/downloads.c 1 Feb 2003 23:16:54 -0000 1.194
+++ src/downloads.c 2 Feb 2003 20:53:01 -0000
@@ -30,7 +30,6 @@
#include "sockets.h"
#include "downloads.h"
#include "hosts.h"
-#include "header.h"
#include "routing.h"
#include "routing.h"
#include "gmsg.h"
@@ -47,6 +46,8 @@
#include "settings.h"
#include "nodes.h"
+#include "parq.h"
+
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
@@ -313,6 +314,7 @@
l = l->next;
switch (d->status) {
+ case GTA_DL_ACTIVE_QUEUED:
case GTA_DL_RECEIVING:
case GTA_DL_HEADERS:
case GTA_DL_PUSH_SENT:
@@ -327,6 +329,9 @@
}
switch (d->status) {
+ case GTA_DL_ACTIVE_QUEUED:
+ t = d->queue_status.retry_delay;
+ break;
case GTA_DL_PUSH_SENT:
case GTA_DL_FALLBACK:
t = download_push_sent_timeout;
@@ -342,7 +347,15 @@
}
if (now - d->last_update > t) {
- if (d->status == GTA_DL_CONNECTING)
+ /*
+ * When the 'timeout' has expired, first check wether the
+ * download was activly queued. If so, tell parq to retry the
+ * download in which case the HTTP connection wasn't closed
+ * JA 31 jan 2003
+ */
+ if (d->status == GTA_DL_ACTIVE_QUEUED)
+ parq_download_retry_active_queued(d);
+ else if (d->status == GTA_DL_CONNECTING)
download_fallback_to_push(d, TRUE, FALSE);
else if (d->status == GTA_DL_HEADERS)
download_incomplete_header(d);
@@ -1902,7 +1915,7 @@
* Returns TRUE if we may continue with the download, FALSE if it has been
* stopped due to a problem.
*/
-static gboolean download_start_prepare_running(struct download *d)
+gboolean download_start_prepare_running(struct download *d)
{
struct dl_file_info *fi = d->file_info;
@@ -1986,7 +1999,7 @@
* Returns TRUE if we may continue with the download, FALSE if it has been
* stopped due to a problem.
*/
-static gboolean download_start_prepare(struct download *d)
+gboolean download_start_prepare(struct download *d)
{
g_assert(d != NULL);
g_assert(d->list_idx != DL_LIST_RUNNING); /* Not already running */
@@ -3784,7 +3797,7 @@
*
* Extract Retry-After delay from header, returning 0 if none.
*/
-static guint extract_retry_after(header_t *header)
+guint extract_retry_after(header_t *header)
{
gchar *buf;
guint delay = 0;
@@ -4042,7 +4055,7 @@
{
gchar *buf;
gchar *available = "X-Available-Ranges";
-
+
if (d->ranges != NULL) {
http_range_free(d->ranges);
d->ranges = NULL;
@@ -4051,6 +4064,9 @@
if (!d->file_info->use_swarming)
return;
+ g_assert(header != NULL);
+ g_assert(header->headers != NULL);
+
buf = header_get(header, available);
if (buf == NULL)
@@ -4173,7 +4189,7 @@
struct dl_file_info *fi;
gchar short_read[80];
guint delay;
-
+
/*
* If `ok' is FALSE, we might not even have fully read the status line,
* in which case `s->getline' will be null.
@@ -4236,7 +4252,7 @@
ack_code = http_status_parse(status, "HTTP",
&ack_message, &http_major, &http_minor);
-
+
if (!download_check_status(d, s->getline, ack_code))
return;
@@ -4284,20 +4300,58 @@
"[short %d line%s header] ", count, count == 1 ? "" : "s");
}
- /*
- * If we made a /uri-res/N2R? request, yet if the download still
- * has the old index/name indication, convert it to a /uri-res/.
- */
-
+
if (ack_code == 503 || (ack_code >= 200 && ack_code <= 299)) {
+
+ /*
+ * If we made a /uri-res/N2R? request, yet if the download still
+ * has the old index/name indication, convert it to a /uri-res/.
+ */
if (d->record_index != URN_INDEX && (d->flags & DL_F_URIRES))
if (!download_convert_to_urires(d))
return;
+
+ /*
+ * The download could be remotely queued. Check this now before
+ * continueing at all.
+ * JA, 31 jan 2003
+ */
+ if (ack_code == 503) {
+
+ /* Check for queued status */
+ if (parq_download_supports_parq(header)) {
+
+ parq_download_parse_queue_status(d, header);
+
+ /* If we are queued there is nothing anymore we can do for now*/
+ if (parq_download_is_active_queued(d)) {
+
+ /* Make sure we're waiting for the right file,
+ collect alt-locs */
+ if (check_content_urn(d, header)) {
+
+ /* Update mesh */
+ if (!d->always_push && d->sha1)
+ dmesh_add(d->sha1, ip, port, d->record_index,
+ d->file_name, 0);
+
+ return;
+
+ } /* Check content urn failed */
+
+ return;
+
+ } /* Download not active queued, continue as normal */
+ d->status = GTA_DL_HEADERS;
+ }
+ } /* ack_code was not 503 */
}
update_available_ranges(d, header); /* Updates `d->ranges' */
delay = extract_retry_after(header);
+
+
/*
* Partial File Sharing Protocol (PFSP) -- client-side
*
@@ -4411,11 +4465,13 @@
gui_update_download(d, TRUE);
}
} else {
+ /* Host might support queueing. If so, retreive queue status */
/* Server has nothing for us yet, give it time */
download_queue_delay(d,
MAX(delay, download_retry_refused_delay),
"Partial file on server, waiting");
}
+
return;
default:
break;
@@ -4459,11 +4515,10 @@
if (download_retry_no_urires(d, delay, ack_code))
return;
break;
- case 503: /* Busy */
+ case 503: /* Busy */
/* Make sure we're waiting for the right file, collect alt-locs */
if (!check_content_urn(d, header))
return;
-
/*
* If we made a follow-up request, mark host as not reliable.
*
@@ -4509,10 +4564,11 @@
/*
* If an URN is present, validate that we can continue this download.
- */
+ */
+
+ if (!check_content_urn(d, header))
+ return;
- if (!check_content_urn(d, header))
- return;
/*
* If they configured us to require a server name, and we have none
@@ -4920,7 +4976,11 @@
"Host: %s\r\n"
"User-Agent: %s\r\n",
ip_port_to_gchar(download_ip(d), download_port(d)), version_string);
-
+
+ /*
+ * Add X-Queue / X-Queued information into the header
+ */
+ parq_download_add_header(dl_tmp, &rw, d);
/*
* If server is known to NOT support keepalives, then request only
* a range starting from d->skip. Likewise if we don't know whether
Index: src/downloads.h
===================================================================
RCS file: /cvsroot/gtk-gnutella/gtk-gnutella-current/src/downloads.h,v
retrieving revision 1.59
diff -u -r1.59 downloads.h
--- src/downloads.h 27 Jan 2003 22:20:37 -0000 1.59
+++ src/downloads.h 2 Feb 2003 20:53:01 -0000
@@ -28,6 +28,9 @@
#include "bsched.h"
#include "fileinfo.h"
+#include "header.h"
+
+#define PARQ_MAX_ID_LENGTH 40
/*
* We keep a list of all the downloads queued per GUID+IP:port (host). Indeed
@@ -66,6 +69,19 @@
guint32 attrs;
};
+/*
+ * Download dependent queuing status.
+ */
+struct dl_queued {
+ guint16 position; /* Current position in the queue */
+ guint16 length; /* Current queue length */
+ guint16 ETA; /* Estimated time till upload slot retreived */
+ guint16 lifetime; /* Maximum interval before loosing queue
+ position */
+ guint16 retry_delay; /* Interval between new attempt */
+ gchar ID[PARQ_MAX_ID_LENGTH]; /* PARQ Queue ID */
+};
+
struct download {
gchar error_str[256]; /* Used to sprintf() error strings with vars */
guint32 status; /* Current status of the download */
@@ -113,6 +129,8 @@
gboolean visible; /* The download is visible in the GUI */
gboolean push; /* Currently in push mode */
gboolean always_push; /* Always use the push method for this download */
+
+ struct dl_queued queue_status; /* Queuing status */
};
/*
@@ -139,6 +157,10 @@
#define GTA_DL_DONE 18 /* All done! */
#define GTA_DL_SINKING 19 /* Sinking HTML reply */
+// JA, 31 jan 2003 GTA_DL_ACTIVE_QUEUED
+#define GTA_DL_ACTIVE_QUEUED 20 /* Actively queued */
+#define GTA_DL_PASSIVE_QUEUED 21 /* Passively queued */
+
/*
* Download flags.
*/
@@ -209,11 +231,13 @@
#define DOWNLOAD_IS_WAITING(d) \
( (d)->status == GTA_DL_TIMEOUT_WAIT)
+// JA, 31 jan 2003 GTA_DL_ACTIVE_QUEUED
#define DOWNLOAD_IS_ESTABLISHING(d) \
( (d)->status == GTA_DL_CONNECTING \
|| (d)->status == GTA_DL_PUSH_SENT \
|| (d)->status == GTA_DL_FALLBACK \
|| (d)->status == GTA_DL_REQ_SENT \
+ || (d)->status == GTA_DL_ACTIVE_QUEUED \
|| (d)->status == GTA_DL_HEADERS )
#define DOWNLOAD_IS_EXPECTING_GIV(d) \
@@ -256,6 +280,7 @@
void download_abort(struct download *);
void download_resume(struct download *);
void download_start(struct download *, gboolean);
+gboolean download_start_prepare_running(struct download *d);
void download_requeue(struct download *);
void download_send_request(struct download *);
void download_retry(struct download *);
@@ -277,6 +302,10 @@
void download_move_progress(struct download *d, guint32 copied);
void download_move_done(struct download *d, time_t elapsed);
void download_move_error(struct download *d);
+
+gboolean download_start_prepare(struct download *d);
+
+guint extract_retry_after(header_t *header);
#endif /* _downloads_h_ */
Index: src/downloads_gui.c
===================================================================
RCS file: /cvsroot/gtk-gnutella/gtk-gnutella-current/src/downloads_gui.c,v
retrieving revision 1.17
diff -u -r1.17 downloads_gui.c
--- src/downloads_gui.c 1 Feb 2003 23:16:54 -0000 1.17
+++ src/downloads_gui.c 2 Feb 2003 20:53:04 -0000
@@ -28,6 +28,7 @@
#include "downloads.h" // FIXME: remove this dependency
#include "statusbar_gui.h"
#include "downloads_cb.h"
+#include "parq.h"
RCSID("$Id: downloads_gui.c,v 1.17 2003/02/01 23:16:54 rmanfredi Exp $");
@@ -83,6 +84,30 @@
fi = d->file_info;
switch (d->status) {
+ case GTA_DL_ACTIVE_QUEUED: // JA, 31 jan 2003 Active queueing
+ {
+ gint when = d->timeout_delay - (now - d->last_update);
+ rw = gm_snprintf(tmpstr, sizeof(tmpstr),
+ "Retry in %ds ", MAX(0, when));
+
+ if (d->queue_status.position > 0) {
+ rw += gm_snprintf(&tmpstr[rw], sizeof(tmpstr)-rw,
+ "Remotely queued at position %d ", d->queue_status.position);
+
+ if (d->queue_status.length > 0) {
+ rw += gm_snprintf(&tmpstr[rw], sizeof(tmpstr)-rw,
+ "out of %d ", d->queue_status.length);
+ }
+
+ if (d->queue_status.ETA > 0) {
+ rw += gm_snprintf(&tmpstr[rw], sizeof(tmpstr)-rw,
+ "ETA: %ds ", d->queue_status.ETA - (now - d->last_update));
+ }
+
+ }
+ a = tmpstr;
+ }
+ break;
case GTA_DL_QUEUED:
a = (gchar *) ((d->remove_msg) ? d->remove_msg : "");
break;
/*
* Copyright (c) 2003 Jeroen Asselman
*
*----------------------------------------------------------------------
* This file is part of gtk-gnutella.
*
* gtk-gnutella is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* gtk-gnutella is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with gtk-gnutella; if not, write to the Free Software
* Foundation, Inc.:
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*----------------------------------------------------------------------
*/
#include "parq.h"
#include "ioheader.h"
#include "sockets.h"
#include "gnutella.h"
RCSID("$Id$");
/*
* get_header_version
*
* Extract the version from a given header. EG:
* X-Queue: 1.0
* major=1 minor=0
*/
gboolean get_header_version(gchar const * const header,
gint *major, gint *minor)
{
return sscanf(header, ": %d.%d", major, minor) != 0;
}
/*
* get_header_value
*
* retreives a value from a header line. If possible the length (in gchars)
* is returned of that value.
*/
gchar* get_header_value(gchar const * const s, gchar const * const attribute,
gint *length)
{
gchar* lowercase_header;
gchar* end;
g_assert(s != NULL);
g_assert(attribute != NULL);
lowercase_header = strcasestr(s, attribute);
if (lowercase_header == NULL)
return NULL;
lowercase_header = strchr(lowercase_header, '=');
if (lowercase_header == NULL)
return NULL;
lowercase_header += sizeof(gchar);
if (length != NULL) {
*length = 0;
end = strchr(lowercase_header, ';');
if (end == NULL)
strchr(lowercase_header, ';');
if (end == NULL) {
/*
* We still couldn't find a delimiter. If there are no other
* values after the current one, 'the end' is just the end off
* this value
*/
if (strchr(lowercase_header, '=') == NULL) {
*length = strlen(lowercase_header);
}
} else {
*length = lowercase_header - end;
}
}
/* Could be NULL */
return lowercase_header;
}
/*
* parq_download_retry_active_queued
*
* Active queued means we didn't close the http connection on a HTTP 503 busy
* when the server supports queueing. So prepare the download structure
* for a 'valid' segment. And re-request the segment.
*/
void parq_download_retry_active_queued(struct download *d)
{
g_assert(d != NULL);
g_assert(d->socket != NULL);
g_assert(d->status == GTA_DL_ACTIVE_QUEUED);
g_assert(parq_download_is_active_queued(d));
if (download_start_prepare_running(d)) {
struct gnutella_socket *s = d->socket;
d->keep_alive = TRUE; /* was reset in start_prepare_running */
/* Will be re initialised in download_send_request */
io_free(d->io_opaque);
d->io_opaque = NULL;
getline_free(s->getline); /* No longer need this */
s->getline = NULL;
/* Resend request for download */
download_send_request(d);
}
}
/*
* parq_download_supports_parq
*
* Wether the server supports queueing or not.
*/
gboolean parq_download_supports_parq(header_t *header)
{
g_assert(header != NULL);
return (NULL != header_get(header, "X-Queue") ||
NULL != header_get(header, "X-Queued"));
}
/*
* parq_download_parse_queue_status
*
* Retreive and parse queueing information.
*/
void parq_download_parse_queue_status(struct download *d, header_t *header)
{
gchar *buf;
gchar *value;
gint major, minor;
gint header_value_length;
g_assert(d != NULL);
g_assert(header != NULL);
buf = header_get(header, "X-Queue");
g_assert(buf != NULL);
if (!get_header_version(buf, &major, &minor)) {
/*
* Could not retreive queueing version. It could be 0.1 but there is no
* way to tell for certain
*/
major = 0;
minor = 1;
}
if (major == 1) {
/* PARQ */
buf = header_get(header, "X-Queued");
}
switch (major) {
case 1:
/* PARQ */
value = get_header_value(buf, "lifetime", NULL);
d->queue_status.lifetime = value == NULL ? 0 : strtoul(value, NULL, 0);
d->queue_status.retry_delay = extract_retry_after(header);
case 0:
/* Active queueing */
value = get_header_value(buf, "pollMin", NULL);
d->queue_status.retry_delay = value == NULL ? 0 : strtoul(value, NULL, 0);
value = get_header_value(buf, "pollmax", NULL);
d->queue_status.lifetime = value == NULL ? 0 : strtoul(value, NULL, 0);
}
value = get_header_value(buf, "position", NULL);
d->queue_status.position = value == NULL ? 0 : strtoul(value, NULL, 0);
value = get_header_value(buf, "length", NULL);
d->queue_status.length = value == NULL ? 0 : strtoul(value, NULL, 0);
value = get_header_value(buf, "ETA", NULL);
d->queue_status.ETA = value == NULL ? 0 : strtoul(value, NULL, 0);
value = get_header_value(buf, "ID", &header_value_length);
if (header_value_length < PARQ_MAX_ID_LENGTH) {
strcpy(d->queue_status.ID, value);
}
if (dbg) {
printf("Queue version: %d.%d, position %d out of %d, retry in %ds\n\r",
major, minor, d->queue_status.position, d->queue_status.length,
d->queue_status.retry_delay);
}
/* FIXME: This isn't 100% correct yet for PARQ, it is for active queueing */
if (parq_download_is_active_queued(d)) {
/*
* Don't keep a chunk busy if we are queued, perhaps another servent
* can complete it for us.
*/
file_info_clear_download(d, TRUE);
d->status = GTA_DL_ACTIVE_QUEUED;
}
d->timeout_delay = d->queue_status.retry_delay;
}
/*
* parq_download_is_active_queued
*
* Wether the download is queued remotely or not.
*/
gboolean parq_download_is_active_queued(struct download *d)
{
g_assert(d != NULL);
return d->queue_status.position > 0 && d->keep_alive;
}
/*
* parq_download_add_header
*
* Adds an:
* X-Queue: 1.0
* X-Queued: position=x; ID=xxxxx
* to the HTTP GET request
*/
void parq_download_add_header(gchar *dl_tmp, gint *rw, struct download *d)
{
*rw += gm_snprintf(&(dl_tmp[*rw]), sizeof(dl_tmp)-*rw,
"X-Queue: %d.%d\r\n"
"X-Queued: position=%d; ID=%s\r\n",
PARQ_VERSION_MAJOR, PARQ_VERSION_MINOR,
d->queue_status.position, d->queue_status.ID);
}
/*
* Copyright (c) 2003 Jeroen Asselman
*
*----------------------------------------------------------------------
* This file is part of gtk-gnutella.
*
* gtk-gnutella is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* gtk-gnutella is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with gtk-gnutella; if not, write to the Free Software
* Foundation, Inc.:
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*----------------------------------------------------------------------
*/
#ifndef _PARQ_H_
#define _PARQ_H_
#include "header.h"
#include "downloads.h"
/*
* PARQ Version information
*/
#define PARQ_VERSION_MAJOR 1
#define PARQ_VERSION_MINOR 0
void parq_download_retry_active_queued(struct download *d);
gboolean parq_download_supports_parq(header_t *header);
void parq_download_parse_queue_status(struct download *d, header_t *header);
gboolean parq_download_is_active_queued(struct download *d);
void parq_download_add_header(gchar *dl_tmp, gint *rw, struct download *d);
#endif /* _PARQ_H_ */