platform.py 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029
  1. # The contents of this file are subject to the BitTorrent Open Source License
  2. # Version 1.1 (the License). You may not copy or use this file, in either
  3. # source code or executable form, except in compliance with the License. You
  4. # may obtain a copy of the License at http://www.bittorrent.com/license/.
  5. #
  6. # Software distributed under the License is distributed on an AS IS basis,
  7. # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  8. # for the specific language governing rights and limitations under the
  9. # License.
  10. # Written by Greg Hazel, Matt Chisholm, Uoti Urpala, and David Harrison
  11. # This module is strictly for cross platform compatibility items and
  12. # should not import anything from other BitTorrent modules.
  13. import os
  14. import sys
  15. import locale
  16. import shutil
  17. import socket
  18. import gettext
  19. import tarfile
  20. import traceback
  21. from BTL.platform import efs, efs2, get_filesystem_encoding
  22. # NOTE: intentionally appears in the file before importing anything
  23. # from BitTorrent because it is called when setting --use_factory_defaults.
  24. def get_temp_dir():
  25. shellvars = ['${TMP}', '${TEMP}']
  26. dir_root = get_dir_root(shellvars, default_to_home=False)
  27. #this method is preferred to the envvars
  28. if os.name == 'nt':
  29. try_dir_root = win32api.GetTempPath()
  30. if try_dir_root is not None:
  31. dir_root = try_dir_root
  32. if dir_root is None:
  33. try_dir_root = None
  34. if os.name == 'nt':
  35. # this should basically never happen. GetTempPath always returns something
  36. try_dir_root = r'C:\WINDOWS\Temp'
  37. elif os.name == 'posix':
  38. try_dir_root = '/tmp'
  39. if (try_dir_root is not None and
  40. os.path.isdir(try_dir_root) and
  41. os.access(try_dir_root, os.R_OK|os.W_OK)):
  42. dir_root = try_dir_root
  43. return dir_root
  44. MAX_DIR = 5
  45. _tmp_subdir = None
  46. # NOTE: intentionally appears in the file before importing anything
  47. # from BitTorrent because it is called when setting --use_factory_defaults.
  48. def get_temp_subdir():
  49. """Creates a unique subdirectory of the platform temp directory.
  50. This revolves between MAX_DIR directory names deleting the oldest
  51. whenever MAX_DIR exist. Upon return the number of temporary
  52. subdirectories should never exceed MAX_DIR-1. If one has already
  53. been created for this execution, this returns that subdirectory.
  54. @return the absolute path of the created temporary directory.
  55. """
  56. global _tmp_subdir
  57. if _tmp_subdir is not None:
  58. return _tmp_subdir
  59. tmp = get_temp_dir()
  60. target = None # holds the name of the directory that will be made.
  61. for i in xrange(MAX_DIR):
  62. subdir = efs2(u"BitTorrentTemp%d" % i)
  63. path = os.path.join(tmp, subdir)
  64. if not os.path.exists(path):
  65. target = path
  66. break
  67. # subdir should not in normal behavior be None. It can occur if something
  68. # prevented a directory from being removed on a previous call or if MAX_DIR
  69. # is changed.
  70. if target is None:
  71. subdir = efs2(u"BitTorrentTemp0")
  72. path = os.path.join(tmp, subdir)
  73. shutil.rmtree( path, ignore_errors = True )
  74. target = path
  75. i = 0
  76. # create the temp dir.
  77. os.mkdir(target)
  78. # delete the oldest directory.
  79. oldest_i = ( i + 1 ) % MAX_DIR
  80. oldest_subdir = efs2(u"BitTorrentTemp%d" % oldest_i)
  81. oldest_path = os.path.join(tmp, oldest_subdir)
  82. if os.path.exists( oldest_path ):
  83. shutil.rmtree( oldest_path, ignore_errors = True )
  84. _tmp_subdir = target
  85. return target
  86. _config_dir = None
  87. def set_config_dir(dir):
  88. """Set the root directory for configuration information."""
  89. global _config_dir
  90. # Normally we won't set it this way. This is called if the
  91. # --use_factory_defaults command-line option is specfied. By changing
  92. # the config directory early in the initialization we can guarantee
  93. # that the system acts like a fresh install.
  94. _config_dir = dir
  95. # Set configuration directory before importing any other BitTorrent modules.
  96. if "--use_factory_defaults" in sys.argv or "-u" in sys.argv:
  97. temp_dir = get_temp_subdir()
  98. set_config_dir(temp_dir)
  99. import BitTorrent.zurllib as urllib
  100. from BTL import language
  101. from BTL.sparse_set import SparseSet
  102. from BTL.defer import ThreadedDeferred
  103. from BTL.platform import app_name, get_module_filename, is_frozen_exe, get_shell_dir
  104. from BitTorrent import version
  105. if os.name == 'nt':
  106. import pywintypes
  107. import _winreg
  108. #import BTL.likewin32api as win32api
  109. import win32api
  110. import win32file
  111. from win32com.shell import shellcon
  112. import win32con
  113. from twisted.python.shortcut import Shortcut
  114. import ctypes
  115. import struct
  116. FILE_ATTRIBUTE_SPARSE_FILE = 0x00000200
  117. FILE_SUPPORTS_SPARSE_FILES = 0x00000040
  118. FSCTL_QUERY_ALLOCATED_RANGES = 0x000940CF
  119. elif os.name == 'posix' and os.uname()[0] == 'Darwin':
  120. has_pyobjc = False
  121. try:
  122. from Foundation import NSBundle
  123. has_pyobjc = True
  124. except ImportError:
  125. pass
  126. try:
  127. import statvfs
  128. except ImportError:
  129. statvfs = None
  130. def get_dir_root(shellvars, default_to_home=True):
  131. def check_sysvars(x):
  132. y = os.path.expandvars(x)
  133. if y != x and os.path.isdir(y):
  134. return y
  135. return None
  136. dir_root = None
  137. for d in shellvars:
  138. dir_root = check_sysvars(d)
  139. if dir_root is not None:
  140. break
  141. else:
  142. if default_to_home:
  143. dir_root = os.path.expanduser('~')
  144. if dir_root == '~' or not os.path.isdir(dir_root):
  145. dir_root = None
  146. if get_filesystem_encoding() == None:
  147. try:
  148. dir_root = dir_root.decode(sys.getfilesystemencoding())
  149. except:
  150. try:
  151. dir_root = dir_root.decode('utf8')
  152. except:
  153. pass
  154. return dir_root
  155. def get_old_dot_dir():
  156. return os.path.join(get_config_dir(), efs2(u'.bittorrent'))
  157. def get_dot_dir():
  158. # So called because on Unix platforms (but not OS X) this returns ~/.bittorrent.
  159. dot_dir = get_old_dot_dir()
  160. new_dot_dir = None
  161. if sys.platform == 'darwin':
  162. new_dot_dir = os.path.join(get_config_dir(), 'Library', 'Application Support', app_name)
  163. elif os.name == 'nt':
  164. new_dot_dir = os.path.join(get_config_dir(), app_name)
  165. if new_dot_dir:
  166. if os.path.exists(dot_dir):
  167. if os.path.exists(new_dot_dir):
  168. count = 0
  169. for root, dirs, files in os.walk(new_dot_dir):
  170. count = len(dirs) + len(files)
  171. break
  172. if count == 0:
  173. shutil.rmtree(new_dot_dir)
  174. shutil.move(dot_dir, new_dot_dir)
  175. else:
  176. shutil.move(dot_dir, new_dot_dir)
  177. dot_dir = new_dot_dir
  178. return dot_dir
  179. old_broken_config_subencoding = 'utf8'
  180. try:
  181. old_broken_config_subencoding = sys.getfilesystemencoding()
  182. except:
  183. pass
  184. os_name = os.name
  185. os_version = None
  186. if os_name == 'nt':
  187. wh = {(1, 4, 0): "95",
  188. (1, 4, 10): "98",
  189. (1, 4, 90): "ME",
  190. (2, 4, 0): "NT",
  191. (2, 5, 0): "2000",
  192. (2, 5, 1): "XP" ,
  193. (2, 5, 2): "2003",
  194. (2, 6, 0): "Vista",
  195. }
  196. class OSVERSIONINFOEX(ctypes.Structure):
  197. _fields_ = [("dwOSVersionInfoSize", ctypes.c_ulong),
  198. ("dwMajorVersion", ctypes.c_ulong),
  199. ("dwMinorVersion", ctypes.c_ulong),
  200. ("dwBuildNumber", ctypes.c_ulong),
  201. ("dwPlatformId", ctypes.c_ulong),
  202. ("szCSDVersion", ctypes.c_char * 128),
  203. ("wServicePackMajor", ctypes.c_ushort),
  204. ("wServicePackMinor", ctypes.c_ushort),
  205. ("wSuiteMask", ctypes.c_ushort),
  206. ("wProductType", ctypes.c_byte),
  207. ("wReserved", ctypes.c_byte),
  208. ]
  209. class OSVERSIONINFO(ctypes.Structure):
  210. _fields_ = [("dwOSVersionInfoSize", ctypes.c_ulong),
  211. ("dwMajorVersion", ctypes.c_ulong),
  212. ("dwMinorVersion", ctypes.c_ulong),
  213. ("dwBuildNumber", ctypes.c_ulong),
  214. ("dwPlatformId", ctypes.c_ulong),
  215. ("szCSDVersion", ctypes.c_char * 128),
  216. ]
  217. o = OSVERSIONINFOEX()
  218. o.dwOSVersionInfoSize = 156 # sizeof(OSVERSIONINFOEX)
  219. r = ctypes.windll.kernel32.GetVersionExA(ctypes.byref(o))
  220. if r:
  221. win_version_num = (o.dwPlatformId, o.dwMajorVersion, o.dwMinorVersion,
  222. o.wServicePackMajor, o.wServicePackMinor, o.dwBuildNumber)
  223. else:
  224. o = OSVERSIONINFOEX()
  225. o.dwOSVersionInfoSize = 148 # sizeof(OSVERSIONINFO)
  226. r = ctypes.windll.kernel32.GetVersionExA(ctypes.byref(o))
  227. win_version_num = (o.dwPlatformId, o.dwMajorVersion, o.dwMinorVersion,
  228. 0, 0, o.dwBuildNumber)
  229. wk = (o.dwPlatformId, o.dwMajorVersion, o.dwMinorVersion)
  230. if wh.has_key(wk):
  231. os_version = wh[wk]
  232. else:
  233. os_version = wh[max(wh.keys())]
  234. sys.stderr.write("Couldn't identify windows version: wk:%s, %s, "
  235. "assuming '%s'\n" % (str(wk),
  236. str(win_version_num),
  237. os_version))
  238. del wh, wk
  239. elif os_name == 'posix':
  240. os_version = os.uname()[0]
  241. app_root = os.path.split(get_module_filename())[0]
  242. doc_root = app_root
  243. osx = False
  244. if os.name == 'posix':
  245. if os.uname()[0] == "Darwin":
  246. doc_root = app_root = app_root.encode('utf8')
  247. if has_pyobjc:
  248. doc_root = NSBundle.mainBundle().resourcePath()
  249. osx = True
  250. def calc_unix_dirs():
  251. appdir = '%s-%s' % (app_name, version)
  252. ip = os.path.join(efs2(u'share'), efs2(u'pixmaps'), appdir)
  253. dp = os.path.join(efs2(u'share'), efs2(u'doc'), appdir)
  254. lp = os.path.join(efs2(u'share'), efs2(u'locale'))
  255. return ip, dp, lp
  256. def no_really_makedirs(path):
  257. # the deal here is, directories like "C:\" exist but can not be created
  258. # (access denied). We check for the exception anyway because of the race
  259. # condition.
  260. if not os.path.exists(path):
  261. try:
  262. os.makedirs(path)
  263. except OSError, e:
  264. if e.errno != 17: # already exists
  265. raise
  266. def get_config_dir():
  267. """a cross-platform way to get user's config directory.
  268. """
  269. if _config_dir is not None:
  270. return _config_dir
  271. shellvars = ['${APPDATA}', '${HOME}', '${USERPROFILE}']
  272. dir_root = get_dir_root(shellvars)
  273. if dir_root is None and os.name == 'nt':
  274. app_dir = get_shell_dir(shellcon.CSIDL_APPDATA)
  275. if app_dir is not None:
  276. dir_root = app_dir
  277. if dir_root is None and os.name == 'nt':
  278. tmp_dir_root = os.path.split(sys.executable)[0]
  279. if os.access(tmp_dir_root, os.R_OK|os.W_OK):
  280. dir_root = tmp_dir_root
  281. return dir_root
  282. # For string literal subdirectories, starting with unicode and then
  283. # converting to filesystem encoding may not always be necessary, but it seems
  284. # safer to do so. --Dave
  285. image_root = os.path.join(app_root, efs2(u'images'))
  286. locale_root = os.path.join(get_dot_dir(), efs2(u'locale'))
  287. no_really_makedirs(locale_root)
  288. plugin_path = []
  289. internal_plugin = os.path.join(app_root, efs2(u'BitTorrent'),
  290. efs2(u'Plugins'))
  291. local_plugin = os.path.join(get_dot_dir(), efs2(u'Plugins'))
  292. if os.access(local_plugin, os.F_OK):
  293. plugin_path.append(local_plugin)
  294. if os.access(internal_plugin, os.F_OK):
  295. plugin_path.append(internal_plugin)
  296. if not os.access(image_root, os.F_OK) or not os.access(locale_root, os.F_OK):
  297. # we guess that probably we are installed on *nix in this case
  298. # (I have no idea whether this is right or not -- matt)
  299. if app_root[-4:] == '/bin':
  300. # yep, installed on *nix
  301. installed_prefix = app_root[:-4]
  302. image_root, doc_root, locale_root = map(
  303. lambda p: os.path.join(installed_prefix, p), calc_unix_dirs()
  304. )
  305. systemwide_plugin = os.path.join(installed_prefix, efs2(u'lib'),
  306. efs2(u'BitTorrent'))
  307. if os.access(systemwide_plugin, os.F_OK):
  308. plugin_path.append(systemwide_plugin)
  309. if os.name == 'nt':
  310. def GetDiskFreeSpaceEx(s):
  311. if isinstance(s, unicode):
  312. GDFS = ctypes.windll.kernel32.GetDiskFreeSpaceExW
  313. else:
  314. GDFS = ctypes.windll.kernel32.GetDiskFreeSpaceExA
  315. FreeBytesAvailable = ctypes.c_ulonglong(0)
  316. TotalNumberOfBytes = ctypes.c_ulonglong(0)
  317. TotalNumberOfFreeBytes = ctypes.c_ulonglong(0)
  318. r = GDFS(s,
  319. ctypes.pointer(FreeBytesAvailable),
  320. ctypes.pointer(TotalNumberOfBytes),
  321. ctypes.pointer(TotalNumberOfFreeBytes))
  322. return FreeBytesAvailable.value, TotalNumberOfBytes.value, TotalNumberOfFreeBytes.value
  323. def get_free_space(path):
  324. # optimistic if we can't tell
  325. free_to_user = 2**64
  326. path, file = os.path.split(path)
  327. if os.name == 'nt':
  328. while not os.path.exists(path):
  329. path, top = os.path.split(path)
  330. free_to_user, total, total_free = GetDiskFreeSpaceEx(path)
  331. elif hasattr(os, "statvfs") and statvfs:
  332. s = os.statvfs(path)
  333. free_to_user = s[statvfs.F_BAVAIL] * long(s[statvfs.F_BSIZE])
  334. return free_to_user
  335. def get_sparse_files_support(path):
  336. supported = False
  337. if os.name == 'nt':
  338. drive, path = os.path.splitdrive(os.path.abspath(path))
  339. if len(drive) > 0: # might be a network path
  340. if drive[-1] != '\\':
  341. drive += '\\'
  342. volumename, serialnumber, maxpath, fsflags, fs_name = win32api.GetVolumeInformation(drive)
  343. if fsflags & FILE_SUPPORTS_SPARSE_FILES:
  344. supported = True
  345. return supported
  346. # is there a linux max path?
  347. def is_path_too_long(path):
  348. if os.name == 'nt':
  349. if len(path) > win32con.MAX_PATH:
  350. return True
  351. return False
  352. def is_sparse(path):
  353. supported = get_sparse_files_support(path)
  354. if not supported:
  355. return False
  356. if os.name == 'nt':
  357. return bool(win32file.GetFileAttributes(path) & FILE_ATTRIBUTE_SPARSE_FILE)
  358. return False
  359. def get_allocated_regions(path, f=None, begin=0, length=None):
  360. supported = get_sparse_files_support(path)
  361. if not supported:
  362. return
  363. if os.name == 'nt':
  364. if not os.path.exists(path):
  365. return False
  366. if f is None:
  367. f = file(path, 'r')
  368. handle = win32file._get_osfhandle(f.fileno())
  369. if length is None:
  370. length = os.path.getsize(path) - begin
  371. a = SparseSet()
  372. interval = 10000000
  373. i = begin
  374. end = begin + length
  375. while i < end:
  376. d = struct.pack("<QQ", i, interval)
  377. try:
  378. r = win32file.DeviceIoControl(handle, FSCTL_QUERY_ALLOCATED_RANGES,
  379. d, interval, None)
  380. except pywintypes.error, e:
  381. # I've seen:
  382. # error: (1784, 'DeviceIoControl', 'The supplied user buffer is not valid for the requested operation.')
  383. return
  384. for c in xrange(0, len(r), 16):
  385. qq = struct.unpack("<QQ", r[c:c+16])
  386. b = qq[0]
  387. e = b + qq[1]
  388. a.add(b, e)
  389. i += interval
  390. return a
  391. return
  392. def get_max_filesize(path):
  393. fs_name = None
  394. # optimistic if we can't tell
  395. max_filesize = 2**64
  396. if os.name == 'nt':
  397. drive, path = os.path.splitdrive(os.path.abspath(path))
  398. if len(drive) > 0: # might be a network path
  399. if drive[-1] != '\\':
  400. drive += '\\'
  401. volumename, serialnumber, maxpath, fsflags, fs_name = win32api.GetVolumeInformation(drive)
  402. if fs_name == "FAT32":
  403. max_filesize = 2**32 - 1
  404. elif (fs_name == "FAT" or
  405. fs_name == "FAT16"):
  406. # information on this varies, so I chose the description from
  407. # MS: http://support.microsoft.com/kb/q118335/
  408. # which happens to also be the most conservative.
  409. max_clusters = 2**16 - 11
  410. max_cluster_size = 2**15
  411. max_filesize = max_clusters * max_cluster_size
  412. else:
  413. path = os.path.realpath(path)
  414. # not implemented yet
  415. #fsname = crawl_path_for_mount_entry(path)
  416. return fs_name, max_filesize
  417. def get_torrents_dir():
  418. return os.path.join(get_dot_dir(), efs2(u'torrents'))
  419. def get_nebula_file():
  420. return os.path.join(get_dot_dir(), efs2(u'nebula'))
  421. def get_home_dir():
  422. shellvars = ['${HOME}', '${USERPROFILE}']
  423. dir_root = get_dir_root(shellvars)
  424. if (dir_root is None) and (os.name == 'nt'):
  425. dir = get_shell_dir(shellcon.CSIDL_PROFILE)
  426. if dir is None:
  427. # there's no clear best fallback here
  428. # MS discourages you from writing directly in the home dir,
  429. # and sometimes (i.e. win98) there isn't one
  430. dir = get_shell_dir(shellcon.CSIDL_DESKTOPDIRECTORY)
  431. dir_root = dir
  432. return dir_root
  433. def get_local_data_dir():
  434. if os.name == 'nt':
  435. # this results in paths that are too long
  436. # 86 characters: 'C:\Documents and Settings\Some Guy\Local Settings\Application Data\BitTorrent\incoming'
  437. #return os.path.join(get_shell_dir(shellcon.CSIDL_LOCAL_APPDATA), app_name)
  438. # I'm even a little nervous about this one
  439. return get_dot_dir()
  440. else:
  441. # BUG: there might be a better place to save incomplete files in under OSX
  442. return get_dot_dir()
  443. def get_old_incomplete_data_dir():
  444. incomplete = efs2(u'incomplete')
  445. return os.path.join(get_old_dot_dir(), incomplete)
  446. def get_incomplete_data_dir():
  447. # 'incomplete' is a directory name and should not be localized
  448. incomplete = efs2(u'incomplete')
  449. return os.path.join(get_local_data_dir(), incomplete)
  450. def get_save_dir():
  451. dirname = u'%s Downloads' % unicode(app_name)
  452. dirname = efs2(dirname)
  453. if os.name == 'nt':
  454. d = get_shell_dir(shellcon.CSIDL_PERSONAL)
  455. if d is None:
  456. d = desktop
  457. else:
  458. d = desktop
  459. return os.path.join(d, dirname)
  460. def get_startup_dir():
  461. """get directory where symlinks/shortcuts to be run at startup belong"""
  462. dir = None
  463. if os.name == 'nt':
  464. dir = get_shell_dir(shellcon.CSIDL_STARTUP)
  465. return dir
  466. def create_shortcut(source, dest, *args):
  467. if os.name == 'nt':
  468. if len(args) == 0:
  469. args = None
  470. path, file = os.path.split(source)
  471. sc = Shortcut(source,
  472. arguments=args,
  473. workingdir=path)
  474. sc.save(dest)
  475. else:
  476. # some other os may not support this, but throwing an error is good since
  477. # the function couldn't do what was requested
  478. os.symlink(source, dest)
  479. # linux also can't do args... maybe we should spit out a shell script?
  480. assert not args
  481. def resolve_shortcut(path):
  482. if os.name == 'nt':
  483. sc = Shortcut()
  484. sc.load(path)
  485. return sc.GetPath(0)[0]
  486. else:
  487. # boy, I don't know
  488. return path
  489. def remove_shortcut(dest):
  490. if os.name == 'nt':
  491. dest += ".lnk"
  492. os.unlink(dest)
  493. def enforce_shortcut(config, log_func):
  494. if os.name != 'nt':
  495. return
  496. path = win32api.GetModuleFileName(0)
  497. if 'python' in path.lower():
  498. # oops, running the .py too lazy to make that work
  499. path = r"C:\Program Files\BitTorrent\bittorrent.exe"
  500. root_key = _winreg.HKEY_CURRENT_USER
  501. subkey = r'Software\Microsoft\Windows\CurrentVersion\run'
  502. key = _winreg.CreateKey(root_key, subkey)
  503. if config['launch_on_startup']:
  504. _winreg.SetValueEx(key, app_name, 0, _winreg.REG_SZ,
  505. '"%s" --force_start_minimized' % path)
  506. else:
  507. try:
  508. _winreg.DeleteValue(key, app_name)
  509. except WindowsError, e:
  510. # value doesn't exist
  511. pass
  512. def enforce_association():
  513. if os.name != 'nt':
  514. return
  515. try:
  516. _enforce_association()
  517. except WindowsError:
  518. # access denied. not much we can do.
  519. traceback.print_exc()
  520. def _enforce_association():
  521. INSTDIR, EXENAME = os.path.split(win32api.GetModuleFileName(0))
  522. if 'python' in EXENAME.lower():
  523. # oops, running the .py too lazy to make that work
  524. INSTDIR = r"C:\Program Files\BitTorrent"
  525. EXENAME = "bittorrent.exe"
  526. # owie
  527. edit_flags = chr(0x00) + chr(0x00) + chr(0x10) + chr(0x00)
  528. # lots of wrappers for more direct NSIS mapping
  529. HKCR = _winreg.HKEY_CLASSES_ROOT
  530. HKCU = _winreg.HKEY_CURRENT_USER
  531. def filter_vars(s):
  532. s = s.replace("$INSTDIR", INSTDIR)
  533. s = s.replace("${EXENAME}", EXENAME)
  534. return s
  535. def WriteReg(root_key, subkey, key_name, type, value):
  536. subkey = filter_vars(subkey)
  537. key_name = filter_vars(key_name)
  538. value = filter_vars(value)
  539. # CreateKey opens the key for us and creates it if it does not exist
  540. #key = _winreg.OpenKey(root_key, subkey, 0, _winreg.KEY_ALL_ACCESS)
  541. key = _winreg.CreateKey(root_key, subkey)
  542. _winreg.SetValueEx(key, key_name, 0, type, value)
  543. def WriteRegStr(root_key, subkey, key_name, value):
  544. WriteReg(root_key, subkey, key_name, _winreg.REG_SZ, value)
  545. def WriteRegBin(root_key, subkey, key_name, value):
  546. WriteReg(root_key, subkey, key_name, _winreg.REG_BINARY, value)
  547. def DeleteRegKey(root_key, subkey):
  548. try:
  549. _winreg.DeleteKey(root_key, subkey)
  550. except WindowsError:
  551. # key doesn't exist
  552. pass
  553. ## Begin NSIS copy/paste/translate
  554. WriteRegStr(HKCR, '.torrent', "", "bittorrent")
  555. DeleteRegKey(HKCR, r".torrent\Content Type")
  556. # This line maks it so that BT sticks around as an option
  557. # after installing some other default handler for torrent files
  558. WriteRegStr(HKCR, r".torrent\OpenWithProgids", "bittorrent", "")
  559. # this prevents user-preference from generating "Invalid Menu Handle" by looking for an app
  560. # that no longer exists, and instead points it at us.
  561. WriteRegStr(HKCU, r"Software\Microsoft\Windows\CurrentVersion\Explorer\FileExts\.torrent", "Application", EXENAME)
  562. WriteRegStr(HKCR, r"Applications\${EXENAME}\shell", "", "open")
  563. WriteRegStr(HKCR, r"Applications\${EXENAME}\shell\open\command", "", r'"$INSTDIR\${EXENAME}" "%1"')
  564. # Add a mime type
  565. WriteRegStr(HKCR, r"MIME\Database\Content Type\application/x-bittorrent", "Extension", ".torrent")
  566. # Add a shell command to match the 'bittorrent' handler described above
  567. WriteRegStr(HKCR, "bittorrent", "", "TORRENT File")
  568. WriteRegBin(HKCR, "bittorrent", "EditFlags", edit_flags)
  569. # make us the default handler for bittorrent://
  570. WriteRegBin(HKCR, "bittorrent", "URL Protocol", chr(0x0))
  571. WriteRegStr(HKCR, r"bittorrent\Content Type", "", "application/x-bittorrent")
  572. WriteRegStr(HKCR, r"bittorrent\DefaultIcon", "", r"$INSTDIR\${EXENAME},0")
  573. WriteRegStr(HKCR, r"bittorrent\shell", "", "open")
  574. ## ReadRegStr $R1 HKCR "bittorrent\shell\open\command" ""
  575. ## StrCmp $R1 "" continue
  576. ##
  577. ## WriteRegStr HKCR "bittorrent\shell\open\command" "backup" $R1
  578. ##
  579. ## continue:
  580. WriteRegStr(HKCR, r"bittorrent\shell\open\command", "", r'"$INSTDIR\${EXENAME}" "%1"')
  581. # Add a shell command to handle torrent:// stuff
  582. WriteRegStr(HKCR, "torrent", "", "TORRENT File")
  583. WriteRegBin(HKCR, "torrent", "EditFlags", edit_flags)
  584. # make us the default handler for torrent://
  585. WriteRegBin(HKCR, "torrent", "URL Protocol", chr(0x0))
  586. WriteRegStr(HKCR, r"torrent\Content Type", "", "application/x-bittorrent")
  587. WriteRegStr(HKCR, r"torrent\DefaultIcon", "", "$INSTDIR\${EXENAME},0")
  588. WriteRegStr(HKCR, r"torrent\shell", "", "open")
  589. ## ReadRegStr $R1 HKCR "torrent\shell\open\command" ""
  590. ## WriteRegStr HKCR "torrent\shell\open\command" "backup" $R1
  591. WriteRegStr(HKCR, r"torrent\shell\open\command", "", r'"$INSTDIR\${EXENAME}" "%1"')
  592. def btspawn(cmd, *args):
  593. ext = ''
  594. if is_frozen_exe:
  595. ext = '.exe'
  596. path = os.path.join(app_root, cmd+ext)
  597. if not os.access(path, os.F_OK):
  598. if os.access(path+'.py', os.F_OK):
  599. path = path+'.py'
  600. args = [path] + list(args) # $0
  601. spawn(*args)
  602. def spawn(*args):
  603. if os.name == 'nt':
  604. # do proper argument quoting since exec/spawn on Windows doesn't
  605. bargs = args
  606. args = []
  607. for a in bargs:
  608. if not a.startswith("/"):
  609. a.replace('"', '\"')
  610. a = '"%s"' % a
  611. args.append(a)
  612. argstr = ' '.join(args[1:])
  613. # use ShellExecute instead of spawn*() because we don't want
  614. # handles (like the controlsocket) to be duplicated
  615. win32api.ShellExecute(0, "open", args[0], argstr, None, 1) # 1 == SW_SHOW
  616. else:
  617. if os.access(args[0], os.X_OK):
  618. forkback = os.fork()
  619. if forkback == 0:
  620. # BUG: stop IPC!
  621. print "execl ", args[0], args
  622. os.execl(args[0], *args)
  623. else:
  624. #BUG: what should we do here?
  625. pass
  626. def language_path():
  627. dot_dir = get_dot_dir()
  628. lang_file_name = os.path.join(dot_dir, efs(u'data')[0],
  629. efs(u'language')[0])
  630. return lang_file_name
  631. def get_language(name):
  632. from BTL import LOCALE_URL
  633. url = LOCALE_URL + name + ".tar.gz"
  634. socket.setdefaulttimeout(5)
  635. r = urllib.urlopen(url)
  636. # urllib seems to ungzip for us
  637. tarname = os.path.join(locale_root, name + ".tar")
  638. f = file(tarname, 'wb')
  639. f.write(r.read())
  640. f.close()
  641. tar = tarfile.open(tarname, "r")
  642. for tarinfo in tar:
  643. tar.extract(tarinfo, path=locale_root)
  644. tar.close()
  645. ##def smart_gettext_translation(domain, localedir, languages, fallback=False):
  646. ## try:
  647. ## t = gettext.translation(domain, localedir, languages=languages)
  648. ## except Exception, e:
  649. ## for lang in languages:
  650. ## try:
  651. ## get_language(lang)
  652. ## except Exception, e:
  653. ## #print "Failed on", lang, e
  654. ## pass
  655. ## t = gettext.translation(domain, localedir, languages=languages,
  656. ## fallback=fallback)
  657. ## return t
  658. def blocking_smart_gettext_and_install(domain, localedir, languages,
  659. fallback=False, unicode=False):
  660. try:
  661. t = gettext.translation(domain, localedir, languages=languages,
  662. fallback=fallback)
  663. except Exception, e:
  664. # if we failed to find the language, fetch it from the web
  665. running_count = 0
  666. running_deferred = {}
  667. # Get some reasonable defaults for arguments that were not supplied
  668. if languages is None:
  669. languages = []
  670. for envar in ('LANGUAGE', 'LC_ALL', 'LC_MESSAGES', 'LANG'):
  671. val = os.environ.get(envar)
  672. if val:
  673. languages = val.split(':')
  674. break
  675. if 'C' not in languages:
  676. languages.append('C')
  677. # now normalize and expand the languages
  678. nelangs = []
  679. for lang in languages:
  680. for nelang in gettext._expand_lang(lang):
  681. if nelang not in nelangs:
  682. nelangs.append(nelang)
  683. languages = nelangs
  684. for lang in languages:
  685. # HACK
  686. if lang.startswith('en'):
  687. continue
  688. if lang.startswith('C'):
  689. continue
  690. try:
  691. get_language(lang)
  692. except: #urllib.HTTPError:
  693. pass
  694. t = gettext.translation(domain, localedir,
  695. languages=languages,
  696. fallback=True)
  697. t.install(unicode)
  698. def smart_gettext_and_install(domain, localedir, languages,
  699. fallback=False, unicode=False):
  700. try:
  701. t = gettext.translation(domain, localedir, languages=languages,
  702. fallback=fallback)
  703. except Exception, e:
  704. # if we failed to find the language, fetch it from the web async-style
  705. running_count = 0
  706. running_deferred = {}
  707. # Get some reasonable defaults for arguments that were not supplied
  708. if languages is None:
  709. languages = []
  710. for envar in ('LANGUAGE', 'LC_ALL', 'LC_MESSAGES', 'LANG'):
  711. val = os.environ.get(envar)
  712. if val:
  713. languages = val.split(':')
  714. break
  715. if 'C' not in languages:
  716. languages.append('C')
  717. # now normalize and expand the languages
  718. nelangs = []
  719. for lang in languages:
  720. for nelang in gettext._expand_lang(lang):
  721. if nelang not in nelangs:
  722. nelangs.append(nelang)
  723. languages = nelangs
  724. for lang in languages:
  725. d = ThreadedDeferred(None, get_language, lang)
  726. def translate_and_install(r, td=d):
  727. running_deferred.pop(td)
  728. # only let the last one try to install
  729. if len(running_deferred) == 0:
  730. t = gettext.translation(domain, localedir,
  731. languages=languages,
  732. fallback=True)
  733. t.install(unicode)
  734. def failed(e, tlang=lang, td=d):
  735. if td in running_deferred:
  736. running_deferred.pop(td)
  737. # don't raise an error, just continue untranslated
  738. sys.stderr.write('Could not find translation for language "%s"\n' %
  739. tlang)
  740. #traceback.print_exc(e)
  741. d.addCallback(translate_and_install)
  742. d.addErrback(failed)
  743. # accumulate all the deferreds first
  744. running_deferred[d] = 1
  745. # start them all, the last one finished will install the language
  746. for d in running_deferred:
  747. d.start()
  748. return
  749. # install it if we got it the first time
  750. t.install(unicode)
  751. def _gettext_install(domain, localedir=None, languages=None, unicode=False):
  752. # gettext on win32 does not use locale.getdefaultlocale() by default
  753. # other os's will fall through and gettext.find() will do this task
  754. if os_name == 'nt':
  755. # this code is straight out of gettext.find()
  756. if languages is None:
  757. languages = []
  758. for envar in ('LANGUAGE', 'LC_ALL', 'LC_MESSAGES', 'LANG'):
  759. val = os.environ.get(envar)
  760. if val:
  761. languages = val.split(':')
  762. break
  763. # this is the important addition - since win32 does not typically
  764. # have any enironment variable set, append the default locale before 'C'
  765. languages.append(locale.getdefaultlocale()[0])
  766. if 'C' not in languages:
  767. languages.append('C')
  768. # we call the smart version, because anyone calling this needs it
  769. # before they can continue. yes, we do block on network IO. there is no
  770. # alternative (installing post-startup causes already loaded strings not
  771. # to be re-loaded)
  772. blocking_smart_gettext_and_install(domain, localedir,
  773. languages=languages,
  774. unicode=unicode)
  775. def read_language_file():
  776. """Reads the language file. The language file contains the
  777. name of the selected language, not any translations."""
  778. lang = None
  779. if os.name == 'nt':
  780. # this pulls user-preference language from the installer location
  781. try:
  782. regko = _winreg.OpenKey(_winreg.HKEY_CURRENT_USER, "Software\\BitTorrent")
  783. lang_num = _winreg.QueryValueEx(regko, "Language")[0]
  784. lang_num = int(lang_num)
  785. lang = language.locale_sucks[lang_num]
  786. except:
  787. pass
  788. else:
  789. lang_file_name = language_path()
  790. if os.access(lang_file_name, os.F_OK|os.R_OK):
  791. mode = 'r'
  792. if sys.version_info >= (2, 3):
  793. mode = 'U'
  794. lang_file = open(lang_file_name, mode)
  795. lang_line = lang_file.readline()
  796. lang_file.close()
  797. if lang_line:
  798. lang = ''
  799. for i in lang_line[:5]:
  800. if not i.isalpha() and i != '_':
  801. break
  802. lang += i
  803. if lang == '':
  804. lang = None
  805. return lang
  806. def write_language_file(lang):
  807. """Writes the language file. The language file contains the
  808. name of the selected language, not any translations."""
  809. if lang != '': # system default
  810. get_language(lang)
  811. if os.name == 'nt':
  812. regko = _winreg.CreateKey(_winreg.HKEY_CURRENT_USER, "Software\\BitTorrent")
  813. if lang == '':
  814. _winreg.DeleteValue(regko, "Language")
  815. else:
  816. lcid = None
  817. # I want two-way dicts
  818. for id, code in language.locale_sucks.iteritems():
  819. if code.lower() == lang.lower():
  820. lcid = id
  821. break
  822. if not lcid:
  823. raise KeyError(lang)
  824. _winreg.SetValueEx(regko, "Language", 0, _winreg.REG_SZ, str(lcid))
  825. else:
  826. lang_file_name = language_path()
  827. lang_file = open(lang_file_name, 'w')
  828. lang_file.write(lang)
  829. lang_file.close()
  830. def install_translation(unicode=False):
  831. languages = None
  832. try:
  833. lang = read_language_file()
  834. if lang is not None:
  835. languages = [lang, ]
  836. except:
  837. #pass
  838. traceback.print_exc()
  839. _gettext_install('bittorrent', locale_root, languages=languages, unicode=unicode)
  840. def write_pid_file(fname, errorfunc = None):
  841. """Creates a pid file on platforms that typically create such files;
  842. otherwise, this returns without doing anything. The fname should
  843. not include a path. The file will be placed in the appropriate
  844. platform-specific directory (/var/run in linux).
  845. """
  846. assert type(fname) == str
  847. assert errorfunc == None or callable(errorfunc)
  848. if os.name == 'nt': return
  849. try:
  850. pid_fname = os.path.join(efs2(u'/var/run'),fname)
  851. file(pid_fname, 'w').write(str(os.getpid()))
  852. except:
  853. try:
  854. pid_fname = os.path.join(efs2(u'/etc/tmp'),fname)
  855. except:
  856. if errorfunc:
  857. errorfunc("Couldn't open pid file. Continuing without one.")
  858. else:
  859. pass # just continue without reporting warning.
  860. desktop = None
  861. if os.name == 'nt':
  862. desktop = get_shell_dir(shellcon.CSIDL_DESKTOPDIRECTORY)
  863. else:
  864. homedir = get_home_dir()
  865. if homedir == None :
  866. desktop = '/tmp/'
  867. else:
  868. desktop = homedir
  869. if os.name in ('mac', 'posix'):
  870. tmp_desktop = os.path.join(homedir, efs2(u'Desktop'))
  871. if os.access(tmp_desktop, os.R_OK|os.W_OK):
  872. desktop = tmp_desktop + os.sep