instrumentation: add next-share/
[cs-p2p-next.git] / instrumentation / next-share / BaseLib / Core / osutils.py
1 # Written by Arno Bakker, ABC authors
2 # see LICENSE.txt for license information
3 """
4 OS-independent utility functions
5
6 get_home_dir()      : Returns CSIDL_APPDATA i.e. App data directory on win32
7 get_picture_dir()
8 getfreespace(path)
9 """
10     
11 #
12 # Multiple methods for getting free diskspace
13 #
14 import sys
15 import os
16 import time
17 import binascii
18
19 if sys.platform == "win32":
20     try:
21         from win32com.shell import shell
22         def get_home_dir():
23             # http://www.mvps.org/access/api/api0054.htm
24             # CSIDL_PROFILE = &H28
25             # C:\Documents and Settings\username
26             return shell.SHGetSpecialFolderPath(0, 0x28)
27
28         def get_appstate_dir():
29             # http://www.mvps.org/access/api/api0054.htm
30             # CSIDL_APPDATA = &H1A
31             # C:\Documents and Settings\username\Application Data
32             return shell.SHGetSpecialFolderPath(0, 0x1a)
33
34         def get_picture_dir():
35             # http://www.mvps.org/access/api/api0054.htm
36             # CSIDL_MYPICTURES = &H27
37             # C:\Documents and Settings\username\My Documents\My Pictures
38             return shell.SHGetSpecialFolderPath(0, 0x27)
39
40         def get_desktop_dir():
41             # http://www.mvps.org/access/api/api0054.htm
42             # CSIDL_DESKTOPDIRECTORY = &H10
43             # C:\Documents and Settings\username\Desktop
44             return shell.SHGetSpecialFolderPath(0, 0x10)
45
46     except ImportError:
47         def get_home_dir():
48             try:
49                 # when there are special unicode characters in the username,
50                 # the following will fail on python 2.4, 2.5, 2.x this will
51                 # always succeed on python 3.x
52                 return os.path.expanduser(u"~")
53             except Exception, unicode_error:
54                 pass
55
56             # non-unicode home
57             home = os.path.expanduser("~")
58             head, tail = os.path.split(home)
59
60             dirs = os.listdir(head)
61             udirs = os.listdir(unicode(head))
62
63             # the character set may be different, but the string length is
64             # still the same
65             islen = lambda dir: len(dir) == len(tail)
66             dirs = filter(islen, dirs)
67             udirs = filter(islen, udirs)
68             if len(dirs) == 1 and len(udirs) == 1:
69                 return os.path.join(head, udirs[0])
70
71             # remove all dirs that are equal in unicode and non-unicode. we
72             # know that we don't need these dirs because the initial
73             # expandusers would not have failed on them
74             for dir in dirs[:]:
75                 if dir in udirs:
76                     dirs.remove(dir)
77                     udirs.remove(dir)
78             if len(dirs) == 1 and len(udirs) == 1:
79                 return os.path.join(head, udirs[0])
80
81             # assume that the user has write access in her own
82             # directory. therefore we can filter out any non-writable
83             # directories
84             writable_udir = [udir for udir in udirs if os.access(udir, os.W_OK)]
85             if len(writable_udir) == 1:
86                 return os.path.join(head, writable_udir[0])
87
88             # fallback: assume that the order of entries in dirs is the same
89             # as in udirs
90             for dir, udir in zip(dirs, udirs):
91                 if dir == tail:
92                     return os.path.join(head, udir)
93
94             # failure
95             raise unicode_error
96
97         def get_appstate_dir():
98             homedir = get_home_dir()
99             # 5 = XP, 6 = Vista
100             # [E1101] Module 'sys' has no 'getwindowsversion' member
101             # pylint: disable-msg=E1101
102             winversion = sys.getwindowsversion()
103             # pylint: enable-msg=E1101
104             if winversion[0] == 6:
105                 appdir = os.path.join(homedir,u"AppData",u"Roaming")
106             else:
107                 appdir = os.path.join(homedir,u"Application Data")
108             return appdir
109
110         def get_picture_dir():
111             return get_home_dir()
112
113         def get_desktop_dir():
114             home = get_home_dir()
115             return os.path.join(home,u"Desktop")
116             
117 else:
118     # linux or darwin (mac)
119     def get_home_dir():
120         return os.path.expanduser(u"~")
121
122     def get_appstate_dir():
123         return get_home_dir()
124
125     def get_picture_dir():
126         return get_desktop_dir()
127
128     def get_desktop_dir():
129         home = get_home_dir()
130         desktop = os.path.join(home, "Desktop")
131         if os.path.exists(desktop):
132             return desktop
133         else:
134             return home
135
136 if sys.version.startswith("2.4"):
137     os.SEEK_SET = 0
138     os.SEEK_CUR = 1
139     os.SEEK_END = 2
140
141 try:
142     # Unix
143     from os import statvfs
144     import statvfs
145     def getfreespace(path):
146         s = os.statvfs(path.encode("utf-8"))
147         size = s[statvfs.F_BAVAIL] * long(s[statvfs.F_BSIZE])
148         return size
149 except:
150     if (sys.platform == 'win32'):
151         try:
152             # Windows if win32all extensions are installed
153             import win32file
154             try:
155                 # Win95 OSR2 and up
156                 # Arno: this code was totally broken as the method returns
157                 # a list of values indicating 1. free space for the user,
158                 # 2. total space for the user and 3. total free space, so
159                 # not a single value.
160                 win32file.GetDiskFreeSpaceEx(".")
161                 def getfreespace(path):
162                     # Boudewijn: the win32file module is NOT unicode
163                     # safe! We will try directories further up the
164                     # directory tree in the hopes of getting a path on
165                     # the same disk without the unicode...
166                     while True:
167                         try:
168                             return win32file.GetDiskFreeSpaceEx(path)[0]
169                         except:
170                             path = os.path.split(path)[0]
171                             if not path:
172                                 raise
173             except:                
174                 # Original Win95
175                 # (2GB limit on partition size, so this should be
176                 #  accurate except for mapped network drives)
177                 # Arno: see http://aspn.activestate.com/ASPN/docs/ActivePython/2.4/pywin32/win32file__GetDiskFreeSpace_meth.html
178                 def getfreespace(path):
179                     [spc, bps, nfc, tnc] = win32file.GetDiskFreeSpace(path)
180                     return long(nfc) * long(spc) * long(bps)
181                     
182         except ImportError:
183             # Windows if win32all extensions aren't installed
184             # (parse the output from the dir command)
185             def getfreespace(path):
186                 try:
187                     mystdin, mystdout = os.popen2(u"dir " + u"\"" + path + u"\"")
188                     
189                     sizestring = "0"
190                 
191                     for line in mystdout:
192                         line = line.strip()
193                         # Arno: FIXME: this won't work on non-English Windows, as reported by the IRT
194                         index = line.rfind("bytes free")
195                         if index > -1 and line[index:] == "bytes free":
196                             parts = line.split(" ")
197                             if len(parts) > 3:
198                                 part = parts[-3]
199                                 part = part.replace(",", "")
200                                 sizestring = part
201                                 break
202
203                     size = long(sizestring)                    
204                     
205                     if size == 0L:
206                         print >>sys.stderr,"getfreespace: can't determine freespace of ",path
207                         for line in mystdout:
208                             print >>sys.stderr,line
209                             
210                         size = 2**80L
211                 except:
212                     # If in doubt, just return something really large
213                     # (1 yottabyte)
214                     size = 2**80L
215                 
216                 return size
217     else:
218         # Any other cases
219         # TODO: support for Mac? (will statvfs work with OS X?)
220         def getfreespace(path):
221             # If in doubt, just return something really large
222             # (1 yottabyte)
223             return 2**80L
224
225
226 invalidwinfilenamechars = ''
227 for i in range(32):
228     invalidwinfilenamechars += chr(i)
229 invalidwinfilenamechars += '"*/:<>?\\|'
230 invalidlinuxfilenamechars = '/'
231
232 def fix_filebasename(name, unit=False, maxlen=255):
233     """ Check if str is a valid Windows file name (or unit name if unit is true)
234      * If the filename isn't valid: returns a corrected name
235      * If the filename is valid: returns the filename
236     """
237     if unit and (len(name) != 2 or name[1] != ':'):
238         return 'c:'
239     if not name or name == '.' or name == '..':
240         return '_'
241     
242     if unit:
243         name = name[0]
244     fixed = False
245     if len(name) > maxlen:
246         name = name[:maxlen]
247         fixed = True
248
249     fixedname = ''
250     spaces = 0
251     for c in name:
252         if sys.platform.startswith('win'):
253             invalidchars = invalidwinfilenamechars
254         else:
255             invalidchars = invalidlinuxfilenamechars
256              
257         if c in invalidchars:
258             fixedname += '_'
259             fixed = True
260         else:
261             fixedname += c
262             if c == ' ':
263                 spaces += 1
264     
265     file_dir, basename = os.path.split(fixedname)
266     while file_dir != '':
267         fixedname = basename
268         file_dir, basename = os.path.split(fixedname)
269         fixed = True
270     
271     if fixedname == '':
272         fixedname = '_'
273         fixed = True
274         
275     if fixed:
276         return last_minute_filename_clean(fixedname)
277     elif spaces == len(name):
278         # contains only spaces
279         return '_'
280     else:
281         return last_minute_filename_clean(name)
282     
283 def last_minute_filename_clean(name):
284     s = name.strip() # Arno: remove initial or ending space
285     if sys.platform == 'win32' and s.endswith('..'):
286         s = s[:-2]
287     return s
288
289
290 def get_readable_torrent_name(infohash, raw_filename):
291     # return name__infohash.torrent
292     hex_infohash = binascii.hexlify(infohash)
293     suffix = '__' + hex_infohash + '.torrent'
294     save_name = ' ' + fix_filebasename(raw_filename, maxlen=254-len(suffix)) + suffix
295     # use a space ahead to distinguish from previous collected torrents
296     return save_name
297
298
299 if sys.platform == "win32":
300     import win32pdh
301     
302     def getcpuload():
303         """ Returns total CPU usage as fraction (0..1).
304         Warning: side-effect: sleeps for 0.1 second to do diff """
305         #mempath = win32pdh.MakeCounterPath((None, "Memory", None, None, -1, "Available MBytes"))
306         cpupath = win32pdh.MakeCounterPath((None, "Processor", "_Total", None, -1, "% Processor Time"))
307         query = win32pdh.OpenQuery(None, 0)
308         counter = win32pdh.AddCounter(query, cpupath, 0)
309         
310         win32pdh.CollectQueryData(query)
311         # Collect must be called twice for CPU, see http://support.microsoft.com/kb/262938
312         time.sleep(0.1)
313         win32pdh.CollectQueryData(query)
314             
315         status, value = win32pdh.GetFormattedCounterValue(counter,win32pdh.PDH_FMT_LONG)
316              
317         return float(value)/100.0
318     
319 elif sys.platform == "linux2":
320     def read_proc_stat():
321         """ Read idle and total CPU time counters from /proc/stat, see
322         man proc """
323         f = open("/proc/stat","rb")
324         try:
325             while True:
326                 line = f.readline()
327                 if len(line) == 0:
328                     break
329                 if line.startswith("cpu "): # note space
330                     words = line.split()
331                     total = 0
332                     for i in range(1,5):
333                         total += int(words[i])
334                     idle = int(words[4])
335                     return (total,idle)
336         finally:
337             f.close()
338     
339     
340     def getcpuload():
341         """ Returns total CPU usage as fraction (0..1).
342         Warning: side-effect: sleeps for 0.1 second to do diff """
343         (total1,idle1) = read_proc_stat()
344         time.sleep(0.1)
345         (total2,idle2) = read_proc_stat()
346         total = total2 - total1
347         idle = idle2 - idle1
348         return 1.0-(float(idle))/float(total)
349 else:
350     # Mac
351     def getupload():
352         raise ValueError("Not yet implemented")