Example #1
0
        public void Delete(string remotename)
        {
            try
            {
                TaggedFileEntry f = GetFile(remotename);

                //This does not work if the item is in a folder, because it will only be removed from the folder,
                // even if with the "delete=true" setting
                //CreateRequest().Delete(new Uri(f.AtomEntry.EditUri.Content + "?delete=true"), f.ETag);

                //Instead we create the root element id (that is without any folder information),
                //and delete that instead, that seems to works as desired, fully removing the file
                Google.Documents.DocumentsRequest req = CreateRequest();
                string url = req.BaseUri + "/" + HttpUtility.UrlEncode(f.ResourceId) + "?delete=true";
                req.Delete(new Uri(url), f.ETag);

                //We need to ensure that a LIST will not return the removed file
                m_files.Remove(remotename);
            }
            catch (Google.GData.Client.CaptchaRequiredException cex)
            {
                throw new Exception(string.Format(Strings.GoogleDocs.CaptchaRequiredError, CAPTCHA_UNLOCK_URL), cex);
            }
            catch (System.IO.FileNotFoundException ex)
            {
                throw new FileMissingException(ex);
            }
            catch
            {
                //We have no idea if the file was removed or not,
                // so we force a filelist reload
                m_files = null;
                throw;
            }
        }
Example #2
0
        private TaggedFileEntry GetFile(string filename)
        {
            TaggedFileEntry res = TryGetFile(filename);

            if (res == null)
            {
                throw new System.IO.FileNotFoundException(filename);
            }

            return(res);
        }
