[Date Prev][Date Next] [Thread Prev][Thread Next] [Date Index] [Thread Index]

request ublock for releaseforge/1.3-1 [WAS: Re: How should I handle the sitation with releaseforge?]



On Wed, Jul 30, 2008 at 08:56:19PM +0200, Marc 'HE' Brockschmidt wrote:
> Roberto C. Sánchez <roberto@connexer.com> writes:
> > As of about one week ago, version 1.3 is now available from upstream
> > [2].  This version works with all the changes to SourceForge's site.
> > My first instinct was to simply request removal of the package.
> > However, that is undesirable as it has already shipped with a stable
> > release (version 1.1-1 is in Etch).  Removal from Lenny would leave Etch
> > users without any sort of upgrade path.  Leaving the current version in
> > Lenny would, however, result in a package that is completely broken for
> > Lenny's entire life cycle.
> 
> Pushing an untested package into the frozen lenny is not an option, so
> removal seems to be the most reasonable course.
> 
OK.  Based on #492933, I asked for a removal from testing.  After that,
I asked about the possibility for inclusion in Lenny on #debian-release
and the impression that I got from vorlon and dato was that if the
changes were limited, that it would be possible to get an exception.

So, I and another user have tested the updated package.  Have a look at
the log for #492933 for the user's comments about the package working
for him.  I have also verified that it works for me.  I have attached
the debdiff between 1.1-4 (currently in Sid and it was in Lenny until
yesterday) and 1.3-1 (please note that upstream never released version
1.2, he went from 1.1 a little over two years ago to 1.3 just last
week).  Please consider this package for inclusion in Lenny (the package
is currently in INCOMING).

Regards,

-Roberto

-- 
Roberto C. Sánchez
http://people.connexer.com/~roberto
http://www.connexer.com
diff -Nru releaseforge-1.1/CHANGELOG.txt releaseforge-1.3/CHANGELOG.txt
--- releaseforge-1.1/CHANGELOG.txt	2006-06-15 05:51:53.000000000 -0400
+++ releaseforge-1.3/CHANGELOG.txt	2008-06-12 00:12:41.000000000 -0400
@@ -1,6 +1,17 @@
 ReleaseForge ChangeLog
 ----------------------
 
+version 1.3:
+  *) added support for sftp
+  *) removed support for ftp
+
+version 1.2:
+ *) uploading release notes and change log now attempts to use tempfiles to transmit
+   the data as multipart encodings rather than inline text (if unable to create the temp files
+   ReleaseForge will fallback to the old mechanism).  This should allow encoded entities (such as 
+   the ampersand) to be transmitted properly.
+ *) ReleaseForge will now automatically retry failed https connections
+
 version 1.1:
  *) added re-guess file attributes button
  *) fix: disabled chunking when selecting files to include in a release which caused the
diff -Nru /tmp/user/2000/X5m6gbvF7f/releaseforge-1.1/MANIFEST.in /tmp/user/2000/EZty4Ck08i/releaseforge-1.3/MANIFEST.in
--- releaseforge-1.1/MANIFEST.in	2006-04-24 12:46:24.000000000 -0400
+++ releaseforge-1.3/MANIFEST.in	2008-07-07 12:17:28.000000000 -0400
@@ -1,7 +1,5 @@
 recursive-include help *.html
 recursive-include help *.png
