diff options
Diffstat (limited to 'Plugins/jingle/libjingle/talk/session/fileshare')
4 files changed, 2016 insertions, 0 deletions
diff --git a/Plugins/jingle/libjingle/talk/session/fileshare/Makefile.am b/Plugins/jingle/libjingle/talk/session/fileshare/Makefile.am new file mode 100644 index 0000000..a6eebf6 --- /dev/null +++ b/Plugins/jingle/libjingle/talk/session/fileshare/Makefile.am @@ -0,0 +1,7 @@ +libcricketsessionfileshare_la_SOURCES = fileshare.cc + +noinst_HEADERS = fileshare.h + +AM_CPPFLAGS := -DPOSIX +noinst_LTLIBRARIES = libcricketsessionfileshare.la + diff --git a/Plugins/jingle/libjingle/talk/session/fileshare/Makefile.in b/Plugins/jingle/libjingle/talk/session/fileshare/Makefile.in new file mode 100644 index 0000000..3c9cd69 --- /dev/null +++ b/Plugins/jingle/libjingle/talk/session/fileshare/Makefile.in @@ -0,0 +1,446 @@ +# Makefile.in generated by automake 1.9.6 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994, 1995, 1996, 1997, 1998, 1999, 2000, 2001, 2002, +# 2003, 2004, 2005 Free Software Foundation, Inc. +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + + +srcdir = @srcdir@ +top_srcdir = @top_srcdir@ +VPATH = @srcdir@ +pkgdatadir = $(datadir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +top_builddir = ../../.. +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +INSTALL = @INSTALL@ +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +build_triplet = @build@ +host_triplet = @host@ +target_triplet = @target@ +subdir = talk/session/fileshare +DIST_COMMON = $(noinst_HEADERS) $(srcdir)/Makefile.am \ + $(srcdir)/Makefile.in +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/talk/pkg.m4 \ + $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +mkinstalldirs = $(install_sh) -d +CONFIG_HEADER = $(top_builddir)/config.h +CONFIG_CLEAN_FILES = +LTLIBRARIES = $(noinst_LTLIBRARIES) +libcricketsessionfileshare_la_LIBADD = +am_libcricketsessionfileshare_la_OBJECTS = fileshare.lo +libcricketsessionfileshare_la_OBJECTS = \ + $(am_libcricketsessionfileshare_la_OBJECTS) +DEFAULT_INCLUDES = -I. -I$(srcdir) -I$(top_builddir) +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__depfiles_maybe = depfiles +CXXCOMPILE = $(CXX) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) \ + $(AM_CPPFLAGS) $(CPPFLAGS) $(AM_CXXFLAGS) $(CXXFLAGS) +LTCXXCOMPILE = $(LIBTOOL) --tag=CXX --mode=compile $(CXX) $(DEFS) \ + $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) $(CPPFLAGS) \ + $(AM_CXXFLAGS) $(CXXFLAGS) +CXXLD = $(CXX) +CXXLINK = $(LIBTOOL) --tag=CXX --mode=link $(CXXLD) $(AM_CXXFLAGS) \ + $(CXXFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +SOURCES = $(libcricketsessionfileshare_la_SOURCES) +DIST_SOURCES = $(libcricketsessionfileshare_la_SOURCES) +HEADERS = $(noinst_HEADERS) +ETAGS = etags +CTAGS = ctags +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +ALSA_LIBS = @ALSA_LIBS@ +AMDEP_FALSE = @AMDEP_FALSE@ +AMDEP_TRUE = @AMDEP_TRUE@ +AMTAR = @AMTAR@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CPP = @CPP@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXCPP = @CXXCPP@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +ECHO = @ECHO@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EGREP = @EGREP@ +EXEEXT = @EXEEXT@ +EXPAT_LIBS = @EXPAT_LIBS@ +F77 = @F77@ +FFLAGS = @FFLAGS@ +GIPS_FALSE = @GIPS_FALSE@ +GIPS_TRUE = @GIPS_TRUE@ +GLIB_CFLAGS = @GLIB_CFLAGS@ +GLIB_LIBS = @GLIB_LIBS@ +ILBC_CFLAGS = @ILBC_CFLAGS@ +ILBC_LIBS = @ILBC_LIBS@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBTOOL = @LIBTOOL@ +LN_S = @LN_S@ +LTLIBOBJS = @LTLIBOBJS@ +MAKEINFO = @MAKEINFO@ +MEDIA_LIBS = @MEDIA_LIBS@ +OBJEXT = @OBJEXT@ +ORTP_CFLAGS = @ORTP_CFLAGS@ +ORTP_LIBS = @ORTP_LIBS@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PHONE_FALSE = @PHONE_FALSE@ +PHONE_TRUE = @PHONE_TRUE@ +PKG_CONFIG = @PKG_CONFIG@ +RANLIB = @RANLIB@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +SPEEX_CFLAGS = @SPEEX_CFLAGS@ +SPEEX_LIBS = @SPEEX_LIBS@ +STRIP = @STRIP@ +VERSION = @VERSION@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +ac_ct_F77 = @ac_ct_F77@ +ac_ct_RANLIB = @ac_ct_RANLIB@ +ac_ct_STRIP = @ac_ct_STRIP@ +am__fastdepCC_FALSE = @am__fastdepCC_FALSE@ +am__fastdepCC_TRUE = @am__fastdepCC_TRUE@ +am__fastdepCXX_FALSE = @am__fastdepCXX_FALSE@ +am__fastdepCXX_TRUE = @am__fastdepCXX_TRUE@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build = @build@ +build_alias = @build_alias@ +build_cpu = @build_cpu@ +build_os = @build_os@ +build_vendor = @build_vendor@ +datadir = @datadir@ +exec_prefix = @exec_prefix@ +host = @host@ +host_alias = @host_alias@ +host_cpu = @host_cpu@ +host_os = @host_os@ +host_vendor = @host_vendor@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +sysconfdir = @sysconfdir@ +target = @target@ +target_alias = @target_alias@ +target_cpu = @target_cpu@ +target_os = @target_os@ +target_vendor = @target_vendor@ +libcricketsessionfileshare_la_SOURCES = fileshare.cc +noinst_HEADERS = fileshare.h +AM_CPPFLAGS := -DPOSIX +noinst_LTLIBRARIES = libcricketsessionfileshare.la +all: all-am + +.SUFFIXES: +.SUFFIXES: .cc .lo .o .obj +$(srcdir)/Makefile.in: $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh \ + && exit 0; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --gnu talk/session/fileshare/Makefile'; \ + cd $(top_srcdir) && \ + $(AUTOMAKE) --gnu talk/session/fileshare/Makefile +.PRECIOUS: Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +clean-noinstLTLIBRARIES: + -test -z "$(noinst_LTLIBRARIES)" || rm -f $(noinst_LTLIBRARIES) + @list='$(noinst_LTLIBRARIES)'; for p in $$list; do \ + dir="`echo $$p | sed -e 's|/[^/]*$$||'`"; \ + test "$$dir" != "$$p" || dir=.; \ + echo "rm -f \"$${dir}/so_locations\""; \ + rm -f "$${dir}/so_locations"; \ + done +libcricketsessionfileshare.la: $(libcricketsessionfileshare_la_OBJECTS) $(libcricketsessionfileshare_la_DEPENDENCIES) + $(CXXLINK) $(libcricketsessionfileshare_la_LDFLAGS) $(libcricketsessionfileshare_la_OBJECTS) $(libcricketsessionfileshare_la_LIBADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/fileshare.Plo@am__quote@ + +.cc.o: +@am__fastdepCXX_TRUE@ if $(CXXCOMPILE) -MT $@ -MD -MP -MF "$(DEPDIR)/$*.Tpo" -c -o $@ $<; \ +@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/$*.Tpo" "$(DEPDIR)/$*.Po"; else rm -f "$(DEPDIR)/$*.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(CXXCOMPILE) -c -o $@ $< + +.cc.obj: +@am__fastdepCXX_TRUE@ if $(CXXCOMPILE) -MT $@ -MD -MP -MF "$(DEPDIR)/$*.Tpo" -c -o $@ `$(CYGPATH_W) '$<'`; \ +@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/$*.Tpo" "$(DEPDIR)/$*.Po"; else rm -f "$(DEPDIR)/$*.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(CXXCOMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +.cc.lo: +@am__fastdepCXX_TRUE@ if $(LTCXXCOMPILE) -MT $@ -MD -MP -MF "$(DEPDIR)/$*.Tpo" -c -o $@ $<; \ +@am__fastdepCXX_TRUE@ then mv -f "$(DEPDIR)/$*.Tpo" "$(DEPDIR)/$*.Plo"; else rm -f "$(DEPDIR)/$*.Tpo"; exit 1; fi +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ source='$<' object='$@' libtool=yes @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCXX_FALSE@ DEPDIR=$(DEPDIR) $(CXXDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCXX_FALSE@ $(LTCXXCOMPILE) -c -o $@ $< + +mostlyclean-libtool: + -rm -f *.lo + +clean-libtool: + -rm -rf .libs _libs + +distclean-libtool: + -rm -f libtool +uninstall-info-am: + +ID: $(HEADERS) $(SOURCES) $(LISP) $(TAGS_FILES) + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) ' { files[$$0] = 1; } \ + END { for (i in files) print i; }'`; \ + mkid -fID $$unique +tags: TAGS + +TAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + tags=; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) ' { files[$$0] = 1; } \ + END { for (i in files) print i; }'`; \ + if test -z "$(ETAGS_ARGS)$$tags$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$tags $$unique; \ + fi +ctags: CTAGS +CTAGS: $(HEADERS) $(SOURCES) $(TAGS_DEPENDENCIES) \ + $(TAGS_FILES) $(LISP) + tags=; \ + here=`pwd`; \ + list='$(SOURCES) $(HEADERS) $(LISP) $(TAGS_FILES)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | \ + $(AWK) ' { files[$$0] = 1; } \ + END { for (i in files) print i; }'`; \ + test -z "$(CTAGS_ARGS)$$tags$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$tags $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && cd $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) $$here + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's|.|.|g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's|.|.|g'`; \ + list='$(DISTFILES)'; for file in $$list; do \ + case $$file in \ + $(srcdir)/*) file=`echo "$$file" | sed "s|^$$srcdirstrip/||"`;; \ + $(top_srcdir)/*) file=`echo "$$file" | sed "s|^$$topsrcdirstrip/|$(top_builddir)/|"`;; \ + esac; \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + dir=`echo "$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test "$$dir" != "$$file" && test "$$dir" != "."; then \ + dir="/$$dir"; \ + $(mkdir_p) "$(distdir)$$dir"; \ + else \ + dir=''; \ + fi; \ + if test -d $$d/$$file; then \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -pR $(srcdir)/$$file $(distdir)$$dir || exit 1; \ + fi; \ + cp -pR $$d/$$file $(distdir)$$dir || exit 1; \ + else \ + test -f $(distdir)/$$file \ + || cp -p $$d/$$file $(distdir)/$$file \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(LTLIBRARIES) $(HEADERS) +installdirs: +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + `test -z '$(STRIP)' || \ + echo "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'"` install +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-generic clean-libtool clean-noinstLTLIBRARIES \ + mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-libtool distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +info: info-am + +info-am: + +install-data-am: + +install-exec-am: + +install-info: install-info-am + +install-man: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic \ + mostlyclean-libtool + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-info-am + +.PHONY: CTAGS GTAGS all all-am check check-am clean clean-generic \ + clean-libtool clean-noinstLTLIBRARIES ctags distclean \ + distclean-compile distclean-generic distclean-libtool \ + distclean-tags distdir dvi dvi-am html html-am info info-am \ + install install-am install-data install-data-am install-exec \ + install-exec-am install-info install-info-am install-man \ + install-strip installcheck installcheck-am installdirs \ + maintainer-clean maintainer-clean-generic mostlyclean \ + mostlyclean-compile mostlyclean-generic mostlyclean-libtool \ + pdf pdf-am ps ps-am tags uninstall uninstall-am \ + uninstall-info-am + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/Plugins/jingle/libjingle/talk/session/fileshare/fileshare.cc b/Plugins/jingle/libjingle/talk/session/fileshare/fileshare.cc new file mode 100644 index 0000000..863c352 --- /dev/null +++ b/Plugins/jingle/libjingle/talk/session/fileshare/fileshare.cc @@ -0,0 +1,1313 @@ +/* + * libjingle + * Copyright 2004--2006, Google Inc. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO + * EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; + * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR + * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "talk/session/fileshare/fileshare.h" + +#include "talk/base/httpcommon-inl.h" + +#include "talk/base/fileutils.h" +#include "talk/base/streamutils.h" +#include "talk/base/event.h" +#include "talk/base/helpers.h" +#include "talk/base/httpclient.h" +#include "talk/base/httpserver.h" +#include "talk/base/pathutils.h" +#include "talk/base/socketstream.h" +#include "talk/base/stringdigest.h" +#include "talk/base/stringencode.h" +#include "talk/base/stringutils.h" +#include "talk/base/tarstream.h" +#include "talk/base/thread.h" +#include "talk/session/tunnel/pseudotcpchannel.h" +#include "talk/session/tunnel/tunnelsessionclient.h" + +/////////////////////////////////////////////////////////////////////////////// +// <description xmlns="http://www.google.com/session/share"> +// <manifest> +// <file size='341'> +// <name>foo.txt</name> +// </file> +// <file size='51321'> +// <name>foo.jpg</name> +// <image width='480' height='320'/> +// </file> +// <folder> +// <name>stuff</name> +// </folder> +// </manifest> +// <protocol> +// <http> +// <url name='source-path'>/temporary/23A53F01/</url> +// <url name='preview-path'>/temporary/90266EA1/</url> +// </http> +// <raw/> +// </protocol> +// </description> +// <p:transport xmns:p="p2p"/> +/////////////////////////////////////////////////////////////////////////////// + +/////////////////////////////////////////////////////////////////////////////// +// Constants and private functions +/////////////////////////////////////////////////////////////////////////////// + +const std::string NS_GOOGLE_SHARE("http://www.google.com/session/share"); + +namespace { + +const buzz::QName QN_SHARE_DESCRIPTION(true, NS_GOOGLE_SHARE, "description"); +const buzz::QName QN_SHARE_MANIFEST(true, NS_GOOGLE_SHARE, "manifest"); +const buzz::QName QN_SHARE_FOLDER(true, NS_GOOGLE_SHARE, "folder"); +const buzz::QName QN_SHARE_FILE(true, NS_GOOGLE_SHARE, "file"); +const buzz::QName QN_SHARE_NAME(true, NS_GOOGLE_SHARE, "name"); +const buzz::QName QN_SHARE_IMAGE(true, NS_GOOGLE_SHARE, "image"); +const buzz::QName QN_SHARE_PROTOCOL(true, NS_GOOGLE_SHARE, "protocol"); +const buzz::QName QN_SHARE_HTTP(true, NS_GOOGLE_SHARE, "http"); +const buzz::QName QN_SHARE_URL(true, NS_GOOGLE_SHARE, "url"); +const buzz::QName QN_SHARE_CHANNEL(true, NS_GOOGLE_SHARE, "channel"); +const buzz::QName QN_SHARE_COMPLETE(true, NS_GOOGLE_SHARE, "complete"); + +const buzz::QName QN_SIZE(true, buzz::STR_EMPTY, "size"); +const buzz::QName QN_WIDTH(true, buzz::STR_EMPTY, "width"); +const buzz::QName QN_HEIGHT(true, buzz::STR_EMPTY, "height"); + +const std::string kHttpSourcePath("source-path"); +const std::string kHttpPreviewPath("preview-path"); + +const size_t kMinImageSize = 16U; +const size_t kMaxImageSize = 0x8000U; // (32k) +const size_t kMaxPreviewSize = 1024; +// Wait 10 seconds to see if any new proxies get established +const uint32 kProxyWait = 10000; + +const int MSG_RETRY = 1; +const uint32 kFileTransferEnableRetryMs = 1000 * 60 * 4; // 4 minutes + +const std::string MIME_OCTET_STREAM("application/octet-stream"); + +enum { + MSG_PROXY_WAIT, +}; + +bool AllowedImageDimensions(size_t width, size_t height) { + return (width >= kMinImageSize) && (width <= kMaxImageSize) + && (height >= kMinImageSize) && (height <= kMaxImageSize); +} + +} // anon namespace + +namespace cricket { + +/////////////////////////////////////////////////////////////////////////////// +// FileShareManifest +/////////////////////////////////////////////////////////////////////////////// + +void +FileShareManifest::AddFile(const std::string& name, size_t size) { + Item i = { T_FILE, name, size }; + items_.push_back(i); +} + +void +FileShareManifest::AddImage(const std::string& name, size_t size, + size_t width, size_t height) { + Item i = { T_IMAGE, name, size, width, height }; + items_.push_back(i); +} + +void +FileShareManifest::AddFolder(const std::string& name, size_t size) { + Item i = { T_FOLDER, name, size }; + items_.push_back(i); +} + +size_t +FileShareManifest::GetItemCount(Type t) const { + size_t count = 0; + for (size_t i=0; i<items_.size(); ++i) { + if (items_[i].type == t) + ++count; + } + return count; +} + +/////////////////////////////////////////////////////////////////////////////// +// FileShareSession +/////////////////////////////////////////////////////////////////////////////// + +FileShareSession::FileShareSession(cricket::Session* session, const std::string &user_agent) + : session_(session), state_(FS_NONE), + is_closed_(false), + is_sender_(false), manifest_(NULL), pool_(this), http_client_(NULL), + http_server_(NULL), + transfer_connection_id_(talk_base::HTTP_INVALID_CONNECTION_ID), + counter_(NULL), item_transferring_(0), bytes_transferred_(0), + local_cancel_(false), local_listener_(NULL), remote_listener_(NULL), + next_channel_id_(1), user_agent_(user_agent) { + session_->SignalState.connect(this, &FileShareSession::OnSessionState); + session_->SignalInfoMessage.connect(this, + &FileShareSession::OnSessionInfoMessage); + session_->SignalChannelGone.connect(this, + &FileShareSession::OnSessionChannelGone); +} + +FileShareSession::~FileShareSession() { + ASSERT(FS_NONE != state_); + // If we haven't closed, do cleanup now. + if (!IsClosed()) { + if (!IsComplete()) { + state_ = FS_FAILURE; + } + DoClose(true); + } + if (session_) { + // Make sure we don't get future state changes on this session. + session_->SignalState.disconnect(this); + session_->SignalInfoMessage.disconnect(this); + session_ = NULL; + } + + for (TransactionList::const_iterator trans_it = transactions_.begin(); + trans_it != transactions_.end(); ++trans_it) { + (*trans_it)->response()->set_error(talk_base::HC_NOT_FOUND); + http_server_->Respond(*trans_it); + } + + delete http_client_; + delete http_server_; + delete manifest_; + delete local_listener_; + delete remote_listener_; +} + +bool +FileShareSession::IsComplete() const { + return (state_ >= FS_COMPLETE); +} + +bool +FileShareSession::IsClosed() const { + return is_closed_; +} + +FileShareState +FileShareSession::state() const { + return state_; +} + +bool +FileShareSession::is_sender() const { + ASSERT(FS_NONE != state_); + return is_sender_; +} + +const buzz::Jid& +FileShareSession::jid() const { + ASSERT(FS_NONE != state_); + return jid_; +} + +const FileShareManifest* +FileShareSession::manifest() const { + ASSERT(FS_NONE != state_); + return manifest_; +} + +const std::string& +FileShareSession::local_folder() const { + ASSERT(!local_folder_.empty()); + return local_folder_; +} + +void +FileShareSession::Share(const buzz::Jid& jid, FileShareManifest* manifest) { + ASSERT(FS_NONE == state_); + ASSERT(NULL != session_); + + http_server_ = new talk_base::HttpServer; + http_server_->SignalHttpRequest.connect(this, + &FileShareSession::OnHttpRequest); + http_server_->SignalHttpRequestComplete.connect(this, + &FileShareSession::OnHttpRequestComplete); + http_server_->SignalConnectionClosed.connect(this, + &FileShareSession::OnHttpConnectionClosed); + + FileShareDescription* desc = new FileShareDescription; + desc->supports_http = true; + desc->manifest = *manifest; + GenerateTemporaryPrefix(&desc->source_path); + GenerateTemporaryPrefix(&desc->preview_path); + session_->Initiate(jid.Str(), NULL, desc); + + delete manifest; +} + +void +FileShareSession::Accept() { + ASSERT(FS_OFFER == state_); + ASSERT(NULL != session_); + ASSERT(NULL != manifest_); + + ASSERT(!http_client_); + ASSERT(item_transferring_ == 0); + http_client_ = new talk_base::HttpClient(user_agent_, + &pool_); + http_client_->SignalHttpClientComplete.connect(this, + &FileShareSession::OnHttpClientComplete); + http_client_->SignalHttpClientClosed.connect(this, + &FileShareSession::OnHttpClientClosed); + + // The receiver now has a need for the http_server_, when previewing already + // downloaded content. + http_server_ = new talk_base::HttpServer; + http_server_->SignalHttpRequest.connect(this, + &FileShareSession::OnHttpRequest); + http_server_->SignalHttpRequestComplete.connect(this, + &FileShareSession::OnHttpRequestComplete); + http_server_->SignalConnectionClosed.connect(this, + &FileShareSession::OnHttpConnectionClosed); + + FileShareDescription* desc = new FileShareDescription; + desc->supports_http = description()->supports_http; + session_->Accept(desc); + + SetState(FS_TRANSFER, false); + NextDownload(); +} + +void +FileShareSession::Decline() { + ASSERT(FS_OFFER == state_); + ASSERT(NULL != session_); + local_cancel_ = true; + session_->Reject(); +} + +void +FileShareSession::Cancel() { + ASSERT(!IsComplete()); + ASSERT(NULL != session_); + local_cancel_ = true; + session_->Terminate(); +} + +bool +FileShareSession::GetItemUrl(size_t index, std::string* url) { + return GetItemBaseUrl(index, false, url); +} + +bool FileShareSession::GetImagePreviewUrl(size_t index, size_t width, + size_t height, std::string* url) { + if (!GetItemBaseUrl(index, true, url)) + return false; + + if (FileShareManifest::T_IMAGE != manifest_->item(index).type) { + ASSERT(false); + return false; + } + + char query[256]; + talk_base::sprintfn(query, ARRAY_SIZE(query), "?width=%u&height=%u", + width, height); + url->append(query); + return true; +} + +void FileShareSession::ResampleComplete(talk_base::StreamInterface *i, talk_base::HttpTransaction *trans, bool success) { + bool found = false; + for (TransactionList::const_iterator trans_it = transactions_.begin(); + trans_it != transactions_.end(); ++trans_it) { + if (*trans_it == trans) { + found = true; + break; + } + } + + if (!found) + return; + + transactions_.remove(trans); + + if (success) { + trans->response()->set_success(MIME_OCTET_STREAM, i); + http_server_->Respond(trans); + + } + trans->response()->set_error(talk_base::HC_NOT_FOUND); + http_server_->Respond(trans); +} + +bool FileShareSession::GetProgress(size_t& bytes) const { + bool known = true; + bytes = bytes_transferred_; + if (counter_) { + size_t current_size = manifest_->item(item_transferring_).size; + size_t current_pos = counter_->GetByteCount(); + if (current_size == FileShareManifest::SIZE_UNKNOWN) { + known = false; + } else if (current_pos > current_size) { + // Don't allow the size of a 'known' item to be reported as larger than + // it claimed to be. + ASSERT(false); + current_pos = current_size; + } + bytes += current_pos; + } + return known; +} + +bool FileShareSession::GetTotalSize(size_t& bytes) const { + bool known = true; + bytes = 0; + for (size_t i=0; i<manifest_->size(); ++i) { + if (manifest_->item(i).size == FileShareManifest::SIZE_UNKNOWN) { + // We make files of unknown length worth a single byte. + known = false; + bytes += 1; + } else { + bytes += manifest_->item(i).size; + } + } + return known; +} + +bool FileShareSession::GetCurrentItemName(std::string* name) { + if (FS_TRANSFER != state_) { + name->clear(); + return false; + } + ASSERT(item_transferring_ < manifest_->size()); + if (transfer_name_.empty()) { + const FileShareManifest::Item& item = manifest_->item(item_transferring_); + *name = item.name; + } else { + *name = transfer_name_; + } + return !name->empty(); +} + +// StreamPool Implementation + +talk_base::StreamInterface* FileShareSession::RequestConnectedStream( + const talk_base::SocketAddress& remote, int* err) { + ASSERT(remote.IPAsString() == jid_.Str()); + ASSERT(!IsClosed()); + ASSERT(NULL != session_); + if (!session_) { + if (err) + *err = -1; + return NULL; + } + + char channel_name[64]; + talk_base::sprintfn(channel_name, ARRAY_SIZE(channel_name), + "private-%u", next_channel_id_++); + if (err) + *err = 0; + return CreateChannel(channel_name); +} + +void FileShareSession::ReturnConnectedStream( + talk_base::StreamInterface* stream) { + talk_base::Thread::Current()->Dispose(stream); +} + +// MessageHandler Implementation + +void FileShareSession::OnMessage(talk_base::Message* msg) { + if (MSG_PROXY_WAIT == msg->message_id) { + LOG_F(LS_INFO) << "MSG_PROXY_WAIT"; + if (proxies_.empty() && IsComplete() && !IsClosed()) { + DoClose(true); + } + } +} + +// Session Signals + +void FileShareSession::OnSessionState(cricket::Session* session, + cricket::Session::State state) { + // Once we are complete, state changes are meaningless. + if (!IsComplete()) { + switch (state) { + case cricket::Session::STATE_SENTINITIATE: + case cricket::Session::STATE_RECEIVEDINITIATE: + OnInitiate(); + break; + case cricket::Session::STATE_SENTACCEPT: + case cricket::Session::STATE_RECEIVEDACCEPT: + case cricket::Session::STATE_INPROGRESS: + SetState(FS_TRANSFER, false); + break; + case cricket::Session::STATE_SENTREJECT: + case cricket::Session::STATE_SENTTERMINATE: + case cricket::Session::STATE_DEINIT: + if (local_cancel_) { + SetState(FS_LOCAL_CANCEL, false); + } else { + SetState(FS_REMOTE_CANCEL, false); + } + break; + case cricket::Session::STATE_RECEIVEDTERMINATE: + if (is_sender()) { + // If we are the sender, and the receiver downloaded the correct number + // of bytes, then we assume the transfer was successful. We've + // introduced support for explicit completion notification + // (QN_SHARE_COMPLETE), but it's not mandatory at this point, so we need + // this as a fallback. + size_t total_bytes; + GetTotalSize(total_bytes); + if (bytes_transferred_ >= total_bytes) { + SetState(FS_COMPLETE, false); + break; + } + } + // Fall through + case cricket::Session::STATE_RECEIVEDREJECT: + SetState(FS_REMOTE_CANCEL, false); + break; + case cricket::Session::STATE_INIT: + case cricket::Session::STATE_SENTMODIFY: + case cricket::Session::STATE_RECEIVEDMODIFY: + case cricket::Session::STATE_SENTREDIRECT: + default: + // These states should not occur. + ASSERT(false); + break; + } + } + + if (state == cricket::Session::STATE_DEINIT) { + if (!IsClosed()) { + DoClose(false); + } + session_ = NULL; + } +} + +void FileShareSession::OnSessionInfoMessage(cricket::Session* session, + const cricket::Session::XmlElements& els) { + if (IsClosed()) + return; + ASSERT(NULL != session_); + for (size_t i=0; i<els.size(); ++i) { + if (is_sender() && (els[i]->Name() == QN_SHARE_CHANNEL)) { + if (els[i]->HasAttr(buzz::QN_NAME)) { + cricket::PseudoTcpChannel* channel = + new cricket::PseudoTcpChannel(talk_base::Thread::Current(), session_); + VERIFY(channel->Connect(els[i]->Attr(buzz::QN_NAME))); + talk_base::StreamInterface* stream = channel->GetStream(); + http_server_->HandleConnection(stream); + } + } else if (is_sender() && (els[i]->Name() == QN_SHARE_COMPLETE)) { + // Normal file transfer has completed, but receiver may still be getting + // previews. + if (!IsComplete()) { + SetState(FS_COMPLETE, true); + } + } else { + LOG(LS_WARNING) << "Unknown FileShareSession info message: " + << els[i]->Name().Merged(); + } + } +} + +void FileShareSession::OnSessionChannelGone(cricket::Session* session, + const std::string& name) { + LOG_F(LS_WARNING) << "(" << name << ")"; + ASSERT(session == session_); + if (cricket::TransportChannel* channel = session->GetChannel(name)) { + session->DestroyChannel(channel); + } +} + +// HttpClient Signals + +void FileShareSession::OnHttpClientComplete(talk_base::HttpClient* http, + int err) { + LOG_F(LS_INFO) << "(" << err << ", " << http->response().scode << ")"; + ASSERT(http == http_client_); + ASSERT(NULL != session_); + + transfer_name_.clear(); + counter_ = NULL; // counter_ is deleted by HttpClient + http->response().document.reset(); + bool success = (err == 0) && (http->response().scode == talk_base::HC_OK); + + const FileShareManifest::Item& item = manifest_->item(item_transferring_); + talk_base::Pathname local_name; + local_name.SetFilename(item.name); + local_name.SetFolder(local_folder_); + + if (local_name.pathname() != transfer_path_) { + const bool is_folder = (item.type == FileShareManifest::T_FOLDER); + if (success && !talk_base::CreateUniqueFile(local_name, false)) { + LOG(LS_ERROR) << "Couldn't rename downloaded file: " + << local_name.pathname(); + success = false; + } + + talk_base::Pathname temp_name(transfer_path_); + if (is_folder) { + // The folder we want is a subdirectory of the transfer_path_. + temp_name.AppendFolder(item.name); + } + + if (!talk_base::Filesystem::MoveFile(temp_name.pathname(), local_name.pathname())) { + success = false; + LOG(LS_ERROR) << "Couldn't move downloaded file from '" + << temp_name.pathname() << "' to '" + << local_name.pathname(); + } + + if (success && is_folder) { + talk_base::Filesystem::DeleteFile(transfer_path_); + } + } + + if (!success) { + if (!talk_base::Filesystem::DeleteFile(transfer_path_)) { + LOG(LS_ERROR) << "Couldn't delete downloaded file: " << transfer_path_; + } + if (!IsComplete()) { + SetState(FS_FAILURE, false); + } + return; + } + + // We may have skipped over some items (if they are directories, or otherwise + // failed. resize ensures that we populate the skipped entries with empty + // strings. + stored_location_.resize(item_transferring_ + 1); + stored_location_[item_transferring_] = local_name.pathname(); + + // bytes_transferred_ represents the size of items which have completely + // transferred, and is added to the progress of the currently transferring + // items. + if (item.size == FileShareManifest::SIZE_UNKNOWN) { + bytes_transferred_ += 1; + } else { + bytes_transferred_ += item.size; + } + item_transferring_ += 1; + NextDownload(); +} + +void FileShareSession::OnHttpClientClosed(talk_base::HttpClient* http, + int err) { + LOG_F(LS_INFO) << "(" << err << ")"; +} + +// HttpServer Signals + +void FileShareSession::OnHttpRequest(talk_base::HttpServer* server, + talk_base::HttpTransaction* transaction) { + LOG_F(LS_INFO) << "(" << transaction->request()->path << ")"; + ASSERT(server == http_server_); + + std::string path, query; + size_t query_start = transaction->request()->path.find('?'); + if (query_start != std::string::npos) { + path = transaction->request()->path.substr(0, query_start); + query = transaction->request()->path.substr(query_start + 1); + } else { + path = transaction->request()->path; + } + + talk_base::Pathname remote_name(path); + bool preview = (preview_path_ == remote_name.folder()); + bool original = (source_path_ == remote_name.folder()); + + std::string requested_file(remote_name.filename()); + talk_base::transform(requested_file, requested_file.size(), requested_file, + talk_base::url_decode); + + size_t item_index; + const FileShareManifest::Item* item = NULL; + if (preview || original) { + for (size_t i=0; i<manifest_->size(); ++i) { + LOG(LS_INFO) << "++++ " << manifest_->item(i).name + " " << requested_file; + if (manifest_->item(i).name == requested_file) { + item_index = i; + item = &manifest_->item(item_index); + break; + } + } + } + + talk_base::StreamInterface* stream = NULL; + std::string mime_type(MIME_OCTET_STREAM); + + if (!item) { + // Fall through + } else if (preview) { + // Only image previews allowed + unsigned int width = 0, height = 0; + if ((item->type == FileShareManifest::T_IMAGE) + && !query.empty() + && (sscanf(query.c_str(), "width=%u&height=%u", + &width, &height) == 2)) { + width = talk_base::_max<unsigned int>(1, talk_base::_min(width, kMaxPreviewSize)); + height = talk_base::_max<unsigned int>(1, talk_base::_min(height, kMaxPreviewSize)); + std::string pathname; + if (is_sender_) { + talk_base::Pathname local_path; + local_path.SetFolder(local_folder_); + local_path.SetFilename(item->name); + pathname = local_path.pathname(); + } else if ((item_index < stored_location_.size()) + && !stored_location_[item_index].empty()) { + pathname = stored_location_[item_index]; + } + if (!pathname.empty()) { + transactions_.push_back(transaction); + SignalResampleImage(pathname, width, height, transaction); + } + } + } else if (item->type == FileShareManifest::T_FOLDER) { + talk_base::Pathname local_path; + local_path.SetFolder(local_folder_); + local_path.AppendFolder(item->name); + talk_base::TarStream* tar = new talk_base::TarStream; + VERIFY(tar->AddFilter(local_path.folder_name())); + if (tar->Open(local_path.parent_folder(), true)) { + stream = tar; + tar->SignalNextEntry.connect(this, &FileShareSession::OnNextEntry); + mime_type = "application/x-tar"; + } else { + delete tar; + } + } else if ((item->type == FileShareManifest::T_FILE) + || (item->type == FileShareManifest::T_IMAGE)) { + talk_base::Pathname local_path; + local_path.SetFolder(local_folder_); + local_path.SetFilename(item->name); + talk_base::FileStream* file = new talk_base::FileStream; + LOG(LS_INFO) << "opening file " << local_path.pathname(); + if (file->Open(local_path.pathname().c_str(), "rb")) { + LOG(LS_INFO) << "File opened"; + stream = file; + } else { + delete file; + } + } + + if (!stream) { + transaction->response()->set_error(talk_base::HC_NOT_FOUND); + } else if (original) { + // We should never have more than one original request pending at a time + ASSERT(NULL == counter_); + StreamCounter* counter = new StreamCounter(stream); + counter->SignalUpdateByteCount.connect(this, &FileShareSession::OnUpdateBytes); + transaction->response()->set_success(mime_type.c_str(), counter); + transfer_connection_id_ = transaction->connection_id(); + item_transferring_ = item_index; + counter_ = counter; + } else { + // Note: in the preview case, we don't set counter_, so the transferred + // bytes won't be shown as progress, and won't trigger a state change. + transaction->response()->set_success(mime_type.c_str(), stream); + } + + LOG_F(LS_INFO) << "Result: " << transaction->response()->scode; + http_server_->Respond(transaction); +} + +void FileShareSession::OnHttpRequestComplete(talk_base::HttpServer* server, + talk_base::HttpTransaction* transaction, int err) { + LOG_F(LS_INFO) << "(" << transaction->request()->path << ", " << err << ")"; + ASSERT(server == http_server_); + + // We only care about transferred originals + if (transfer_connection_id_ != transaction->connection_id()) + return; + + ASSERT(item_transferring_ < manifest_->size()); + ASSERT(NULL != counter_); + + transfer_connection_id_ = talk_base::HTTP_INVALID_CONNECTION_ID; + transfer_name_.clear(); + counter_ = NULL; + + if (err == 0) { + const FileShareManifest::Item& item = manifest_->item(item_transferring_); + if (item.size == FileShareManifest::SIZE_UNKNOWN) { + bytes_transferred_ += 1; + } else { + bytes_transferred_ += item.size; + } + } +} + +void FileShareSession::OnHttpConnectionClosed(talk_base::HttpServer* server, + int err, talk_base::StreamInterface* stream) { + LOG_F(LS_INFO) << "(" << err << ")"; + talk_base::Thread::Current()->Dispose(stream); +} + +// TarStream Signals + +void FileShareSession::OnNextEntry(const std::string& name, size_t size) { + LOG_F(LS_VERBOSE) << "(" << name << ", " << size << ")"; + transfer_name_ = name; + SignalNextFile(this); +} + +// Socket Signals + +void FileShareSession::OnProxyAccept(talk_base::AsyncSocket* socket) { + bool is_remote; + if (socket == remote_listener_) { + is_remote = true; + ASSERT(NULL != session_); + } else if (socket == local_listener_) { + is_remote = false; + } else { + ASSERT(false); + return; + } + + while (talk_base::AsyncSocket* accepted = + static_cast<talk_base::AsyncSocket*>(socket->Accept(NULL))) { + + // Check if connection is from localhost. + if (accepted->GetRemoteAddress().ip() != 0x7F000001) { + delete accepted; + continue; + } + + LOG_F(LS_VERBOSE) << (is_remote ? "[remote]" : "[local]"); + + if (is_remote) { + char channel_name[64]; + talk_base::sprintfn(channel_name, ARRAY_SIZE(channel_name), + "proxy-%u", next_channel_id_++); + talk_base::StreamInterface* remote = + (NULL != session_) ? CreateChannel(channel_name) : NULL; + if (!remote) { + LOG_F(LS_WARNING) << "CreateChannel(" << channel_name << ") failed"; + delete accepted; + continue; + } + + talk_base::StreamInterface* local = new talk_base::SocketStream(accepted); + StreamRelay* proxy = new StreamRelay(local, remote, 64 * 1024); + proxy->SignalClosed.connect(this, &FileShareSession::OnProxyClosed); + proxies_.push_back(proxy); + proxy->Circulate(); + talk_base::Thread::Current()->Clear(this, MSG_PROXY_WAIT); + } else { + talk_base::StreamInterface* local = new talk_base::SocketStream(accepted); + http_server_->HandleConnection(local); + } + } +} + +void FileShareSession::OnProxyClosed(StreamRelay* proxy, int error) { + ProxyList::iterator it = std::find(proxies_.begin(), proxies_.end(), proxy); + if (it == proxies_.end()) { + ASSERT(false); + return; + } + + LOG_F(LS_VERBOSE) << "(" << error << ")"; + + proxies_.erase(it); + talk_base::Thread::Current()->Dispose(proxy); + + if (proxies_.empty() && IsComplete() && !IsClosed()) { + talk_base::Thread::Current()->PostDelayed(kProxyWait, this, MSG_PROXY_WAIT); + } +} + + +void FileShareSession::OnUpdateBytes(size_t count) { + SignalUpdateProgress(this); +} + +// Internal Helpers + +void FileShareSession::GenerateTemporaryPrefix(std::string* prefix) { + std::string data = cricket::CreateRandomString(32); + ASSERT(NULL != prefix); + prefix->assign("/temporary/"); + prefix->append(talk_base::MD5(data)); + prefix->append("/"); +} + +void FileShareSession::GetItemNetworkPath(size_t index, bool preview, + std::string* path) { + ASSERT(index < manifest_->size()); + ASSERT(NULL != path); + + // preview_path_ and source_path_ are url path segments, which are composed + // with the address of the localhost p2p proxy to provide a url which IE can + // use. + + std::string ue_name; + const std::string& name = manifest_->item(index).name; + talk_base::transform(ue_name, name.length() * 3, name, talk_base::url_encode); + + talk_base::Pathname pathname; + pathname.SetFolder(preview ? preview_path_ : source_path_); + pathname.SetFilename(ue_name); + *path = pathname.pathname(); +} + +bool FileShareSession::GetItemBaseUrl(size_t index, bool preview, + std::string* url) { + // This function composes a URL to the referenced item. It may be a local + // file url (file:///...), or a remote peer url relayed through localhost + // (http://...) + + ASSERT(NULL != url); + if (index >= manifest_->size()) { + ASSERT(false); + return false; + } + + const FileShareManifest::Item& item = manifest_->item(index); + + bool is_remote; + if (is_sender_) { + if (!preview) { + talk_base::Pathname path(local_folder_); + path.SetFilename(item.name); + *url = path.url(); + return true; + } + is_remote = false; + } else { + if ((index < stored_location_.size()) && !stored_location_[index].empty()) { + if (!preview) { + *url = talk_base::Pathname(stored_location_[index]).url(); + return true; + } + // Note: Using the local downloaded files as a source for previews is + // desireable, because it means that previews can be regenerated if IE's + // cached versions get flushed for some reason, and the remote side is + // not available. However, it has the downside that IE _must_ regenerate + // the preview locally, which takes time, memory and CPU. Eventually, + // we will unify the remote and local cached copy through some sort of + // smart http proxying. In the meantime, always use the remote url, to + // eliminate the annoying transition from remote to local caching. + //is_remote = false; + is_remote = true; + } else { + is_remote = true; + } + } + + talk_base::SocketAddress address; + if (!GetProxyAddress(address, is_remote)) + return false; + + std::string path; + GetItemNetworkPath(index, preview, &path); + talk_base::Url<char> make_url(path.c_str(), + address.IPAsString().c_str(), + address.port()); + *url = make_url.url(); + return true; +} + +bool FileShareSession::GetProxyAddress(talk_base::SocketAddress& address, + bool is_remote) { + talk_base::AsyncSocket*& proxy_listener = + is_remote ? remote_listener_ : local_listener_; + + if (!proxy_listener) { + talk_base::AsyncSocket* listener = + talk_base::Thread::Current()->socketserver() + ->CreateAsyncSocket(SOCK_STREAM); + if (!listener) + return false; + + talk_base::SocketAddress bind_address("127.0.0.1", 0); + + if ((listener->Bind(bind_address) != 0) + || (listener->Listen(5) != 0)) { + delete listener; + return false; + } + + LOG(LS_INFO) << "Proxy listener available @ " + << listener->GetLocalAddress().ToString(); + + listener->SignalReadEvent.connect(this, &FileShareSession::OnProxyAccept); + proxy_listener = listener; + } + + if (proxy_listener->GetState() == talk_base::Socket::CS_CLOSED) { + if (is_remote) { + address = remote_listener_address_; + return true; + } + return false; + } + + address = proxy_listener->GetLocalAddress(); + return !address.IsAny(); +} + +talk_base::StreamInterface* FileShareSession::CreateChannel( + const std::string& channel_name) { + ASSERT(NULL != session_); + + // Send a heads-up for our new channel + cricket::Session::XmlElements els; + buzz::XmlElement* xel_channel = new buzz::XmlElement(QN_SHARE_CHANNEL, true); + xel_channel->AddAttr(buzz::QN_NAME, channel_name); + els.push_back(xel_channel); + session_->SendInfoMessage(els); + + cricket::PseudoTcpChannel* channel = + new cricket::PseudoTcpChannel(talk_base::Thread::Current(), session_); + VERIFY(channel->Connect(channel_name)); + return channel->GetStream(); +} + +void FileShareSession::SetState(FileShareState state, bool prevent_close) { + if (state == state_) + return; + + if (IsComplete()) { + // Entering a completion state is permanent. + ASSERT(false); + return; + } + + state_ = state; + if (IsComplete()) { + // All completion states auto-close except for FS_COMPLETE + bool close = (state_ > FS_COMPLETE) || !prevent_close; + if (close) { + DoClose(true); + } + } + + SignalState(state_); +} + +void FileShareSession::OnInitiate() { + // Cache the variables we will need, in case session_ goes away + is_sender_ = session_->initiator(); + jid_ = buzz::Jid(session_->remote_name()); + manifest_ = new FileShareManifest(description()->manifest); + source_path_ = description()->source_path; + preview_path_ = description()->preview_path; + + if (local_folder_.empty()) { + LOG(LS_ERROR) << "FileShareSession - no local folder, using temp"; + talk_base::Pathname temp_folder; + talk_base::Filesystem::GetTemporaryFolder(temp_folder, true, NULL); + local_folder_ = temp_folder.pathname(); + } + LOG(LS_INFO) << session_->state(); + SetState(FS_OFFER, false); +} + +void FileShareSession::NextDownload() { + if (FS_TRANSFER != state_) + return; + + if (item_transferring_ >= manifest_->size()) { + // Notify the other side that transfer has completed + cricket::Session::XmlElements els; + els.push_back(new buzz::XmlElement(QN_SHARE_COMPLETE, true)); + session_->SendInfoMessage(els); + SetState(FS_COMPLETE, !proxies_.empty()); + return; + } + + const FileShareManifest::Item& item = manifest_->item(item_transferring_); + if ((item.type != FileShareManifest::T_FILE) + && (item.type != FileShareManifest::T_IMAGE) + && (item.type != FileShareManifest::T_FOLDER)) { + item_transferring_ += 1; + NextDownload(); + return; + } + + const bool is_folder = (item.type == FileShareManifest::T_FOLDER); + talk_base::Pathname temp_name; + temp_name.SetFilename(item.name); + if (!talk_base::CreateUniqueFile(temp_name, !is_folder)) { + SetState(FS_FAILURE, false); + return; + } + + talk_base::StreamInterface* stream = NULL; + if (is_folder) { + // Convert unique filename into unique foldername + temp_name.AppendFolder(temp_name.filename()); + temp_name.SetFilename(""); + talk_base::TarStream* tar = new talk_base::TarStream; + // Note: the 'target' directory will be a subdirectory of the transfer_path_ + talk_base::Pathname target; + target.SetFolder(item.name); + tar->AddFilter(target.pathname()); + if (!tar->Open(temp_name.pathname(), false)) { + delete tar; + SetState(FS_FAILURE, false); + return; + } + stream = tar; + tar->SignalNextEntry.connect(this, &FileShareSession::OnNextEntry); + } else { + talk_base::FileStream* file = new talk_base::FileStream; + if (!file->Open(temp_name.pathname().c_str(), "wb")) { + delete file; + talk_base::Filesystem::DeleteFile(temp_name); + SetState(FS_FAILURE, false); + return; + } + stream = file; + } + + ASSERT(NULL != stream); + transfer_path_ = temp_name.pathname(); + + std::string remote_path; + GetItemNetworkPath(item_transferring_, false, &remote_path); + + StreamCounter* counter = new StreamCounter(stream); + counter->SignalUpdateByteCount.connect(this, &FileShareSession::OnUpdateBytes); + counter_ = counter; + + http_client_->reset(); + http_client_->set_server(talk_base::SocketAddress(jid_.Str(), 0, false)); + http_client_->request().verb = talk_base::HV_GET; + http_client_->request().path = remote_path; + http_client_->response().document.reset(counter); + http_client_->start(); +} + + +const FileShareSession::FileShareDescription* FileShareSession::description() +const { + ASSERT(NULL != session_); + const cricket::SessionDescription* desc = + session_->initiator() ? session_->description() + : session_->remote_description(); + return static_cast<const FileShareDescription*>(desc); +} + +void FileShareSession::DoClose(bool terminate) { + ASSERT(!is_closed_); + ASSERT(IsComplete()); + ASSERT(NULL != session_); + + is_closed_ = true; + + if (http_client_) { + http_client_->reset(); + } + if (http_server_) { + http_server_->CloseAll(true); + // Currently, CloseAll doesn't result in OnHttpRequestComplete callback. + // If we change that, the following resetting won't be necessary. + transfer_connection_id_ = talk_base::HTTP_INVALID_CONNECTION_ID; + transfer_name_.clear(); + counter_ = NULL; + } + // 'reset' and 'CloseAll' cause counter_ to clear. + ASSERT(NULL == counter_); + + if (remote_listener_) { + // Cache the address for the remote_listener_, so that we can continue to + // present a consistent URL for remote previews, which is necessary for IE + // to continue using its cached copy. + remote_listener_address_ = remote_listener_->GetLocalAddress(); + remote_listener_->Close(); + LOG(LS_INFO) << "Proxy listener closed @ " + << remote_listener_address_.ToString(); + } + + if (terminate) { + session_->Terminate(); + } +} + +////////////////////////////// +/// FileShareSessionClient // +//////////////////////////// + +void FileShareSessionClient::OnSessionCreate(cricket::Session* session, + bool received_initiate) { + VERIFY(sessions_.insert(session).second); + if (received_initiate) { + FileShareSession* share = new FileShareSession(session, user_agent_); + SignalFileShareSessionCreate(share); + UNUSED(share); // FileShareSession registers itself with the UI + } +} + +void FileShareSessionClient::OnSessionDestroy(cricket::Session* session) { + VERIFY(1 == sessions_.erase(session)); +} + +const cricket::SessionDescription* FileShareSessionClient::CreateSessionDescription( + const buzz::XmlElement* element) { + FileShareSession::FileShareDescription* share_desc = + new FileShareSession::FileShareDescription; + + if (element->Name() != QN_SHARE_DESCRIPTION) + return share_desc; + + const buzz::XmlElement* manifest = element->FirstNamed(QN_SHARE_MANIFEST); + const buzz::XmlElement* protocol = element->FirstNamed(QN_SHARE_PROTOCOL); + + if (!manifest || !protocol) + return share_desc; + + for (const buzz::XmlElement* item = manifest->FirstElement(); + item != NULL; item = item->NextElement()) { + bool is_folder; + if (item->Name() == QN_SHARE_FOLDER) { + is_folder = true; + } else if (item->Name() == QN_SHARE_FILE) { + is_folder = false; + } else { + continue; + } + std::string name; + if (const buzz::XmlElement* el_name = item->FirstNamed(QN_SHARE_NAME)) { + name = el_name->BodyText(); + } + if (name.empty()) { + continue; + } + size_t size = FileShareManifest::SIZE_UNKNOWN; + if (item->HasAttr(QN_SIZE)) { + size = strtoul(item->Attr(QN_SIZE).c_str(), NULL, 10); + } + if (is_folder) { + share_desc->manifest.AddFolder(name, size); + } else { + // Check if there is a valid image description for this file. + if (const buzz::XmlElement* image = item->FirstNamed(QN_SHARE_IMAGE)) { + if (image->HasAttr(QN_WIDTH) && image->HasAttr(QN_HEIGHT)) { + size_t width = strtoul(image->Attr(QN_WIDTH).c_str(), NULL, 10); + size_t height = strtoul(image->Attr(QN_HEIGHT).c_str(), NULL, 10); + if (AllowedImageDimensions(width, height)) { + share_desc->manifest.AddImage(name, size, width, height); + continue; + } + } + } + share_desc->manifest.AddFile(name, size); + } + } + + if (const buzz::XmlElement* http = protocol->FirstNamed(QN_SHARE_HTTP)) { + share_desc->supports_http = true; + for (const buzz::XmlElement* url = http->FirstNamed(QN_SHARE_URL); + url != NULL; url = url->NextNamed(QN_SHARE_URL)) { + if (url->Attr(buzz::QN_NAME) == kHttpSourcePath) { + share_desc->source_path = url->BodyText(); + } else if (url->Attr(buzz::QN_NAME) == kHttpPreviewPath) { + share_desc->preview_path = url->BodyText(); + } + } + } + + return share_desc; +} + +buzz::XmlElement* FileShareSessionClient::TranslateSessionDescription( + const cricket::SessionDescription* description) { + + const FileShareSession::FileShareDescription* share_desc = + static_cast<const FileShareSession::FileShareDescription*>(description); + + scoped_ptr<buzz::XmlElement> el(new buzz::XmlElement(QN_SHARE_DESCRIPTION, + true)); + + const FileShareManifest& manifest = share_desc->manifest; + el->AddElement(new buzz::XmlElement(QN_SHARE_MANIFEST)); + for (size_t i=0; i<manifest.size(); ++i) { + const FileShareManifest::Item& item = manifest.item(i); + buzz::QName qname; + if (item.type == FileShareManifest::T_FOLDER) { + qname = QN_SHARE_FOLDER; + } else if ((item.type == FileShareManifest::T_FILE) + || (item.type == FileShareManifest::T_IMAGE)) { + qname = QN_SHARE_FILE; + } else { + ASSERT(false); + continue; + } + el->AddElement(new buzz::XmlElement(qname), 1); + if (item.size != FileShareManifest::SIZE_UNKNOWN) { + char buffer[256]; + talk_base::sprintfn(buffer, sizeof(buffer), "%lu", item.size); + el->AddAttr(QN_SIZE, buffer, 2); + } + buzz::XmlElement* el_name = new buzz::XmlElement(QN_SHARE_NAME); + el_name->SetBodyText(item.name); + el->AddElement(el_name, 2); + if ((item.type == FileShareManifest::T_IMAGE) + && AllowedImageDimensions(item.width, item.height)) { + el->AddElement(new buzz::XmlElement(QN_SHARE_IMAGE), 2); + char buffer[256]; + talk_base::sprintfn(buffer, sizeof(buffer), "%lu", item.width); + el->AddAttr(QN_WIDTH, buffer, 3); + talk_base::sprintfn(buffer, sizeof(buffer), "%lu", item.height); + el->AddAttr(QN_HEIGHT, buffer, 3); + } + } + + el->AddElement(new buzz::XmlElement(QN_SHARE_PROTOCOL)); + if (share_desc->supports_http) { + el->AddElement(new buzz::XmlElement(QN_SHARE_HTTP), 1); + if (!share_desc->source_path.empty()) { + buzz::XmlElement* url = new buzz::XmlElement(QN_SHARE_URL); + url->SetAttr(buzz::QN_NAME, kHttpSourcePath); + url->SetBodyText(share_desc->source_path); + el->AddElement(url, 2); + } + if (!share_desc->preview_path.empty()) { + buzz::XmlElement* url = new buzz::XmlElement(QN_SHARE_URL); + url->SetAttr(buzz::QN_NAME, kHttpPreviewPath); + url->SetBodyText(share_desc->preview_path); + el->AddElement(url, 2); + } + } + + return el.release(); +} + +FileShareSession *FileShareSessionClient::CreateFileShareSession() { + cricket::Session* session = sm_->CreateSession(jid_.Str(), + NS_GOOGLE_SHARE); + FileShareSession* share = new FileShareSession(session, user_agent_); + SignalFileShareSessionCreate(share); + return share; +} + + +} // namespace cricket diff --git a/Plugins/jingle/libjingle/talk/session/fileshare/fileshare.h b/Plugins/jingle/libjingle/talk/session/fileshare/fileshare.h new file mode 100644 index 0000000..b0802b4 --- /dev/null +++ b/Plugins/jingle/libjingle/talk/session/fileshare/fileshare.h @@ -0,0 +1,250 @@ +#ifndef TALK_APP_WIN32_FILESHARE_H__ +#define TALK_APP_WIN32_FILESHARE_H__ +#include "talk/base/messagequeue.h" +#include "talk/base/socketpool.h" +#include "talk/base/stringutils.h" +#include "talk/base/sigslot.h" +#include "talk/p2p/base/session.h" +#include "talk/p2p/base/sessiondescription.h" +#include "talk/xmpp/jid.h" + +class StreamCounter; +class StreamRelay; + +namespace talk_base { + class HttpClient; + class HttpServer; + class HttpTransaction; +} + +extern const std::string NS_GOOGLE_SHARE; + + +namespace cricket { + +/////////////////////////////////////////////////////////////////////////////// +// FileShareManifest +/////////////////////////////////////////////////////////////////////////////// + +class FileShareManifest { +public: + enum Type { T_FILE, T_IMAGE, T_FOLDER }; + enum { SIZE_UNKNOWN = talk_base::SIZE_UNKNOWN }; + + struct Item { + Type type; + std::string name; + size_t size, width, height; + }; + typedef std::vector<Item> ItemList; + + inline bool empty() const { return items_.empty(); } + inline size_t size() const { return items_.size(); } + inline const Item& item(size_t index) const { return items_[index]; } + + void AddFile(const std::string& name, size_t size); + void AddImage(const std::string& name, size_t size, + size_t width, size_t height); + void AddFolder(const std::string& name, size_t size); + + size_t GetItemCount(Type t) const; + inline size_t GetFileCount() const { return GetItemCount(T_FILE); } + inline size_t GetImageCount() const { return GetItemCount(T_IMAGE); } + inline size_t GetFolderCount() const { return GetItemCount(T_FOLDER); } + +private: + ItemList items_; +}; + + +enum FileShareState { + FS_NONE, // Initialization + FS_OFFER, // Offer extended + FS_TRANSFER, // In progress + FS_COMPLETE, // Completed successfully + FS_LOCAL_CANCEL, // Local side cancelled + FS_REMOTE_CANCEL, // Remote side cancelled + FS_FAILURE // An error occurred during transfer +}; + + +class FileShareSession + : public talk_base::StreamPool, + public talk_base::MessageHandler, + public sigslot::has_slots<> { +public: + struct FileShareDescription : public cricket::SessionDescription { + FileShareManifest manifest; + bool supports_http; + std::string source_path; + std::string preview_path; + FileShareDescription() : supports_http(false) { } + }; + + FileShareSession(cricket::Session* session, const std::string &user_agent); + virtual ~FileShareSession(); + + bool IsComplete() const; + bool IsClosed() const; + FileShareState state() const; + sigslot::signal1<FileShareState> SignalState; + sigslot::signal1<FileShareSession*> SignalNextFile; + sigslot::signal1<FileShareSession*> SignalUpdateProgress; + sigslot::signal4<std::string, int, int, talk_base::HttpTransaction*> SignalResampleImage; + + void ResampleComplete(talk_base::StreamInterface *si, talk_base::HttpTransaction *trans, bool success); + + bool is_sender() const; + const buzz::Jid& jid() const; + const FileShareManifest* manifest() const; + const std::string& local_folder() const; + + void SetLocalFolder(const std::string& folder) { local_folder_ = folder; } + void Share(const buzz::Jid& jid, FileShareManifest* manifest); + + void Accept(); + void Decline(); + void Cancel(); + + bool GetItemUrl(size_t index, std::string* url); + bool GetImagePreviewUrl(size_t index, size_t width, size_t height, + std::string* url); + // Returns true if the transferring item size is known + bool GetProgress(size_t& bytes) const; + // Returns true if the total size is known + bool GetTotalSize(size_t& bytes) const; + // Returns true if currently transferring item name is known + bool GetCurrentItemName(std::string* name); + + // TODO: Eliminate this eventually? + cricket::Session* session() { return session_; } + + // StreamPool Interface + virtual talk_base::StreamInterface* + RequestConnectedStream(const talk_base::SocketAddress& remote, int* err); + virtual void ReturnConnectedStream(talk_base::StreamInterface* stream); + + // MessageHandler Interface + virtual void OnMessage(talk_base::Message* msg); + + void GetItemNetworkPath(size_t index, bool preview, std::string* path); + +private: + typedef std::list<StreamRelay*> ProxyList; + typedef std::list<talk_base::HttpTransaction*> TransactionList; + + // Session Signals + void OnSessionState(cricket::Session* session, cricket::Session::State state); + void OnSessionInfoMessage(cricket::Session* session, + const cricket::Session::XmlElements& els); + void OnSessionChannelGone(cricket::Session* session, + const std::string& name); + + // HttpClient Signals + void OnHttpClientComplete(talk_base::HttpClient* http, int err); + void OnHttpClientClosed(talk_base::HttpClient* http, int err); + + // HttpServer Signals + void OnHttpRequest(talk_base::HttpServer* server, + talk_base::HttpTransaction* transaction); + void OnHttpRequestComplete(talk_base::HttpServer* server, + talk_base::HttpTransaction* transaction, + int err); + void OnHttpConnectionClosed(talk_base::HttpServer* server, + int err, + talk_base::StreamInterface* stream); + + // TarStream Signals + void OnNextEntry(const std::string& name, size_t size); + + // Socket Signals + void OnProxyAccept(talk_base::AsyncSocket* socket); + void OnProxyClosed(StreamRelay* proxy, int error); + + // StreamCounterSignals + void OnUpdateBytes(size_t count); + + // Internal Helpers + void GenerateTemporaryPrefix(std::string* prefix); + bool GetItemBaseUrl(size_t index, bool preview, std::string* url); + bool GetProxyAddress(talk_base::SocketAddress& address, bool is_remote); + talk_base::StreamInterface* CreateChannel(const std::string& channel_name); + void SetState(FileShareState state, bool prevent_close); + void OnInitiate(); + void NextDownload(); + const FileShareDescription* description() const; + void DoClose(bool terminate); + + cricket::Session* session_; + FileShareState state_; + bool is_closed_; + bool is_sender_; + buzz::Jid jid_; + FileShareManifest* manifest_; + std::string source_path_; + std::string preview_path_; + std::string local_folder_; + + // The currently active p2p streams to our peer + talk_base::StreamCache pool_; + // The http client state (client only) + talk_base::HttpClient* http_client_; + // The http server state (server only) + talk_base::HttpServer* http_server_; + // The connection id of the currently transferring file (server) + int transfer_connection_id_; + // The counter for the currently transferring file + const StreamCounter* counter_; + // The number of manifest items that have successfully transferred + size_t item_transferring_; + // The byte count of successfully transferred items + size_t bytes_transferred_; + // Where the currently transferring item is being (temporarily) saved (client) + std::string transfer_path_; + // The name of the currently transferring item + std::string transfer_name_; + // Where the files are saved after transfer (client) + std::vector<std::string> stored_location_; + // Was it a local cancel? Or a remote cancel? + bool local_cancel_; + // Proxy socket for local IE http requests + talk_base::AsyncSocket* local_listener_; + // Proxy socket for remote IE http requests + talk_base::AsyncSocket* remote_listener_; + // Cached address of remote_listener_ + talk_base::SocketAddress remote_listener_address_; + // Uniqueness for channel names + size_t next_channel_id_; + // Proxy relays + ProxyList proxies_; + std::string user_agent_; + TransactionList transactions_; +}; + +class FileShareSessionClient : public SessionClient +{ + public: + FileShareSessionClient(SessionManager *sm, buzz::Jid jid, const std::string &user_agent) : sm_(sm), jid_(jid), + user_agent_(user_agent) {} + virtual void OnSessionCreate(cricket::Session* session, + bool received_initiate); + virtual void OnSessionDestroy(cricket::Session* session); + virtual const cricket::SessionDescription* CreateSessionDescription(const buzz::XmlElement* element); + virtual buzz::XmlElement* TranslateSessionDescription(const cricket::SessionDescription* description); + FileShareSession *CreateFileShareSession(); + + sigslot::signal1<FileShareSession*> SignalFileShareSessionCreate; + sigslot::signal1<FileShareSession*> SignalFileShareSessionDestroy; + + private: + SessionManager *sm_; + buzz::Jid jid_; + friend class FileShareSession; + typedef std::set<cricket::Session*> SessionSet; + SessionSet sessions_; + std::string user_agent_; +}; + +} // namespace cricket + +#endif // TALK_APP_WIN32_FILESHARE_H__ |