Example #3
0
        public void Put(string remotename, System.IO.Stream stream)
        {
            try
            {
                
                Google.Documents.Document folder = GetFolder();
                
                //Special, since uploads can overwrite or create,
                // we must figure out if the file exists in advance.
                //Unfortunately it would be wastefull to request the list 
                // for each upload request, so we rely on the cache being
                // correct

                TaggedFileEntry doc = null;
                if (m_files == null)
                    doc = TryGetFile(remotename);
                else 
                    m_files.TryGetValue(remotename, out doc);

                try
                {
                    string resumableUri;
                    if (doc != null)
                    {
                        if (doc.MediaUrl == null)
                        {
                            //Strange, we could not get the edit url, perhaps it is readonly?
                            //Fallback strategy is "delete-then-upload"
                            try { this.Delete(remotename); }
                            catch { }

                            doc = TryGetFile(remotename);
                            if (doc != null || doc.MediaUrl == null)
                                throw new Exception(string.Format(Strings.GoogleDocs.FileIsReadOnlyError, remotename));
                        }
                    }


                    //File does not exist, we upload a new one
                    if (doc == null)
                    {
                        //First we need to get a resumeable upload url
                        HttpWebRequest req = (HttpWebRequest)WebRequest.Create("https://docs.google.com/feeds/upload/create-session/default/private/full/" + System.Web.HttpUtility.UrlEncode(folder.ResourceId) + "/contents?convert=false");
                        req.Method = "POST";
                        req.Headers.Add("X-Upload-Content-Length", stream.Length.ToString());
                        req.Headers.Add("X-Upload-Content-Type", "application/octet-stream");
                        req.UserAgent = USER_AGENT;
                        req.Headers.Add("GData-Version", "3.0");

                        //Build the atom entry describing the file we want to create
                        string labels = "";
                        foreach (string s in m_labels)
                            if (s.Trim().Length > 0)
                                labels += string.Format(ATTRIBUTE_TEMPLATE, s);

                        //Apply the name and content-type to the not-yet-uploaded file
                        byte[] data = System.Text.Encoding.UTF8.GetBytes(string.Format(CREATE_ITEM_TEMPLATE, System.Web.HttpUtility.HtmlEncode(remotename), labels));
                        req.ContentLength = data.Length;
                        req.ContentType = "application/atom+xml";

                        //Authenticate our request
                        m_cla.ApplyAuthenticationToRequest(req);

                        Utility.AsyncHttpRequest areq = new Utility.AsyncHttpRequest(req);
                        using (System.IO.Stream s = areq.GetRequestStream())
                            s.Write(data, 0, data.Length);

                        using (HttpWebResponse resp = (HttpWebResponse)areq.GetResponse())
                        {
                            int code = (int)resp.StatusCode;
                            if (code < 200 || code >= 300) //For some reason Mono does not throw this automatically
                                throw new System.Net.WebException(resp.StatusDescription, null, System.Net.WebExceptionStatus.ProtocolError, resp);

                            resumableUri = resp.Headers["Location"];
                        }
                    }
                    else
                    {
                        //First we need to get a resumeable upload url
                        HttpWebRequest req = (HttpWebRequest)WebRequest.Create(doc.MediaUrl);
                        req.Method = "PUT";
                        req.Headers.Add("X-Upload-Content-Length", stream.Length.ToString());
                        req.Headers.Add("X-Upload-Content-Type", "application/octet-stream");
                        req.UserAgent = USER_AGENT;
                        req.Headers.Add("If-Match", doc.ETag);
                        req.Headers.Add("GData-Version", "3.0");

                        //This is a blank marker request
                        req.ContentLength = 0;
                        //Bad... docs say "text/plain" or "text/xml", but really needs to be content type, otherwise overwrite fails
                        //req.ContentType = "text/plain";
                        req.ContentType = "application/octet-stream";

                        //Authenticate our request
                        m_cla.ApplyAuthenticationToRequest(req);

                        Utility.AsyncHttpRequest areq = new Utility.AsyncHttpRequest(req);
                        using (HttpWebResponse resp = (HttpWebResponse)areq.GetResponse())
                        {
                            int code = (int)resp.StatusCode;
                            if (code < 200 || code >= 300) //For some reason Mono does not throw this automatically
                                throw new System.Net.WebException(resp.StatusDescription, null, System.Net.WebExceptionStatus.ProtocolError, resp);

                            resumableUri = resp.Headers["Location"];
                        }
                    }

                    //Ensure that we have a resumeable upload url
                    if (resumableUri == null)
                        throw new Exception(Strings.GoogleDocs.NoResumeURLError);

                    string id = null;
                    byte[] buffer = new byte[8 * 1024];
                    int retries = 0;
                    long initialPosition;
                    DateTime initialRequestTime = DateTime.Now;

                    while (stream.Position != stream.Length)
                    {
                        initialPosition = stream.Position;
                        long postbytes = Math.Min(stream.Length - initialPosition, TRANSFER_CHUNK_SIZE);

                        //Post a fragment of the file as a partial request
                        HttpWebRequest req = (HttpWebRequest)WebRequest.Create(resumableUri);
                        req.Method = "PUT";
                        req.UserAgent = USER_AGENT;
                        req.ContentLength = postbytes;
                        req.ContentType = "application/octet-stream";
                        req.Headers.Add("Content-Range", string.Format("bytes {0}-{1}/{2}", initialPosition, initialPosition + (postbytes - 1), stream.Length.ToString()));

                        //Copy the current fragment of bytes
                        Utility.AsyncHttpRequest areq = new Utility.AsyncHttpRequest(req);
                        using (System.IO.Stream s = areq.GetRequestStream())
                        {
                            long bytesleft = postbytes;
                            long written = 0;
                            int a;
                            while (bytesleft != 0 && ((a = stream.Read(buffer, 0, (int)Math.Min(buffer.Length, bytesleft))) != 0))
                            {
                                s.Write(buffer, 0, a);
                                bytesleft -= a;
                                written += a;
                            }

                            s.Flush();

                            if (bytesleft != 0 || postbytes != written)
                                throw new System.IO.EndOfStreamException();
                        }

                        try
                        {
                            using (HttpWebResponse resp = (HttpWebResponse)areq.GetResponse())
                            {
                                int code = (int)resp.StatusCode;
                                if (code < 200 || code >= 300) //For some reason Mono does not throw this automatically
                                    throw new System.Net.WebException(resp.StatusDescription, null, System.Net.WebExceptionStatus.ProtocolError, resp);

                                //If all goes well, we should now get an atom entry describing the new element
                                System.Xml.XmlDocument xml = new XmlDocument();
                                using (System.IO.Stream s = areq.GetResponseStream())
                                    xml.Load(s);

                                System.Xml.XmlNamespaceManager mgr = new XmlNamespaceManager(xml.NameTable);
                                mgr.AddNamespace("atom", "http://www.w3.org/2005/Atom");
                                mgr.AddNamespace("gd", "http://schemas.google.com/g/2005");
                                id = xml.SelectSingleNode("atom:entry/atom:id", mgr).InnerText;
                                string resourceId = xml.SelectSingleNode("atom:entry/gd:resourceId", mgr).InnerText;
                                string url = xml.SelectSingleNode("atom:entry/atom:content", mgr).Attributes["src"].Value;
                                string mediaUrl = null;

                                foreach(XmlNode n in xml.SelectNodes("atom:entry/atom:link", mgr))
                                    if (n.Attributes["rel"] != null && n.Attributes["href"] != null &&n.Attributes["rel"].Value.EndsWith("#resumable-edit-media")) 
                                    {
                                        mediaUrl = n.Attributes["href"].Value;
                                        break;
                                    }

                                if (doc == null)
                                {
                                    TaggedFileEntry tf = new TaggedFileEntry(remotename, stream.Length, initialRequestTime, initialRequestTime, resourceId, url, mediaUrl, resp.Headers["ETag"]);
                                    m_files.Add(remotename, tf);
                                }
                                else
                                {

                                    //Since we update an existing item, we just need to update the ETag
                                    doc.ETag = resp.Headers["ETag"];
                                }


                            }
                            retries = 0;
                        }
                        catch (WebException wex)
                        {
							bool acceptedError =
								wex.Status == WebExceptionStatus.ProtocolError &&
                                wex.Response is HttpWebResponse &&
                                (int)((HttpWebResponse)wex.Response).StatusCode == 308;

							//Mono does not give us the response object,
							// so we rely on the error code being present
							// in the string, not ideal, but I have found
							// no other workaround :(
							if (Duplicati.Library.Utility.Utility.IsMono)
							{
								acceptedError |= 
									wex.Status == WebExceptionStatus.ProtocolError &&
									wex.Message.Contains("308");
							}

                            //Accept the 308 until we are complete
                            if (acceptedError &&
                                initialPosition + postbytes != stream.Length)
                            {
                                retries = 0;
                                //Accept the 308 until we are complete
                            }
                            else
                            {
                                //Retries are handled in Duplicati, but it is much more efficient here,
                                // because we only re-submit the last TRANSFER_CHUNK_SIZE bytes,
                                // instead of the entire file
                                retries++;
                                if (retries > 2)
                                    throw;
                                else
                                    System.Threading.Thread.Sleep(2000 * retries);

                                stream.Position = initialPosition;
                            }

                        }
                    }

                    if (string.IsNullOrEmpty(id))
                        throw new Exception(Strings.GoogleDocs.NoIDReturnedError);
                }
                catch
                {
                    //Clear the cache as we have no idea what happened
                    m_files = null;

                    throw;
                }
            }
            catch (Google.GData.Client.CaptchaRequiredException cex)
            {
                throw new Exception(string.Format(Strings.GoogleDocs.CaptchaRequiredError, CAPTCHA_UNLOCK_URL), cex);
            }
        }
Example #4
0
        public void Put(string remotename, System.IO.Stream stream)
        {
            try
            {
                Google.Documents.Document folder = GetFolder();

                //Special, since uploads can overwrite or create,
                // we must figure out if the file exists in advance.
                //Unfortunately it would be wastefull to request the list
                // for each upload request, so we rely on the cache being
                // correct

                TaggedFileEntry doc = null;
                if (m_files == null)
                {
                    doc = TryGetFile(remotename);
                }
                else
                {
                    m_files.TryGetValue(remotename, out doc);
                }

                try
                {
                    string resumableUri;
                    if (doc != null)
                    {
                        if (doc.MediaUrl == null)
                        {
                            //Strange, we could not get the edit url, perhaps it is readonly?
                            //Fallback strategy is "delete-then-upload"
                            try { this.Delete(remotename); }
                            catch { }

                            doc = TryGetFile(remotename);
                            if (doc != null || doc.MediaUrl == null)
                            {
                                throw new Exception(string.Format(Strings.GoogleDocs.FileIsReadOnlyError, remotename));
                            }
                        }
                    }


                    //File does not exist, we upload a new one
                    if (doc == null)
                    {
                        //First we need to get a resumeable upload url
                        HttpWebRequest req = (HttpWebRequest)WebRequest.Create("https://docs.google.com/feeds/upload/create-session/default/private/full/" + System.Web.HttpUtility.UrlEncode(folder.ResourceId) + "/contents?convert=false");
                        req.Method = "POST";
                        req.Headers.Add("X-Upload-Content-Length", stream.Length.ToString());
                        req.Headers.Add("X-Upload-Content-Type", "application/octet-stream");
                        req.UserAgent = USER_AGENT;
                        req.Headers.Add("GData-Version", "3.0");

                        //Build the atom entry describing the file we want to create
                        string labels = "";
                        foreach (string s in m_labels)
                        {
                            if (s.Trim().Length > 0)
                            {
                                labels += string.Format(ATTRIBUTE_TEMPLATE, s);
                            }
                        }

                        //Apply the name and content-type to the not-yet-uploaded file
                        byte[] data = System.Text.Encoding.UTF8.GetBytes(string.Format(CREATE_ITEM_TEMPLATE, System.Web.HttpUtility.HtmlEncode(remotename), labels));
                        req.ContentLength = data.Length;
                        req.ContentType   = "application/atom+xml";

                        //Authenticate our request
                        m_cla.ApplyAuthenticationToRequest(req);

                        Utility.AsyncHttpRequest areq = new Utility.AsyncHttpRequest(req);
                        using (System.IO.Stream s = areq.GetRequestStream())
                            s.Write(data, 0, data.Length);

                        using (HttpWebResponse resp = (HttpWebResponse)areq.GetResponse())
                        {
                            int code = (int)resp.StatusCode;
                            if (code < 200 || code >= 300) //For some reason Mono does not throw this automatically
                            {
                                throw new System.Net.WebException(resp.StatusDescription, null, System.Net.WebExceptionStatus.ProtocolError, resp);
                            }

                            resumableUri = resp.Headers["Location"];
                        }
                    }
                    else
                    {
                        //First we need to get a resumeable upload url
                        HttpWebRequest req = (HttpWebRequest)WebRequest.Create(doc.MediaUrl);
                        req.Method = "PUT";
                        req.Headers.Add("X-Upload-Content-Length", stream.Length.ToString());
                        req.Headers.Add("X-Upload-Content-Type", "application/octet-stream");
                        req.UserAgent = USER_AGENT;
                        req.Headers.Add("If-Match", doc.ETag);
                        req.Headers.Add("GData-Version", "3.0");

                        //This is a blank marker request
                        req.ContentLength = 0;
                        //Bad... docs say "text/plain" or "text/xml", but really needs to be content type, otherwise overwrite fails
                        //req.ContentType = "text/plain";
                        req.ContentType = "application/octet-stream";

                        //Authenticate our request
                        m_cla.ApplyAuthenticationToRequest(req);

                        Utility.AsyncHttpRequest areq = new Utility.AsyncHttpRequest(req);
                        using (HttpWebResponse resp = (HttpWebResponse)areq.GetResponse())
                        {
                            int code = (int)resp.StatusCode;
                            if (code < 200 || code >= 300) //For some reason Mono does not throw this automatically
                            {
                                throw new System.Net.WebException(resp.StatusDescription, null, System.Net.WebExceptionStatus.ProtocolError, resp);
                            }

                            resumableUri = resp.Headers["Location"];
                        }
                    }

                    //Ensure that we have a resumeable upload url
                    if (resumableUri == null)
                    {
                        throw new Exception(Strings.GoogleDocs.NoResumeURLError);
                    }

                    string   id      = null;
                    byte[]   buffer  = new byte[8 * 1024];
                    int      retries = 0;
                    long     initialPosition;
                    DateTime initialRequestTime = DateTime.Now;

                    while (stream.Position != stream.Length)
                    {
                        initialPosition = stream.Position;
                        long postbytes = Math.Min(stream.Length - initialPosition, TRANSFER_CHUNK_SIZE);

                        //Post a fragment of the file as a partial request
                        HttpWebRequest req = (HttpWebRequest)WebRequest.Create(resumableUri);
                        req.Method        = "PUT";
                        req.UserAgent     = USER_AGENT;
                        req.ContentLength = postbytes;
                        req.ContentType   = "application/octet-stream";
                        req.Headers.Add("Content-Range", string.Format("bytes {0}-{1}/{2}", initialPosition, initialPosition + (postbytes - 1), stream.Length.ToString()));

                        //Copy the current fragment of bytes
                        Utility.AsyncHttpRequest areq = new Utility.AsyncHttpRequest(req);
                        using (System.IO.Stream s = areq.GetRequestStream())
                        {
                            long bytesleft = postbytes;
                            long written   = 0;
                            int  a;
                            while (bytesleft != 0 && ((a = stream.Read(buffer, 0, (int)Math.Min(buffer.Length, bytesleft))) != 0))
                            {
                                s.Write(buffer, 0, a);
                                bytesleft -= a;
                                written   += a;
                            }

                            s.Flush();

                            if (bytesleft != 0 || postbytes != written)
                            {
                                throw new System.IO.EndOfStreamException();
                            }
                        }

                        try
                        {
                            using (HttpWebResponse resp = (HttpWebResponse)areq.GetResponse())
                            {
                                int code = (int)resp.StatusCode;
                                if (code < 200 || code >= 300) //For some reason Mono does not throw this automatically
                                {
                                    throw new System.Net.WebException(resp.StatusDescription, null, System.Net.WebExceptionStatus.ProtocolError, resp);
                                }

                                //If all goes well, we should now get an atom entry describing the new element
                                System.Xml.XmlDocument xml = new XmlDocument();
                                using (System.IO.Stream s = areq.GetResponseStream())
                                    xml.Load(s);

                                System.Xml.XmlNamespaceManager mgr = new XmlNamespaceManager(xml.NameTable);
                                mgr.AddNamespace("atom", "http://www.w3.org/2005/Atom");
                                mgr.AddNamespace("gd", "http://schemas.google.com/g/2005");
                                id = xml.SelectSingleNode("atom:entry/atom:id", mgr).InnerText;
                                string resourceId = xml.SelectSingleNode("atom:entry/gd:resourceId", mgr).InnerText;
                                string url        = xml.SelectSingleNode("atom:entry/atom:content", mgr).Attributes["src"].Value;
                                string mediaUrl   = null;

                                foreach (XmlNode n in xml.SelectNodes("atom:entry/atom:link", mgr))
                                {
                                    if (n.Attributes["rel"] != null && n.Attributes["href"] != null && n.Attributes["rel"].Value.EndsWith("#resumable-edit-media"))
                                    {
                                        mediaUrl = n.Attributes["href"].Value;
                                        break;
                                    }
                                }

                                if (doc == null)
                                {
                                    TaggedFileEntry tf = new TaggedFileEntry(remotename, stream.Length, initialRequestTime, initialRequestTime, resourceId, url, mediaUrl, resp.Headers["ETag"]);
                                    m_files.Add(remotename, tf);
                                }
                                else
                                {
                                    //Since we update an existing item, we just need to update the ETag
                                    doc.ETag = resp.Headers["ETag"];
                                }
                            }
                            retries = 0;
                        }
                        catch (WebException wex)
                        {
                            bool acceptedError =
                                wex.Status == WebExceptionStatus.ProtocolError &&
                                wex.Response is HttpWebResponse &&
                                (int)((HttpWebResponse)wex.Response).StatusCode == 308;

                            //Mono does not give us the response object,
                            // so we rely on the error code being present
                            // in the string, not ideal, but I have found
                            // no other workaround :(
                            if (Duplicati.Library.Utility.Utility.IsMono)
                            {
                                acceptedError |=
                                    wex.Status == WebExceptionStatus.ProtocolError &&
                                    wex.Message.Contains("308");
                            }

                            //Accept the 308 until we are complete
                            if (acceptedError &&
                                initialPosition + postbytes != stream.Length)
                            {
                                retries = 0;
                                //Accept the 308 until we are complete
                            }
                            else
                            {
                                //Retries are handled in Duplicati, but it is much more efficient here,
                                // because we only re-submit the last TRANSFER_CHUNK_SIZE bytes,
                                // instead of the entire file
                                retries++;
                                if (retries > 2)
                                {
                                    throw;
                                }
                                else
                                {
                                    System.Threading.Thread.Sleep(2000 * retries);
                                }

                                stream.Position = initialPosition;
                            }
                        }
                    }

                    if (string.IsNullOrEmpty(id))
                    {
                        throw new Exception(Strings.GoogleDocs.NoIDReturnedError);
                    }
                }
                catch
                {
                    //Clear the cache as we have no idea what happened
                    m_files = null;

                    throw;
                }
            }
            catch (Google.GData.Client.CaptchaRequiredException cex)
            {
                throw new Exception(string.Format(Strings.GoogleDocs.CaptchaRequiredError, CAPTCHA_UNLOCK_URL), cex);
            }
        }
Example #5
0
        private TaggedFileEntry ParseXmlAtomEntry(XmlNode node, XmlNamespaceManager mgr)
        {
            if (mgr == null)
                mgr = new XmlNamespaceManager(node.OwnerDocument.NameTable);

            if (!mgr.HasNamespace("live"))
                mgr.AddNamespace("live", "http://api.live.com/schemas");
            if (!mgr.HasNamespace("atom"))
                mgr.AddNamespace("atom", "http://www.w3.org/2005/Atom");

            XmlNode title = node.SelectSingleNode("atom:title", mgr);
            XmlNode id = node.SelectSingleNode("live:resourceId", mgr);
            XmlNode url = node.SelectSingleNode("atom:id", mgr);

            if (title == null || id == null || url == null)
                return null;

            XmlNode updated = node.SelectSingleNode("atom:updated", mgr);
            XmlNode size = node.SelectSingleNode("live:size", mgr);
            XmlNode type = node.SelectSingleNode("live:type", mgr);

            string u = url.InnerText;
            XmlNode content = node.SelectSingleNode("atom:content", mgr);
            if (content != null) content = content.Attributes["src"];
            if (content != null) u = content.Value;

            string editLink = null;
            string altLink = null;

            foreach (XmlNode n in node.SelectNodes("atom:link", mgr))
                if (n.Attributes["rel"] != null && n.Attributes["href"] != null)
                {
                    if (n.Attributes["rel"].Value == "edit-media")
                        editLink = n.Attributes["href"].Value;
                    else if (n.Attributes["rel"].Value == "alternate")
                        altLink = n.Attributes["href"].Value;
                }

            TaggedFileEntry tf = new TaggedFileEntry(title.InnerText, u, id.InnerText, altLink, editLink);
            try { tf.LastAccess = tf.LastModification = DateTime.Parse(updated.InnerText); }
            catch { }

            try { tf.Size = long.Parse(size.InnerText); }
            catch { }

            try { tf.IsFolder = type.InnerText.Equals("Library", StringComparison.InvariantCultureIgnoreCase) || type.InnerText.Equals("Folder", StringComparison.InvariantCultureIgnoreCase); }
            catch { }

            return tf;
        }