-######recursive-include translations *
-######recursive-include scripts *
 include setup.cfg
 include images/*.png
 include images/*.ico
@@ -9,6 +7,7 @@
 include LICENSE.txt
 include README.txt
 include CHANGELOG.txt
+include RELEASE_NOTES.txt
 include ReleaseForge/*.ui
 include ReleaseForge/*.py
 include releaseforge.pro
diff -Nru /tmp/user/2000/X5m6gbvF7f/releaseforge-1.1/PKG-INFO /tmp/user/2000/EZty4Ck08i/releaseforge-1.3/PKG-INFO
--- releaseforge-1.1/PKG-INFO	2006-06-15 06:02:06.000000000 -0400
+++ releaseforge-1.3/PKG-INFO	2008-07-07 12:17:53.000000000 -0400
@@ -1,6 +1,6 @@
 Metadata-Version: 1.0
 Name: releaseforge
-Version: 1.1
+Version: 1.3
 Summary: ReleaseForge is a utility designed for the administrators and release engineers of SourceForge projects.
 Home-page: http://releaseforge.sourceforge.net
 Author: Phil Schwartz
diff -Nru /tmp/user/2000/X5m6gbvF7f/releaseforge-1.1/RELEASE_NOTES.txt /tmp/user/2000/EZty4Ck08i/releaseforge-1.3/RELEASE_NOTES.txt
--- releaseforge-1.1/RELEASE_NOTES.txt	1969-12-31 19:00:00.000000000 -0500
+++ releaseforge-1.3/RELEASE_NOTES.txt	2008-07-07 12:16:27.000000000 -0400
@@ -0,0 +1,19 @@
+Version 1.3:
+============
+
+Sourceforge has recently deprecated the FTP upload mechanism.  This policy change cripples ReleaseForge prior to 1.3.
+
+ReleaseForge 1.3 now uploads files to Sourceforge via SFTP (one of the new supported methods).
+
+Prior to running ReleaseForge 1.3 you will need to obtain Paramiko v1.74 or greater.
+ReleaseFroge uses paramiko module for uploading files to SourceForge via SFTP.  [Special thanks to Robey Pointer for adding the 
+patch required for ReleaseForge].
+
+Paramiko can be downloaded from:
+
+http://www.lag.net/paramiko/
+
+Make sure you obtain 1.74 or greater.  Versions prior to 1.74 will not work.
+
+Once paramiko is installed, ReleaseForge should be able to upload files to Sourceforge via SFTP using the same login credentials 
+for accessing the Sourceforge web interface. 
diff -Nru /tmp/user/2000/X5m6gbvF7f/releaseforge-1.1/ReleaseForge/MultipartPostHandler.py /tmp/user/2000/EZty4Ck08i/releaseforge-1.3/ReleaseForge/MultipartPostHandler.py
--- releaseforge-1.1/ReleaseForge/MultipartPostHandler.py	1969-12-31 19:00:00.000000000 -0500
+++ releaseforge-1.3/ReleaseForge/MultipartPostHandler.py	2006-07-01 17:30:14.000000000 -0400
@@ -0,0 +1,128 @@
+#!/usr/bin/python
+
+####
+# 02/2006 Will Holcomb <wholcomb@gmail.com>
+# 
+# This library is free software; you can redistribute it and/or
+# modify it under the terms of the GNU Lesser General Public
+# License as published by the Free Software Foundation; either
+# version 2.1 of the License, or (at your option) any later version.
+# 
+# This library is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+# Lesser General Public License for more details.
+#
+"""
+Usage:
+  Enables the use of multipart/form-data for posting forms
+
+Inspirations:
+  Upload files in python:
+    http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/146306
+  urllib2_file:
+    Fabien Seisen: <fabien@seisen.org>
+
+Example:
+  import MultipartPostHandler, urllib2, cookielib
+
+  cookies = cookielib.CookieJar()
+  opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookies),
+                                MultipartPostHandler.MultipartPostHandler)
+  params = { "username" : "bob", "password" : "riviera",
+             "file" : open("filename", "rb") }
+  opener.open("http://wwww.bobsite.com/upload/";, params)
+
+Further Example:
+  The main function of this file is a sample which downloads a page and
+  then uploads it to the W3C validator.
+"""
+
+import urllib
+import urllib2
+import mimetools, mimetypes
+import os, stat
+
+class Callable:
+    def __init__(self, anycallable):
+        self.__call__ = anycallable
+
+class MultipartPostHandler(urllib2.BaseHandler):
+    handler_order = urllib2.HTTPHandler.handler_order - 10 # needs to run first
+
+    def http_request(self, request):
+        data = request.get_data()
+        if data is not None and type(data) != str:
+            v_files = []
+            v_vars = []
+            try:
+                 for(key, value) in data.items():
+                     if type(value) == file:
+                         v_files.append((key, value))
+                     else:
+                         v_vars.append((key, value))
+            except TypeError:
+                systype, value, traceback = sys.exc_info()
+                raise TypeError, "not a valid non-string sequence or mapping object", traceback
+
+            if len(v_files) == 0:
+                data = urllib.urlencode(v_vars)
+            else:
+                boundary, data = self.multipart_encode(v_vars, v_files)
+                contenttype = 'multipart/form-data; boundary=%s' % boundary
+                if request.has_header('Content-type'):
+                    pass # print "Replacing %s with %s" % (request.get_header('content-type'), contenttype)
+                request.add_unredirected_header('Content-type', contenttype)
+
+            request.add_data(data)
+
+        return request
+
+    def multipart_encode(vars, files, boundary = None, buffer = None):
+        if boundary is None:
+            boundary = mimetools.choose_boundary()
+        if buffer is None:
+            buffer = ''
+        for(key, value) in vars:
+            buffer += '--%s\r\n' % boundary
+            buffer += 'Content-Disposition: form-data; name="%s"' % key
+            buffer += '\r\n\r\n' + value + '\r\n'
+        for(key, fd) in files:
+            file_size = os.fstat(fd.fileno())[stat.ST_SIZE]
+            filename = fd.name.split('/')[-1]
+            contenttype = mimetypes.guess_type(filename)[0] or 'application/octet-stream'
+            buffer += '--%s\r\n' % boundary
+            buffer += 'Content-Disposition: form-data; name="%s"; filename="%s"\r\n' % (key, filename)
+            buffer += 'Content-Type: %s\r\n' % contenttype
+            # buffer += 'Content-Length: %s\r\n' % file_size
+            fd.seek(0)
+            buffer += '\r\n' + fd.read() + '\r\n'
+        buffer += '--%s--\r\n\r\n' % boundary
+        return boundary, buffer
+    multipart_encode = Callable(multipart_encode)
+
+    https_request = http_request
+
+def main():
+    import tempfile, sys
+
+    validatorURL = "http://validator.w3.org/check";
+    opener = urllib2.build_opener(MultipartPostHandler)
+
+    def validateFile(url):
+        temp = tempfile.mkstemp(suffix=".html")
+        os.write(temp[0], opener.open(url).read())
+        params = { "ss" : "0",            # show source
+                   "doctype" : "Inline",
+                   "uploaded_file" : open(temp[1], "rb") }
+        print opener.open(validatorURL, params).read()
+        os.remove(temp[1])
+
+    if len(sys.argv[1:]) > 0:
+        for arg in sys.argv[1:]:
+            validateFile(arg)
+    else:
+        validateFile("http://www.google.com";)
+
+if __name__=="__main__":
+    main()
diff -Nru /tmp/user/2000/X5m6gbvF7f/releaseforge-1.1/ReleaseForge/constants.py /tmp/user/2000/EZty4Ck08i/releaseforge-1.3/ReleaseForge/constants.py
--- releaseforge-1.1/ReleaseForge/constants.py	2006-04-21 16:31:00.000000000 -0400
+++ releaseforge-1.3/ReleaseForge/constants.py	2008-06-12 21:33:57.000000000 -0400
@@ -12,8 +12,10 @@
 
 LOGGER_CONFIG_FILENAME = "logger.ini"
 
-SF_FTP_SERVER = "upload.sourceforge.net"
-SF_FTP_DIRECTORY = "incoming"
+SF_SFTP_SERVER = "frs.sourceforge.net"
+SF_SFTP_PORT = 22
+SF_SFTP_DIRECTORY = "uploads"
+
 
 SF_WARNING = """
 The follwing files could not be added to your release
diff -Nru /tmp/user/2000/X5m6gbvF7f/releaseforge-1.1/ReleaseForge/ftp.py /tmp/user/2000/EZty4Ck08i/releaseforge-1.3/ReleaseForge/ftp.py
--- releaseforge-1.1/ReleaseForge/ftp.py	2005-12-16 17:47:12.000000000 -0500
+++ releaseforge-1.3/ReleaseForge/ftp.py	1969-12-31 19:00:00.000000000 -0500
@@ -1,44 +0,0 @@
-from ftplib import FTP
-
-class FTP_(FTP):
-    def storbinary(self, cmd, fp, blocksize=8192, callback=None):
-        '''Store a file in binary mode and optionally
-           use a callback func to provide a tuple containing
-           number of bytes transmitted to the ftp server and total size
-           '''
-        self.voidcmd('TYPE I')
-        conn = self.transfercmd(cmd)
-        if callback:
-            totalsent = 0
-            fp.seek(0L, 2) # go to file end
-            totalsize = fp.tell()
-            fp.seek(0L, 0) # go to file start
-            
-        while 1:
-            buf = fp.read(blocksize)
-            if not buf: break   
-            conn.sendall(buf)
-            if callback:
-                callback( (totalsent, totalsize) )
-                totalsent += blocksize
-        conn.close()
-        return self.voidresp()
-
-
-if __name__ == '__main__':
-    def func(transmitted): print "transmitted:", transmitted
-    
-    import sys
-    from constants import SF_FTP_SERVER, SF_FTP_DIRECTORY
-    
-    filename = sys.argv[1]
-    f = open(filename, "rb")
-
-    ftp = FTP_()
-    ftp.connect(SF_FTP_SERVER)
-    ftp.getwelcome()
-
-    ftp.login("ftp", "releaseforge")
-    ftp.cwd(SF_FTP_DIRECTORY)
-    ftp.storbinary('STOR ' + filename, f, callback=func)
-    ftp.close()
diff -Nru /tmp/user/2000/X5m6gbvF7f/releaseforge-1.1/ReleaseForge/messageQueue.py /tmp/user/2000/EZty4Ck08i/releaseforge-1.3/ReleaseForge/messageQueue.py
--- releaseforge-1.1/ReleaseForge/messageQueue.py	2005-12-16 17:47:12.000000000 -0500
+++ releaseforge-1.3/ReleaseForge/messageQueue.py	2008-06-12 21:30:14.000000000 -0400
@@ -22,4 +22,4 @@
 mainMessageQueue = MessageQueue()
 progressMessageQueue = MessageQueue()
 freshmeatMessageQueue = MessageQueue()
-ftpProgressQueue = MessageQueue()
+sftpProgressQueue = MessageQueue()
diff -Nru /tmp/user/2000/X5m6gbvF7f/releaseforge-1.1/ReleaseForge/releaseWizard.py /tmp/user/2000/EZty4Ck08i/releaseforge-1.3/ReleaseForge/releaseWizard.py
--- releaseforge-1.1/ReleaseForge/releaseWizard.py	2006-06-15 05:14:06.000000000 -0400
+++ releaseforge-1.3/ReleaseForge/releaseWizard.py	2008-06-12 21:31:35.000000000 -0400
@@ -6,7 +6,7 @@
 import os
 from ReleaseDataVO import ReleaseDataVO
 from FileVO import FileVO
-from messageQueue import progressMessageQueue, ftpProgressQueue
+from messageQueue import progressMessageQueue, sftpProgressQueue
 from workerThread import WorkerThread
 from help import Help
 import time
@@ -79,9 +79,9 @@
         self.connect(self.timer, SIGNAL("timeout()"),
                      self.getProgressMsg)
 
-        self.ftp_timer = QTimer(self)
-        self.connect(self.ftp_timer, SIGNAL("timeout()"),
-                     self.update_ftp_progress)
+        self.sftp_timer = QTimer(self)
+        self.connect(self.sftp_timer, SIGNAL("timeout()"),
+                     self.update_sftp_progress)
         
         self.connect(self, SIGNAL("selected(const QString &)"), self.pageChanged)
         self.releaseNameEdited(self.newReleaseNameLineEdit.text())
@@ -200,12 +200,12 @@
     def show_file_progress(self, show):
         debug("show_file_progress: %s", show)
         if show:
-            self.ftp_timer.start(50, False)
+            self.sftp_timer.start(50, False)
             self.fileTextLabel.show()
             self.fileProgressBar.setProgress(-1)
             self.fileProgressBar.show()
         else:
-            self.ftp_timer.stop()
+            self.sftp_timer.stop()
             self.fileTextLabel.hide()
             self.fileProgressBar.hide()
 
@@ -664,7 +664,7 @@
         release_notes = data_tuple[0]
         change_log = data_tuple[1]
         files = data_tuple[2]
-              
+
         self.releaseNotesTextEdit.setText(from_utf8(release_notes))
         self.changeLogTextEdit.setText(from_utf8(change_log))
         self.setNextEnabled(self.page(WIZARD_PAGE_RELEASE_NAME), False)
@@ -763,13 +763,19 @@
         # populate the progress label with it.
         msg = progressMessageQueue.get()
         if msg:
-            self.progressTextLabel.setText(msg)            
-            self.incrementProgressBar()
+            self.progressTextLabel.setText(msg)
+            if not msg.startswith("Retry: "):
+                self.incrementProgressBar()
+#            else:
+#                self.decrementProgressBar(1)
                 
 
     def incrementProgressBar(self, increment=1):
         self.progressBar.setProgress(self.progressBar.progress() + increment)
 
+    def decrementProgressBar(self, decrement=1):
+        self.progressBar.setProgress(self.progressBar.progress() - decrement)
+
 
     def isCancelled(self):
         return self.cancelled
@@ -1054,7 +1060,7 @@
 
     def error(self, msg):
         self.timer.stop()
-        self.ftp_timer.stop()
+        self.sftp_timer.stop()
         page = self.page(WIZARD_PAGE_PROGRESS)
         self.progressTextLabel.setText(msg)
         self.setFinishEnabled(page, False)
@@ -1065,7 +1071,7 @@
 
     def complete(self, data):
         self.timer.stop()
-        self.ftp_timer.stop()
+        self.sftp_timer.stop()
         page = self.page(WIZARD_PAGE_PROGRESS)
         if self.create:
             self.progressTextLabel.setText("Successfully created new release")
@@ -1107,10 +1113,10 @@
         self.notifyTextLabel.setEnabled(False)
 
 
-    def update_ftp_progress(self):
+    def update_sftp_progress(self):
         # read the message queue.  If an item exists,
         # populate the progress label with it.
-        progress_tuple = ftpProgressQueue.get()
+        progress_tuple = sftpProgressQueue.get()
         #debug("update_ftp_progress: %s",  progress_tuple)
         if progress_tuple:
             if progress_tuple == (True, True):
diff -Nru /tmp/user/2000/X5m6gbvF7f/releaseforge-1.1/ReleaseForge/sfcomm.py /tmp/user/2000/EZty4Ck08i/releaseforge-1.3/ReleaseForge/sfcomm.py
--- releaseforge-1.1/ReleaseForge/sfcomm.py	2006-06-15 05:49:44.000000000 -0400
+++ releaseforge-1.3/ReleaseForge/sfcomm.py	2008-07-07 09:51:03.000000000 -0400
@@ -1,7 +1,7 @@
 from workerThread import WorkerThread
 from sfurls import *
 from sfregex import *
-from constants import SF_COOKIE_FILENAME, SF_FTP_SERVER, SF_FTP_DIRECTORY
+from constants import SF_COOKIE_FILENAME, SF_SFTP_SERVER, SF_SFTP_DIRECTORY, SF_SFTP_PORT
 
 from ProjectVO import ProjectVO
 from PackageVO import PackageVO
@@ -12,14 +12,19 @@
 except:
     # python2.3 compatibility
     from python24.urllib2 import build_opener, HTTPCookieProcessor, ProxyHandler
+import MultipartPostHandler
 import logging
-from messageQueue import mainMessageQueue, progressMessageQueue, ftpProgressQueue
+from messageQueue import mainMessageQueue, progressMessageQueue, sftpProgressQueue
 import os, sys
-from ftp import FTP_
+import paramiko
 import time
 from constants import SF_RESPONSE_CHUNK_SIZE
+from types import StringType
+import tempfile
 
 debug = logging.getLogger("sfcomm").debug
+info = logging.getLogger("sfcomm").info
+warn =  logging.getLogger("sfcomm").warn
 error = logging.getLogger("sfcomm").error
 exception = logging.getLogger("sfcomm").exception
 
@@ -43,6 +48,18 @@
 
 TRUNCATE_POST = 1024
 
+class RetryException(Exception): pass
+
+def unescape_entities(s):
+    # replace &amp;  &lt;  &gt;  &quot;  in s with their text counterparts
+    if not s: return s
+    s = s.replace("&amp;", "&")
+    s = s.replace("&lt;", "<")
+    s = s.replace("&gt;", ">")
+    s = s.replace("&quot;", '"')
+    return s
+    
+
 class SFComm:
     def __init__(self, data_path, proxy=None):
         self.cookie_file = os.path.join(data_path, SF_COOKIE_FILENAME)
@@ -64,15 +81,17 @@
                   queue=progressMessageQueue,
                   addl_headers=None,
                   ok_status=None,
+                  post_as_multipart=None,
+                  retry=True,
                   dbg=None):
         #
         # parameters:
         # url: url to retrieve
-        # post_encoded: the encoded query string sent to SF
+        # post_encoded: the (preferably) encoded query string sent to SF
         # queue: the python Queue that status information will be sent to
         # addl_headers: any additional headers that need to be passed to SF
         # ok_status: a string as described below.
-        #
+        # post_as_multipart: post form data with enctype: multipart/form-data
         # returns a tuple containing the data returned from SF and the status.
         #
         # If ok_status is provided, the status returned is True if the
@@ -81,26 +100,59 @@
         #
         # If ok_status is None, status is always True and data will not be truncated
         #
+        # If retry is True, then this is the first attempt and if it fails this transaction
+        # will be retried.
+        #
         # return tuple form: (data, status)
         #
         data = ""
         ok = True
         try:
             debug("Fetching: %s", url)
-            if post_encoded:
+            if post_encoded and type(post_encoded) is StringType:
                 if len(post_encoded) > TRUNCATE_POST: truncated = "..."
                 else: truncated = ""
+                
                 debug("posted: %s%s", post_encoded[:TRUNCATE_POST], truncated)
 
-            queue.put("Fetching: %s" % url)
+            if not retry:
+                # has already been tried, this is the retry attempt
+                queue.put("Retry: %s" % url)
+            else:
+                # has not be attempted yet... will retry if this fails
+                queue.put("Fetching: %s" % url)
             
             cj = cookielib.MozillaCookieJar()
             cj.load(self.cookie_file, True)
-            opener = build_opener(HTTPCookieProcessor(cj))
+            if post_as_multipart:
+                # enctype: multipart/form-data
+                opener = build_opener(HTTPCookieProcessor(cj),
+                                      MultipartPostHandler.MultipartPostHandler)
+            else:
+                opener = build_opener(HTTPCookieProcessor(cj))
+
             if addl_headers:
                 opener.addheaders = addl_headers
                 
-            r = opener.open(url, post_encoded)
+            try:
+                r = opener.open(url, post_encoded)
+            except Exception, e:
+                exception(e)
+                error("failed posting: %s",  post_encoded)
+                if retry:
+                    error("retrying last action")
+                    return self.fetch_url(url,
+                                          post_encoded,
+                                          queue,
+                                          addl_headers,
+                                          ok_status,
+                                          post_as_multipart,
+                                          False,
+                                          dbg)
+                else:
+                    error("giving up")
+                    raise RetryException("Retry failed... giving up")
+                
 #            debug("r methods: %s", dir(r))
 #            cj.save(self.cookie_file, True)
             if not ok_status: 
@@ -129,8 +181,10 @@
             r.close()
             if dbg: debug("Done")
             #debug("length: %ld  -  url: %s", len(data), url)
+        except RetryException, e:
+            raise
         except Exception, e:
-            print e
+            exception(e)
 
         if dbg: debug("DATA: %s", data)
         return data, ok
@@ -140,6 +194,8 @@
     def login(self, username, password, return_to=None):
         # returns a set of projects for the given username/password
         # or None
+        self.username = username  # used by sftp
+        self.password = password  # used by sftp
         debug("Attempting to login to SourceForge")
         form = {'form_loginname': username,
                 'form_pw': password,
@@ -168,13 +224,14 @@
 
         cj.save(self.cookie_file, True, True) # ignore discard! ignore expires
         
+        projects = set()
+
         data = r.read()
         idx = data.find("Invalid Password or User Name")
         if idx != -1:
             debug("Could not login")
             return projects
 
-        projects = set()
         #debug(data)
         project_tuples = PROJECTS_FROM_MY_PROJECTS.findall(data)
         for pt in project_tuples:
@@ -186,7 +243,6 @@
         return projects
 
 
-
     def get_packages(self, group_id):
         url = "%s=%s" % (EDIT_PACKAGE_BASE_URL, group_id)
         packages = set()
@@ -237,32 +293,7 @@
             mainMessageQueue.put("You do not have the proper permissions")
             return False
         else: return True
-        
 
-    def add_release(self, group_id, package_id, name):
-        url = "%s?package_id=%s&group_id=%s" % (NEW_RELEASE_URL,
-                                                package_id,
-                                                group_id)
-        form = {'package_id': package_id,
-                'group_id': group_id,
-                'release_name': name,
-                'newrelease': 'yes',
-                'submit': 'Create This Release'}
-
-        data = self.fetch_url(url, urlencode(form))[0]
-        #print url
-        m = GET_RELEASE_ID.search(data)
-        if m:
-            return m.group("releaseid")
-        else:
-            if data.find("Error creating Project object") != -1:
-                progressMessageQueue.put("Error creating Project object")
-            elif data.find("Login to SourceForge.net") != -1:
-                progressMessageQueue.put("Login required?")
-                #print data
-            else:
-                progressMessageQueue.put("Error creating new release")
-            return None
 
     def get_releases(self, group_id, package_id):
         url = "%s?group_id=%s&package_id=%s" % (EDIT_RELEASE_URL,
@@ -293,8 +324,8 @@
         
         m = GET_NOTES_AND_LOG.search(data)        
         if m:
-            release_notes = m.group("releasenotes")
-            change_log = m.group("changelog")
+            release_notes = unescape_entities(m.group("releasenotes"))
+            change_log = unescape_entities(m.group("changelog"))
 
         filedata = GET_FILE_NAMES_ETC.findall(data)
         files = []
@@ -313,6 +344,7 @@
             debug("file: %s - %s - %s - %s", fileid, filename, processor, filetype)
         
         debug("edit release - files: %s", files)
+    
         return (release_notes, change_log, files)
 
 
@@ -338,8 +370,6 @@
         return result
     
 
-
-
     def add_release(self, group_id, package_id, name):
         url = "%s?package_id=%s&group_id=%s" % (NEW_RELEASE_URL,
                                                 package_id,
@@ -363,6 +393,7 @@
                 #print data
             else:
                 progressMessageQueue.put("Error creating new release")
+                error("error creating new release (could not find releaseid): " + data)
             return None
 
 
@@ -383,19 +414,92 @@
                 'status_id': status_id,
                 'uploaded_notes': '',
                 'uploaded_changes': '',
-                'release_notes': release_notes,
-                'release_changes': change_log,
+                'release_notes': '', 
+                'release_changes': '',
                 'preformatted': '1',
                 'submit': 'Submit/Refresh'
                 }
-        ok = self.fetch_url(url,
-                            urlencode(form),
-                            ok_status="Data Saved")[1]
+
+        # attempt to create temp files to store the release notes and change log
+        # if unable to do so or if it cannot completed the task fallback to old behavior
+        # and simply post the text of each as-is.
+        try:
+            release_notes_fp, release_notes_path = tempfile.mkstemp(prefix="rf-", text=True)
+            os.write(release_notes_fp, "%s\n" % release_notes)
+            os.close(release_notes_fp)
+            form['uploaded_notes'] = open(release_notes_path, "r")
+        except:
+            warn("could not create tempfile for release_notes, reverting to old implementation")
+            release_notes_path = ""
+            
+        try:
+            change_log_fp, change_log_path = tempfile.mkstemp(prefix="rf-", text=True)
+            os.write(change_log_fp, "%s\n" % change_log)
+            os.close(change_log_fp)
+            form['uploaded_changes'] = open(change_log_path, "r")        
+        except:
+            warn("could not create tempfile for change_log, reverting to old implementation")
+            change_log_path = ""
+            
+        if not change_log_path or not release_notes_path:
+            return self.edit_release_step1_fallback(group_id, package_id, release_id,
+                                                    name, release_notes, change_log, status)
+       
+        try:
+            ok = self.fetch_url(url,
+                                form, # encoded by MultipartPostHandler
+                                post_as_multipart=True,
+                                ok_status="Data Saved")[1]  
+        except:
+            ok = self.edit_release_step1_fallback(group_id, package_id, release_id,
+                                                  name, release_notes, change_log, status)
+        
+        # if the temp files were created, remove them now       
+        if release_notes_path:
+            try: os.remove(release_notes_path)
+            except: pass
+
+        if change_log_path:
+            try: os.remove(change_log_path)
+            except: pass
+            
         return ok
-        #if data.find("Data Saved") != -1: return True
-        #else: return False
 
 
+    def edit_release_step1_fallback(self, group_id, package_id, release_id,
+                                    name, release_notes, change_log, status='active'):        
+        url = EDIT_RELEASE_URL
+        if status.lower() == 'active': status_id = '1'
+        else: status_id = '3'
+
+        warn("using (post) fallback method... html entities will be munged")
+        
+        form = {'package_id': package_id,
+                'new_package_id': package_id,
+                'group_id': group_id,
+                'release_id': release_id,
+                'release_name': name,
+                'release_date': time.strftime("%Y-%m-%d"),
+                'step1': '1',
+                'status_id': status_id,
+                'uploaded_notes': '',
+                'uploaded_changes': '',
+                'release_notes': release_notes, 
+                'release_changes': change_log,
+                'preformatted': '1',
+                'submit': 'Submit/Refresh'
+                }
+
+        try:
+            ok = self.fetch_url(url,
+                                urlencode(form), 
+                                ok_status="Data Saved",
+                                retry=False)[1]
+        except:
+            ok = False
+        
+        return ok
+    
 
     def edit_release_step2(self, group_id, package_id, release_id, fileVOs):
         url = EDIT_RELEASE_URL
@@ -421,7 +525,7 @@
             error("Error selecting %s for inclusion", filename)
             #debug("dumping data: %s", data)  # remove me...
             ok = False
-        
+
         matches = GET_FILE_ID.findall(data)
         lookup = {}
         for match in matches:
@@ -432,7 +536,7 @@
 
     def edit_release_step3(self, group_id, package_id, release_id, fileVOs, lookup_dict):
         url = EDIT_RELEASE_URL
-        
+
         form = {'package_id': package_id,
                 'group_id': group_id,
                 'release_id': release_id,
@@ -446,8 +550,10 @@
             form['processor_id'] = fileVO.getProcessorId()
             form['type_id'] = fileVO.getFileTypeId()
             try:
-                #debug("filename: %s", fileVO.getFilename() )
-                #debug("dict: %s", str(lookup_dict))
+                # REMOVE THESE!!!!! for debugging purpose re: twb
+                #error("filename: %s", fileVO.getFilename() )
+                #error("dict: %s", str(lookup_dict))
+                ######
                 
                 form['file_id'] = lookup_dict[fileVO.getFilename()]
                 ok &= self.fetch_url(url, urlencode(form), ok_status="File Updated")[1]
@@ -510,29 +616,23 @@
                                        status)
 
 
-    def upload_files(self, parent, fileVOs):
-        ftp = FTP_()
 
+    def upload_files(self, parent, fileVOs):
         try:
-            ftp.connect(SF_FTP_SERVER)
-            ftp.getwelcome()
-
-            ftp.login("ftp", "releaseforge")
-            ftp.cwd(SF_FTP_DIRECTORY)
+            t = paramiko.Transport((SF_SFTP_SERVER, SF_SFTP_PORT))
+            t.start_client()  
+            t.auth_password(self.username, self.password)
+            sftp = paramiko.SFTPClient.from_transport(t)
+            sftp.chdir(SF_SFTP_DIRECTORY)
+            
             num_uploaded = 0
-
             for fileVO in fileVOs:
                 fullpath = fileVO.getFullPath()
                 filename = fileVO.getFilename()
                 debug("uploading %s (%s)", filename, fullpath)
                 progressMessageQueue.put("Uploading: %s" % filename)
-                f = open(fullpath, "rb")
-                
-                ftp.storbinary('STOR ' + filename,
-                               f,
-                               callback=self.update_ftp_progress)
-                f.close()
 
+                sftp.put(fullpath, filename, callback=self.update_sftp_progress)
                 num_uploaded += 1
                 if parent.isCancelled(): break
 
@@ -542,21 +642,22 @@
                 msg = "Successfully uploaded %d files" % num_uploaded
         except Exception, e:
             msg = str(e)
-            progressMessageQueue.put("FTP error: %s" % msg)
+            error(msg)
+            progressMessageQueue.put("SFTP error: %s" % msg)
         debug(msg)
         try:
-            ftp.close()
+            sftp.close()
         except: pass
 
-        ftpProgressQueue.clear()
-        ftpProgressQueue.put( (True, True) )
+        sftpProgressQueue.clear()
+        sftpProgressQueue.put( (True, True) )
         return msg
 
 
-    def update_ftp_progress(self, progress):
+    def update_sftp_progress(self, progress, total):
         #debug("progress: %s", progress)
-        ftpProgressQueue.clear()
-        ftpProgressQueue.put(progress)
+        sftpProgressQueue.clear()
+        sftpProgressQueue.put( (progress, total) )
 
 
     def submit_news(self, group_id, subject, message):
diff -Nru /tmp/user/2000/X5m6gbvF7f/releaseforge-1.1/ReleaseForge/version.py /tmp/user/2000/EZty4Ck08i/releaseforge-1.3/ReleaseForge/version.py
--- releaseforge-1.1/ReleaseForge/version.py	2006-06-15 06:01:54.000000000 -0400
+++ releaseforge-1.3/ReleaseForge/version.py	2008-07-07 12:17:32.000000000 -0400
@@ -1 +1 @@
-VERSION="1.1"
+VERSION="1.3"
diff -Nru /tmp/user/2000/X5m6gbvF7f/releaseforge-1.1/debian/changelog /tmp/user/2000/EZty4Ck08i/releaseforge-1.3/debian/changelog
--- releaseforge-1.1/debian/changelog	2008-08-02 13:50:52.000000000 -0400
+++ releaseforge-1.3/debian/changelog	2008-08-02 13:50:53.000000000 -0400
@@ -1,3 +1,14 @@
+releaseforge (1.3-1) unstable; urgency=low
+
+  * New upstream release. (Closes: #492933)
+    - Remove patches now included upstream:
+      + debian/patches/02_empty_set.dpatch
+      + debian/patches/03_new_login_form.dpatch
+    - Add dependency on python-paramiko (>= 1.7.4)
+  * Updated Standards-Version to 3.8.0 (no changes)
+
+ -- Roberto C. Sanchez <roberto@connexer.com>  Fri, 01 Aug 2008 23:53:10 -0400
+
 releaseforge (1.1-4) unstable; urgency=low
 
   * Update man page.
diff -Nru /tmp/user/2000/X5m6gbvF7f/releaseforge-1.1/debian/control /tmp/user/2000/EZty4Ck08i/releaseforge-1.3/debian/control
--- releaseforge-1.1/debian/control	2008-08-02 13:50:52.000000000 -0400
+++ releaseforge-1.3/debian/control	2008-08-02 13:50:53.000000000 -0400
@@ -5,11 +5,11 @@
 Homepage: http://releaseforge.sourceforge.net/
 Build-Depends: debhelper (>= 5.0.37.2), dpatch
 Build-Depends-Indep: python, python-dev, python-qt3 (>= 3.13), pyqt-tools (>= 3.14.1), python-support (>= 0.5.6)
-Standards-Version: 3.7.3
+Standards-Version: 3.8.0
 
 Package: releaseforge
 Architecture: all
-Depends: ${python:Depends}, python-qt3 (>= 3.13)
+Depends: ${python:Depends}, python-qt3 (>= 3.13), python-paramiko (>= 1.7.4)
 Description: alternative to SourceForge's File Release System (FRS)
  An open source utility designed for the administrators and
  release engineers of SourceForge projects. ReleaseForge allows
diff -Nru /tmp/user/2000/X5m6gbvF7f/releaseforge-1.1/debian/copyright /tmp/user/2000/EZty4Ck08i/releaseforge-1.3/debian/copyright
--- releaseforge-1.1/debian/copyright	2008-08-02 13:50:52.000000000 -0400
+++ releaseforge-1.3/debian/copyright	2008-08-02 13:50:53.000000000 -0400
@@ -3,7 +3,9 @@
 
 It was downloaded from http://releaseforge.sourceforge.net/
 
-Copyright Holder: Copyright (c) 2005 Phil Schwartz <phil_schwartz@users.sourceforge.net>
+Copyright Holder:
+
+Copyright (c) 2005 Phil Schwartz <phil_schwartz@users.sourceforge.net>
 
 License:
 
diff -Nru /tmp/user/2000/X5m6gbvF7f/releaseforge-1.1/debian/patches/00list /tmp/user/2000/EZty4Ck08i/releaseforge-1.3/debian/patches/00list
--- releaseforge-1.1/debian/patches/00list	2008-08-02 13:50:52.000000000 -0400
+++ releaseforge-1.3/debian/patches/00list	2008-08-02 13:50:53.000000000 -0400
@@ -1,3 +1 @@
 01_interpreter_patch.dpatch
-02_empty_set.dpatch
-03_new_login_form.dpatch
diff -Nru /tmp/user/2000/X5m6gbvF7f/releaseforge-1.1/debian/patches/02_empty_set.dpatch /tmp/user/2000/EZty4Ck08i/releaseforge-1.3/debian/patches/02_empty_set.dpatch
--- releaseforge-1.1/debian/patches/02_empty_set.dpatch	2008-08-02 13:50:52.000000000 -0400
+++ releaseforge-1.3/debian/patches/02_empty_set.dpatch	1969-12-31 19:00:00.000000000 -0500
@@ -1,25 +0,0 @@
-#! /bin/sh /usr/share/dpatch/dpatch-run
-## 02_empty_set.dpatch by  <roberto@connexer.com>
-##
-## All lines beginning with `## DP:' are a description of the patch.
-## DP: Fixes return of non-existent variable
-
-@DPATCH@
-
-diff -uNr releaseforge-1.1.orig/ReleaseForge/sfcomm.py releaseforge-1.1/ReleaseForge/sfcomm.py
---- releaseforge-1.1.orig/ReleaseForge/sfcomm.py	2006-06-15 05:49:44.000000000 -0400
-+++ releaseforge-1.1/ReleaseForge/sfcomm.py	2006-08-03 18:28:07.099087126 -0400
-@@ -169,12 +169,12 @@
-         cj.save(self.cookie_file, True, True) # ignore discard! ignore expires
-         
-         data = r.read()
-+        projects = set()
-         idx = data.find("Invalid Password or User Name")
-         if idx != -1:
-             debug("Could not login")
-             return projects
- 
--        projects = set()
-         #debug(data)
-         project_tuples = PROJECTS_FROM_MY_PROJECTS.findall(data)
-         for pt in project_tuples:
diff -Nru /tmp/user/2000/X5m6gbvF7f/releaseforge-1.1/debian/patches/03_new_login_form.dpatch /tmp/user/2000/EZty4Ck08i/releaseforge-1.3/debian/patches/03_new_login_form.dpatch
--- releaseforge-1.1/debian/patches/03_new_login_form.dpatch	2008-08-02 13:50:52.000000000 -0400
+++ releaseforge-1.3/debian/patches/03_new_login_form.dpatch	1969-12-31 19:00:00.000000000 -0500
@@ -1,24 +0,0 @@
-#! /bin/sh /usr/share/dpatch/dpatch-run
-## 03_new_login_form.dpatch by  <roberto@connexer.com>
-##
-## All lines beginning with `## DP:' are a description of the patch.
-## DP: New login form
-
-@DPATCH@
-
-Index: trunk/ReleaseForge/sfcomm.py
-===================================================================
---- trunk/ReleaseForge/sfcomm.py	(revision 761)
-+++ trunk/ReleaseForge/sfcomm.py	(working copy)
-@@ -143,9 +143,8 @@
-         debug("Attempting to login to SourceForge")
-         form = {'form_loginname': username,
-                 'form_pw': password,
--                'stay_in_ssl': '1',
--                'persistent_login': '1',
--                'login': 'Login With SSL'}
-+                'ssl_status': '1',
-+                'login': 'Login+in'}
-         url = LOGIN_URL
- 
-         if return_to: url += "?return_to=%s" % quote_plus(return_to)
diff -Nru /tmp/user/2000/X5m6gbvF7f/releaseforge-1.1/setup.cfg /tmp/user/2000/EZty4Ck08i/releaseforge-1.3/setup.cfg
--- releaseforge-1.1/setup.cfg	2006-06-15 06:01:18.000000000 -0400
+++ releaseforge-1.3/setup.cfg	2008-06-22 21:08:50.000000000 -0400
@@ -5,7 +5,10 @@
 distribution-name = Red Hat Linux
 requires = python >= 2.3 
 requires = PyQt >= 3.2
+requires = paramiko > 1.7.3
 build-requires = python-devel
-doc_files = README.txt LICENSE.txt CHANGELOG.txt
+#doc_files = README.txt LICENSE.txt CHANGELOG.txt
 #icon = images/releaseforge-icon.png
 install-script = install.sh
+[install]
+optimize = 1
diff -Nru /tmp/user/2000/X5m6gbvF7f/releaseforge-1.1/setup.py /tmp/user/2000/EZty4Ck08i/releaseforge-1.3/setup.py
--- releaseforge-1.1/setup.py	2006-03-31 23:21:15.000000000 -0500
+++ releaseforge-1.3/setup.py	2008-07-07 12:17:08.000000000 -0400
@@ -1,6 +1,7 @@
 #!/usr/bin/env python
 from ReleaseForge.version import VERSION
 from distutils.core import setup
+#from setuptools import setup 
 import os
 import os.path
 import sys
@@ -43,6 +44,7 @@
                   (libpath, glob('releaseforge.pro')),
                   (libpath, glob('logger.ini')),
                   (libpath, glob('README.txt')),
+                  (libpath, glob('RELEASE_NOTES.txt')),
                   (libpath, glob('CHANGELOG.txt')),
                   (libpath, glob('LICENSE.txt')),
 #                  (TRANSLATIONS_DIR, glob(os.path.join("translations", "*"))),

Attachment: signature.asc
Description: Digital signature


Reply to: