utils: handle permanent redirect for downloads
[beastbuild.git] / utils.py
1 ###########################################################
2 # taken from kde windows port emerge
3 ###########################################################
4
5 # this file contains some helper functions for emerge
6
7 # copyright:
8 # Holger Schroeder <holger [AT] holgis [DOT] net>
9 # Patrick Spendrin <ps_ml [AT] gmx [DOT] de>
10
11
12 import httplib
13 import ftplib
14 import os
15 import sys
16 import re
17 import urlparse
18 import shutil
19 import zipfile
20 import tarfile
21 import hashlib
22 import subprocess
23 import __builtin__
24 import imp
25 from Path import Path
26 from config import config
27 from buildutils import platform_is_win32, platform_is_unix, call_log
28
29 ### fetch functions
30
31 #FIXME: get this from somewhere else:
32 if platform_is_win32():
33   WGetExecutable = os.path.join( config.PREFIX.native(), "bin", "wget.exe" )
34 else:
35   WGetExecutable = os.path.join( config.PREFIX.native(), "bin", "wget" )
36
37
38 def getFiles( urls, destdir ):
39     debug( "getfiles called. urls: %s" % urls, 1 )
40     # make sure distfiles dir exists
41     if ( not os.path.exists( destdir ) ):
42         os.makedirs( destdir )
43
44     for url in urls.split():
45         #print "getfiles url:", url
46         if ( not getFile( url, destdir ) ):
47             return False
48
49     return True
50
51 def getFile( url, destdir ):
52     debug( "getFile called. url: %s" % url, 1 )
53     if url == "":
54         error( "fetch: no url given" )
55         return False
56
57
58     wgetpath = WGetExecutable             ### FIXME
59     if ( os.path.exists( wgetpath ) ):
60         return wgetFile( url, destdir )
61
62     scheme, host, path, params, qu, fr = urlparse.urlparse( url )
63
64
65     filename = os.path.basename( path )
66     debug( "%s\n%s\n%s\n%s" % ( scheme, host, path, filename ) )
67
68     if ( scheme == "http" ):
69         return getHttpFile( host, path, destdir, filename )
70     elif ( scheme == "ftp" ):
71         return getFtpFile( host, path, destdir, filename )
72     else:
73         error( "getFile: protocol not understood" )
74         return False
75
76 def wgetFile( url, destdir ):
77     compath = WGetExecutable
78     command = "%s -c -t 1 -P %s %s" % ( compath, destdir, url )
79     debug( "wgetfile called", 1 )
80     attempts = 1
81     if url.lower().startswith( "http://downloads.sourceforge.net" ):
82         debug( "Detected downloads.sourceforge.net... Trying three times." )
83         attempts=3
84
85     while( attempts > 0 ):
86         attempts -= 1
87         ret = os.system( command )
88         debug( "wget ret: %s" % ret )
89         if ret == True:
90             # success stop early.
91             break;
92
93     return ret
94
95 def getFtpFile( host, path, destdir, filename ):
96     # FIXME check return values here (implement useful error handling)...
97     debug( "FIXME getFtpFile called. %s %s" % ( host, path ), 1 )
98
99     outfile = open( os.path.join( destdir, filename ), "wb" )
100     ftp = ftplib.FTP( host )
101     ftp.login( "anonymous", "johndoe@nowhere.org" )
102     ftp.retrbinary( "RETR " + path, outfile.write )
103
104     outfile.close()
105     return True
106
107 def getHttpFile( host, path, destdir, filename ):
108     # FIXME check return values here (implement useful error handling)...
109     debug( "getHttpFile called. %s %s" % ( host, path ) , 1 )
110
111     conn = httplib.HTTPConnection( host )
112     conn.request( "GET", path )
113     r1 = conn.getresponse()
114     debug( "status: %s; reason: %s" % ( str( r1.status ), str( r1.reason ) ) )
115         
116     count = 0
117     # temp(302) and permanent(301) redirect
118     while (r1.status == 302) or (r1.status == 301):
119         if count > 10:
120             print "Redirect loop"
121             return False
122         count += 1
123         scheme, host, path, params, qu, fr = urlparse.urlparse( r1.getheader( "Location" ) )
124         debug( "Redirection: %s %s" % ( host, path ), 1 )
125         conn = httplib.HTTPConnection( host )
126         conn.request( "GET", path )
127         r1 = conn.getresponse()
128         debug( "status: %s; reason: %s" % ( str( r1.status ), str( r1.reason ) ) )
129     
130         
131     data = r1.read()
132
133     f = open( os.path.join( destdir, filename ), "wb" )
134     f.write( data )
135     f.close()
136     return True
137
138
139 ### unpack functions
140
141 def unpackFiles( downloaddir, filenames, workdir ):
142     cleanDirectory( workdir )
143
144     for filename in filenames:
145         debug( "unpacking this file: %s" % filename, 1 )
146         if ( not unpackFile( downloaddir, filename, workdir ) ):
147             return False
148
149     return True
150
151 def unpackFile( downloaddir, filename, workdir ):
152     ( shortname, ext ) = os.path.splitext( filename )
153     if ( ext == ".zip" ):
154         return unZip (os.path.join (downloaddir.native(), filename), workdir)
155     elif ( ext == ".tgz" ):
156         return unTar (downloaddir.join (Path (filename)), workdir)
157     elif ( ext == ".gz" or ext == ".bz2" or ext == ".lzma" ):
158         ( myshortname, myext ) = os.path.splitext( shortname )
159         if ( myext == ".tar" ):
160             return unTar (downloaddir.join (Path (filename)), workdir)
161         else:
162             error( "unpacking %s" % myext )
163             return False
164     elif ( ext == ".exe" ):
165         warning( "unpack ignoring exe file" )
166         return True
167     error( "dont know how to unpack this file: %s" % filename )
168     return False
169
170 def unTar( file, destdir ):
171     debug( "unTar called. file: %s, destdir: %s" % ( file, destdir ), 1 )
172     ( shortname, ext ) = os.path.splitext( file.unix() )
173
174     mode = "r"
175     if (ext == ".gz" or ext == ".tgz"):
176         mode = "r:gz"
177     elif (ext == ".bz2"):
178         mode = "r:bz2"
179
180     if (ext == ".lzma"):
181         cmd="xz -dc %s | tar xCf %s -" % (file.msys(), destdir.msys())
182         call_log ([ "sh", "-c", cmd ])
183         return True
184
185     tar = tarfile.open (file.native(), mode)
186
187     # FIXME how to handle errors here ?
188     for foo in tar:
189         tar.extract (foo, destdir.native())
190
191     return True
192
193 def unZip (file, destdir):
194     debug ("unZip called: file %s to destination %s" % (file, destdir.native()), 1)
195
196     if not os.path.exists (destdir.native()):
197         os.makedirs (destdir.native())
198
199     zip = zipfile.ZipFile (file)
200
201     for i, name in enumerate( zip.namelist() ):
202         if not name.endswith( '/' ):
203             dirname = os.path.join (destdir.native(), os.path.dirname (name))
204
205             if not os.path.exists (dirname):
206                 os.makedirs (dirname)
207
208             outfile = open (os.path.join (destdir.native(), name), 'wb')
209             outfile.write (zip.read (name))
210             outfile.flush()
211             outfile.close()
212
213     return True
214
215 ### debug functions
216 def info( message ):
217     if verbose() > 0:
218         print "beastbuild info: %s" % message
219     return True
220
221 def debug( message, level=0 ):
222     if verbose() > level:
223         print "beastbuild debug:", message
224     return True
225
226 def warning( message ):
227     if verbose() > 0:
228         print "beastbuild warning: %s" % message
229     return True
230
231 def debug_line( level=0 ):
232     if verbose() > level:
233         print "_" * 80
234
235 def error( message ):
236     if verbose() > 0:
237         print >> sys.stderr, "beastbuild error: %s" % message
238     return False
239
240 def die( message ):
241     print >> sys.stderr, "beastbuild fatal error: %s" % message
242     exit( 1 )
243
244 def verbose():
245     verb = os.getenv( "BEASTBUILD_VERBOSE" )
246     if ( not verb == None and verb.isdigit() and int(verb) > 0 ):
247         return int( verb )
248     else:
249         return 0
250
251 ### create directory
252 def makeDirs (path):
253     if os.path.exists (path.native()):
254         if os.path.isdir (path.native()):
255             return
256     else:
257         try:
258             os.makedirs (path.native())
259             if os.path.exists (path.native()) and os.path.isdir (path.native()):
260                 return
261         except:
262             pass
263     die ("Can't create directory '" + path.native() + "'")