Mercurial > pidgin.yaz
view src/rvous.c @ 958:df664ea5eced
[gaim-migrate @ 968]
This is mostly just a test.
committer: Tailor Script <tailor@pidgin.im>
author | Eric Warmenhoven <eric@warmenhoven.org> |
---|---|
date | Mon, 09 Oct 2000 09:18:19 +0000 |
parents | 2876c40108cd |
children | 2586b2a3725e |
line wrap: on
line source
/* * gaim * * Copyright (C) 1998-1999, Mark Spencer <markster@marko.net> * * This program 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. * * This program 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 this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * */ #ifdef HAVE_CONFIG_H #include "../config.h" #endif #include <string.h> #include <stdio.h> #include <stdlib.h> #include <time.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/socket.h> #include <netdb.h> #include <netinet/in.h> #include <unistd.h> #include <netinet/in.h> #include <arpa/inet.h> #include <errno.h> #include <gtk/gtk.h> #include "gaim.h" static void do_send_file(GtkWidget *, struct file_transfer *); static void do_get_file (GtkWidget *, struct file_transfer *); static int snpa = -1; static void toggle(GtkWidget *w, int *m) { *m = !(*m); } static void free_ft(struct file_transfer *ft) { if (ft->window) { gtk_widget_destroy(ft->window); ft->window = NULL; } if (ft->filename) g_free(ft->filename); if (ft->user) g_free(ft->user); if (ft->message) g_free(ft->message); if (ft->ip) g_free(ft->ip); if (ft->cookie) g_free(ft->cookie); g_free(ft); } static void warn_callback(GtkWidget *widget, struct file_transfer *ft) { show_warn_dialog(ft->user); } static void info_callback(GtkWidget *widget, struct file_transfer *ft) { serv_get_info(ft->user); } static void cancel_callback(GtkWidget *widget, struct file_transfer *ft) { if (ft->accepted) { return; } serv_rvous_cancel(ft->user, ft->cookie, ft->UID); free_ft(ft); } static void accept_callback(GtkWidget *widget, struct file_transfer *ft) { char buf[BUF_LEN]; char fname[BUF_LEN]; char *c; if (!strcmp(ft->UID, FILE_SEND_UID)) { c = ft->filename + strlen(ft->filename); while (c != ft->filename) { if (*c == '/' || *c == '\\') { strcpy(fname, c+1); break; } c--; } if (c == ft->filename) strcpy(fname, ft->filename); } gtk_widget_destroy(ft->window); ft->window = NULL; ft->window = gtk_file_selection_new(_("Gaim - Save As...")); gtk_file_selection_hide_fileop_buttons(GTK_FILE_SELECTION(ft->window)); if (!strcmp(ft->UID, FILE_SEND_UID)) g_snprintf(buf, BUF_LEN - 1, "%s/%s", getenv("HOME"), fname); else g_snprintf(buf, BUF_LEN - 1, "%s/", getenv("HOME")); gtk_file_selection_set_filename(GTK_FILE_SELECTION(ft->window), buf); gtk_signal_connect(GTK_OBJECT(ft->window), "destroy", GTK_SIGNAL_FUNC(cancel_callback), ft); if (!strcmp(ft->UID, FILE_SEND_UID)) { gtk_signal_connect(GTK_OBJECT( GTK_FILE_SELECTION(ft->window)->ok_button), "clicked", GTK_SIGNAL_FUNC(do_get_file), ft); } else { gtk_signal_connect(GTK_OBJECT( GTK_FILE_SELECTION(ft->window)->ok_button), "clicked", GTK_SIGNAL_FUNC(do_send_file), ft); } gtk_signal_connect(GTK_OBJECT(GTK_FILE_SELECTION(ft->window)->cancel_button), "clicked", GTK_SIGNAL_FUNC(cancel_callback), ft); gtk_widget_show(ft->window); } static int read_file_header(int fd, struct file_header *header) { char buf[257]; int read_rv = read(fd, buf, 256); if (read_rv < 0) return read_rv; memcpy(&header->magic, buf + 0, 4); memcpy(&header->hdrlen, buf + 4, 2); memcpy(&header->hdrtype, buf + 6, 2); memcpy(&header->bcookie, buf + 8, 8); memcpy(&header->encrypt, buf + 16, 2); memcpy(&header->compress, buf + 18, 2); memcpy(&header->totfiles, buf + 20, 2); memcpy(&header->filesleft, buf + 22, 2); memcpy(&header->totparts, buf + 24, 2); memcpy(&header->partsleft, buf + 26, 2); memcpy(&header->totsize, buf + 28, 4); memcpy(&header->size, buf + 32, 4); memcpy(&header->modtime, buf + 36, 4); memcpy(&header->checksum, buf + 40, 4); memcpy(&header->rfrcsum, buf + 44, 4); memcpy(&header->rfsize, buf + 48, 4); memcpy(&header->cretime, buf + 52, 4); memcpy(&header->rfcsum, buf + 56, 4); memcpy(&header->nrecvd, buf + 60, 4); memcpy(&header->recvcsum, buf + 64, 4); memcpy(&header->idstring, buf + 68, 32); memcpy(&header->flags, buf + 100, 1); memcpy(&header->lnameoffset, buf + 101, 1); memcpy(&header->lsizeoffset, buf + 102, 1); memcpy(&header->dummy, buf + 103, 69); memcpy(&header->macfileinfo, buf + 172, 16); memcpy(&header->nencode, buf + 188, 2); memcpy(&header->nlanguage, buf + 190, 2); memcpy(&header->name, buf + 192, 64); return read_rv; } static int write_file_header(int fd, struct file_header *header) { char buf[257]; memcpy(buf + 0, &header->magic, 4); memcpy(buf + 4, &header->hdrlen, 2); memcpy(buf + 6, &header->hdrtype, 2); memcpy(buf + 8, &header->bcookie, 8); memcpy(buf + 16, &header->encrypt, 2); memcpy(buf + 18, &header->compress, 2); memcpy(buf + 20, &header->totfiles, 2); memcpy(buf + 22, &header->filesleft, 2); memcpy(buf + 24, &header->totparts, 2); memcpy(buf + 26, &header->partsleft, 2); memcpy(buf + 28, &header->totsize, 4); memcpy(buf + 32, &header->size, 4); memcpy(buf + 36, &header->modtime, 4); memcpy(buf + 40, &header->checksum, 4); memcpy(buf + 44, &header->rfrcsum, 4); memcpy(buf + 48, &header->rfsize, 4); memcpy(buf + 52, &header->cretime, 4); memcpy(buf + 56, &header->rfcsum, 4); memcpy(buf + 60, &header->nrecvd, 4); memcpy(buf + 64, &header->recvcsum, 4); memcpy(buf + 68, &header->idstring, 32); memcpy(buf + 100, &header->flags, 1); memcpy(buf + 101, &header->lnameoffset, 1); memcpy(buf + 102, &header->lsizeoffset, 1); memcpy(buf + 103, &header->dummy, 69); memcpy(buf + 172, &header->macfileinfo, 16); memcpy(buf + 188, &header->nencode, 2); memcpy(buf + 190, &header->nlanguage, 2); memcpy(buf + 192, &header->name, 64); return write(fd, buf, 256); } static void do_get_file(GtkWidget *w, struct file_transfer *ft) { char *file = gtk_file_selection_get_filename(GTK_FILE_SELECTION(ft->window)); char buf[BUF_LONG]; char *buf2; struct file_header header; int read_rv; guint32 rcv; int cont = 1; GtkWidget *fw = NULL, *fbar = NULL, *label = NULL; GtkWidget *button = NULL, *pct = NULL; if (!(ft->f = fopen(file,"w"))) { g_snprintf(buf, BUF_LONG / 2, _("Error writing file %s"), file); do_error_dialog(buf, _("Error")); ft->accepted = 0; accept_callback(NULL, ft); return; } ft->accepted = 1; gtk_widget_destroy(ft->window); ft->window = NULL; serv_rvous_accept(ft->user, ft->cookie, ft->UID); ft->fd = connect_address(inet_addr(ft->ip), ft->port); if (ft->fd <= -1) { fclose(ft->f); free_ft(ft); return; } read_rv = read_file_header(ft->fd, &header); if(read_rv < 0) { close(ft->fd); fclose(ft->f); free_ft(ft); return; } sprintf(debug_buff, "header length %d\n", header.hdrlen); debug_print(debug_buff); header.hdrtype = 0x202; buf2 = frombase64(ft->cookie); memcpy(header.bcookie, buf2, 8); snprintf(header.idstring, 32, "Gaim"); g_free(buf2); header.encrypt = 0; header.compress = 0; header.totparts = 1; header.partsleft = 1; sprintf(debug_buff, "sending confirmation\n"); debug_print(debug_buff); write_file_header(ft->fd, &header); rcv = 0; fw = gtk_dialog_new(); snprintf(buf, 2048, _("Receiving %s from %s"), ft->filename, ft->user); label = gtk_label_new(buf); gtk_box_pack_start(GTK_BOX(GTK_DIALOG(fw)->vbox), label, 0, 0, 5); gtk_widget_show(label); fbar = gtk_progress_bar_new(); gtk_box_pack_start(GTK_BOX(GTK_DIALOG(fw)->action_area), fbar, 0, 0, 5); gtk_widget_show(fbar); pct = gtk_label_new("0 %"); gtk_box_pack_start(GTK_BOX(GTK_DIALOG(fw)->action_area), pct, 0, 0, 5); gtk_widget_show(pct); button = gtk_button_new_with_label(_("Cancel")); gtk_box_pack_start(GTK_BOX(GTK_DIALOG(fw)->action_area), button, 0, 0, 5); gtk_widget_show(button); if (display_options & OPT_DISP_COOL_LOOK) gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE); gtk_signal_connect(GTK_OBJECT(button), "clicked", (GtkSignalFunc)toggle, &cont); gtk_window_set_title(GTK_WINDOW(fw), _("Gaim - File Transfer")); gtk_widget_realize(fw); aol_icon(fw->window); gtk_widget_show(fw); sprintf(debug_buff, "Receiving %s from %s (%d bytes)\n", ft->filename, ft->user, ft->size); debug_print(debug_buff); fcntl(ft->fd, F_SETFL, O_NONBLOCK); while (rcv != ft->size && cont) { int i; float pcnt = ((float)rcv)/((float)ft->size); int remain = ft->size - rcv > 1024 ? 1024 : ft->size - rcv; read_rv = read(ft->fd, buf, remain); if(read_rv < 0) { if (errno == EWOULDBLOCK) { while(gtk_events_pending()) gtk_main_iteration(); continue; } gtk_widget_destroy(fw); fw = NULL; fclose(ft->f); close(ft->fd); free_ft(ft); return; } rcv += read_rv; for (i = 0; i < read_rv; i++) fprintf(ft->f, "%c", buf[i]); gtk_progress_bar_update(GTK_PROGRESS_BAR(fbar), pcnt); sprintf(buf, "%d / %d K (%2.0f %%)", rcv/1024, ft->size/1024, 100*pcnt); gtk_label_set_text(GTK_LABEL(pct), buf); while(gtk_events_pending()) gtk_main_iteration(); } fclose(ft->f); gtk_widget_destroy(fw); fw = NULL; if (!cont) { char *tmp = frombase64(ft->cookie); serv_rvous_cancel(ft->user, tmp, ft->UID); close(ft->fd); free_ft(ft); return; } sprintf(debug_buff, "Download complete.\n"); debug_print(debug_buff); header.hdrtype = 0x402; header.totparts = 0; header.partsleft = 0; header.flags = 0; header.recvcsum = header.checksum; header.nrecvd = header.totsize; write_file_header(ft->fd, &header); close(ft->fd); free_ft(ft); } static void send_file_callback(gpointer data, gint source, GdkInputCondition condition) { struct file_transfer *ft = (struct file_transfer *)data; struct file_header fhdr; int read_rv; char buf[2048]; GtkWidget *fw = NULL, *fbar = NULL, *label = NULL; GtkWidget *button = NULL, *pct = NULL; int rcv, cont; gdk_input_remove(snpa); snpa = -1; if (condition & GDK_INPUT_EXCEPTION) { fclose(ft->f); close(ft->fd); free_ft(ft); return; } /* OK, so here's what's going on: we need to send a file. The person * sends us a file_header, then we send a file_header, then they send * us a file header, then we send the file, then they send a header, * and we're done. */ /* we can do some neat tricks because the other person sends us back * all the information we need in the file_header */ read_rv = read_file_header(ft->fd, &fhdr); if (read_rv < 0) { fclose(ft->f); close(ft->fd); free_ft(ft); return; } if (fhdr.hdrtype != 0xc12) { sprintf(debug_buff, "%s decided to cancel. (%x)\n", ft->user, fhdr.hdrtype); debug_print(debug_buff); fclose(ft->f); close(ft->fd); free_ft(ft); return; } /* now we need to set the hdrtype to a certain value, but I don't know * what that value is, and I don't happen to have a win computer to do * my sniffing from :-P */ fhdr.hdrtype = ntohs(0x101); fhdr.totfiles = 1; fhdr.filesleft = 1; fhdr.flags = 0x20; write_file_header(ft->fd, &fhdr); read_file_header(ft->fd, &fhdr); fw = gtk_dialog_new(); snprintf(buf, 2048, _("Sending %s to %s"), fhdr.name, ft->user); label = gtk_label_new(buf); gtk_box_pack_start(GTK_BOX(GTK_DIALOG(fw)->vbox), label, 0, 0, 5); gtk_widget_show(label); fbar = gtk_progress_bar_new(); gtk_box_pack_start(GTK_BOX(GTK_DIALOG(fw)->action_area), fbar, 0, 0, 5); gtk_widget_show(fbar); pct = gtk_label_new("0 %"); gtk_box_pack_start(GTK_BOX(GTK_DIALOG(fw)->action_area), pct, 0, 0, 5); gtk_widget_show(pct); button = gtk_button_new_with_label(_("Cancel")); gtk_box_pack_start(GTK_BOX(GTK_DIALOG(fw)->action_area), button, 0, 0, 5); gtk_widget_show(button); if (display_options & OPT_DISP_COOL_LOOK) gtk_button_set_relief(GTK_BUTTON(button), GTK_RELIEF_NONE); gtk_signal_connect(GTK_OBJECT(button), "clicked", (GtkSignalFunc)toggle, &cont); gtk_window_set_title(GTK_WINDOW(fw), _("Gaim - File Transfer")); gtk_widget_realize(fw); aol_icon(fw->window); gtk_widget_show(fw); sprintf(debug_buff, "Sending %s to %s (%ld bytes)\n", fhdr.name, ft->user, fhdr.totsize); debug_print(debug_buff); rcv = 0; cont = 1; while (rcv != ntohl(fhdr.totsize) && cont) { int i; float pcnt = ((float)rcv)/((float)ntohl(fhdr.totsize)); int remain = ntohl(fhdr.totsize) - rcv > 1024 ? 1024 : ntohl(fhdr.totsize) - rcv; for (i = 0; i < remain; i++) fscanf(ft->f, "%c", &buf[i]); read_rv = write(ft->fd, buf, remain); if (read_rv < 0) { fclose(ft->f); gtk_widget_destroy(fw); fw = NULL; fclose(ft->f); close(ft->fd); free_ft(ft); return; } rcv += read_rv; gtk_progress_bar_update(GTK_PROGRESS_BAR(fbar), pcnt); sprintf(buf, "%d / %d K (%2.0f %%)", rcv/1024, ntohl(fhdr.totsize)/1024, 100*pcnt); gtk_label_set_text(GTK_LABEL(pct), buf); while(gtk_events_pending()) gtk_main_iteration(); } fclose(ft->f); gtk_widget_destroy(fw); fw = NULL; if (!cont) { char *tmp = frombase64(ft->cookie); serv_rvous_cancel(ft->user, tmp, ft->UID); close(ft->fd); free_ft(ft); return; } sprintf(debug_buff, "Upload complete.\n"); debug_print(debug_buff); read_file_header(ft->fd, &fhdr); close(ft->fd); free_ft(ft); } static void do_send_file(GtkWidget *w, struct file_transfer *ft) { char *file = g_strdup(gtk_file_selection_get_filename(GTK_FILE_SELECTION(ft->window))); char buf[BUF_LONG]; char *buf2; int read_rv; struct file_header fhdr; guint32 rcv = 0; char *c; struct stat st; struct tm *fortime; stat(file, &st); if (!(ft->f = fopen(file, "r"))) { g_snprintf(buf, BUF_LONG / 2, _("Error reading file %s"), file); do_error_dialog(buf, _("Error")); ft->accepted = 0; accept_callback(NULL, ft); free_ft(ft); return; } ft->accepted = 1; ft->filename = file; gtk_widget_destroy(ft->window); ft->window = NULL; serv_rvous_accept(ft->user, ft->cookie, ft->UID); ft->fd = connect_address(inet_addr(ft->ip), ft->port); if (ft->fd <= -1) { free_ft(ft); return; } /* here's where we differ from do_get_file */ /* 1. build/send header * 2. receive header * 3. send listing file * 4. receive header * * then we need to wait to actually send the file, if they want. * */ /* 1. build/send header */ c = file + strlen(file); while (*(c - 1) != '/') c--; buf2 = frombase64(ft->cookie); sprintf(debug_buff, "Building header to send %s (cookie: %s)\n", file, buf2); debug_print(debug_buff); fhdr.magic[0] = 'O'; fhdr.magic[1] = 'F'; fhdr.magic[2] = 'T'; fhdr.magic[3] = '2'; fhdr.hdrlen = htons(256); fhdr.hdrtype = htons(0x1108); snprintf(fhdr.bcookie, 8, "%s", buf2); g_free(buf2); fhdr.encrypt = 0; fhdr.compress = 0; fhdr.totfiles = htons(1); fhdr.filesleft = htons(1); fhdr.totparts = htons(1); fhdr.partsleft = htons(1); fhdr.totsize = htonl((long)st.st_size); /* combined size of all files */ /* size = strlen("mm/dd/yyyy hh:mm sizesize 'name'\r\n") */ fhdr.size = htonl(28 + strlen(c)); /* size of listing.txt */ fhdr.modtime = htonl(time(NULL)); /* time since UNIX epoch */ fhdr.checksum = htonl(0x89f70000); /* ? i don't think this matters */ fhdr.rfrcsum = 0; fhdr.rfsize = 0; fhdr.cretime = 0; fhdr.rfcsum = 0; fhdr.nrecvd = 0; fhdr.recvcsum = 0; snprintf(fhdr.idstring, 32, "OFT_Windows ICBMFT V1.1 32"); fhdr.flags = 0x02; /* don't ask me why */ fhdr.lnameoffset = 0x1A; /* ? still no clue */ fhdr.lsizeoffset = 0x10; /* whatever */ memset(fhdr.dummy, 0, 69); memset(fhdr.macfileinfo, 0, 16); fhdr.nencode = 0; fhdr.nlanguage = 0; snprintf(fhdr.name, 64, "listing.txt"); read_rv = write_file_header(ft->fd, &fhdr); if (read_rv <= -1) { sprintf(debug_buff, "Couldn't write opening header\n"); debug_print(debug_buff); close(ft->fd); free_ft(ft); return; } /* 2. receive header */ sprintf(debug_buff, "Receiving header\n"); debug_print(debug_buff); read_rv = read_file_header(ft->fd, &fhdr); if (read_rv <= -1) { sprintf(debug_buff, "Couldn't read header\n"); debug_print(debug_buff); close(ft->fd); free_ft(ft); return; } /* 3. send listing file */ /* mm/dd/yyyy hh:mm sizesize name.ext\r\n */ /* creation date ^ */ sprintf(debug_buff, "Sending file\n"); debug_print(debug_buff); fortime = localtime(&st.st_ctime); snprintf(buf, ntohl(fhdr.size) + 1, "%2d/%2d/%4d %2d:%2d %8ld %s\r\n", fortime->tm_mon + 1, fortime->tm_mday, fortime->tm_year + 1900, fortime->tm_hour + 1, fortime->tm_min + 1, st.st_size, c); sprintf(debug_buff, "Sending listing.txt (%d bytes) to %s\n", ntohl(fhdr.size) + 1, ft->user); debug_print(debug_buff); read_rv = write(ft->fd, buf, ntohl(fhdr.size)); if (read_rv <= -1) { sprintf(debug_buff, "Could not send file, wrote %d\n", rcv); debug_print(debug_buff); close(ft->fd); free_ft(ft); return; } /* 4. receive header */ sprintf(debug_buff, "Receiving closing header\n"); debug_print(debug_buff); read_rv = read_file_header(ft->fd, &fhdr); if (read_rv <= -1) { sprintf(debug_buff, "Couldn't read closing header\n"); debug_print(debug_buff); close(ft->fd); free_ft(ft); return; } snpa = gdk_input_add(ft->fd, GDK_INPUT_READ | GDK_INPUT_EXCEPTION, send_file_callback, ft); } void accept_file_dialog(struct file_transfer *ft) { GtkWidget *accept, *info, *warn, *cancel; GtkWidget *label; GtkWidget *vbox, *bbox; char buf[1024]; ft->window = gtk_window_new(GTK_WINDOW_DIALOG); gtk_window_set_wmclass(GTK_WINDOW(ft->window), "accept_file", "Gaim"); accept = gtk_button_new_with_label(_("Accept")); info = gtk_button_new_with_label(_("Info")); warn = gtk_button_new_with_label(_("Warn")); cancel = gtk_button_new_with_label(_("Cancel")); bbox = gtk_hbox_new(TRUE, 10); vbox = gtk_vbox_new(FALSE, 5); gtk_widget_show(accept); gtk_widget_show(info); gtk_widget_show(warn); gtk_widget_show(cancel); if (display_options & OPT_DISP_COOL_LOOK) { gtk_button_set_relief(GTK_BUTTON(accept), GTK_RELIEF_NONE); gtk_button_set_relief(GTK_BUTTON(info), GTK_RELIEF_NONE); gtk_button_set_relief(GTK_BUTTON(warn), GTK_RELIEF_NONE); gtk_button_set_relief(GTK_BUTTON(cancel), GTK_RELIEF_NONE); } gtk_box_pack_start(GTK_BOX(bbox), accept, TRUE, TRUE, 10); gtk_box_pack_start(GTK_BOX(bbox), info, TRUE, TRUE, 10); gtk_box_pack_start(GTK_BOX(bbox), warn, TRUE, TRUE, 10); gtk_box_pack_start(GTK_BOX(bbox), cancel, TRUE, TRUE, 10); if (!strcmp(ft->UID, FILE_SEND_UID)) { g_snprintf(buf, sizeof(buf), _("%s requests you to accept the file: %s (%d bytes)"), ft->user, ft->filename, ft->size); } else { g_snprintf(buf, sizeof(buf), _("%s requests you to send them a file"), ft->user); } if (ft->message) strncat(buf, ft->message, sizeof(buf) - strlen(buf)); label = gtk_label_new(buf); gtk_widget_show(label); gtk_box_pack_start(GTK_BOX(vbox), label, TRUE, TRUE, 5); gtk_box_pack_start(GTK_BOX(vbox), bbox, TRUE, TRUE, 5); gtk_window_set_title(GTK_WINDOW(ft->window), _("Gaim - File Transfer?")); gtk_window_set_focus(GTK_WINDOW(ft->window), accept); gtk_container_add(GTK_CONTAINER(ft->window), vbox); gtk_container_border_width(GTK_CONTAINER(ft->window), 10); gtk_widget_show(vbox); gtk_widget_show(bbox); gtk_widget_realize(ft->window); aol_icon(ft->window->window); gtk_widget_show(ft->window); gtk_signal_connect(GTK_OBJECT(accept), "clicked", GTK_SIGNAL_FUNC(accept_callback), ft); gtk_signal_connect(GTK_OBJECT(cancel), "clicked", GTK_SIGNAL_FUNC(cancel_callback), ft); gtk_signal_connect(GTK_OBJECT(warn), "clicked", GTK_SIGNAL_FUNC(warn_callback), ft); gtk_signal_connect(GTK_OBJECT(info), "clicked", GTK_SIGNAL_FUNC(info_callback), ft); if (ft->message) { /* we'll do this later while(gtk_events_pending()) gtk_main_iteration(); html_print(text, ft->message); */ } }