From 390dbcd9f3a0e6a59329ab8c775461d7e2f0bc76 Mon Sep 17 00:00:00 2001 From: DiMartinoXBMC Date: Sun, 26 Jul 2015 21:44:46 +0300 Subject: [PATCH] lag fix --- Libtorrent.py | 151 ++++++++++++++++++++++++++++++++++-------------- Localization.py | 1 + Player.py | 88 +++++++++++++++------------- changelog.txt | 4 ++ functions.py | 25 +++++--- 5 files changed, 179 insertions(+), 90 deletions(-) diff --git a/Libtorrent.py b/Libtorrent.py index 8926fc9..a57477a 100644 --- a/Libtorrent.py +++ b/Libtorrent.py @@ -25,18 +25,17 @@ import hashlib import re from StringIO import StringIO import gzip +import sys import xbmc import xbmcgui import xbmcvfs import Localization -from functions import file_encode, isSubtitle, DownloadDB, log, debug +from functions import file_encode, isSubtitle, DownloadDB, log, debug, is_writable from platform_pulsar import get_platform class Libtorrent: - torrentFile = None magnetLink = None - storageDirectory = '' startPart = 0 endPart = 0 partOffset = 0 @@ -45,9 +44,19 @@ class Libtorrent: downloadThread = None threadComplete = False lt = None + save_resume_data = None def __init__(self, storageDirectory='', torrentFile='', torrentFilesDirectory='torrents'): self.platform = get_platform() + self.storageDirectory = storageDirectory + self.torrentFilesPath = os.path.join(self.storageDirectory, torrentFilesDirectory) + os.sep + if not is_writable(self.storageDirectory): + xbmcgui.Dialog().ok(Localization.localize('Torrenter v2'), + Localization.localize('Your storage path is not writable or not local! Please change it in settings!'), + Localization.localize(self.storageDirectory)) + + sys.exit(1) + try: import libtorrent @@ -69,8 +78,6 @@ class Libtorrent: Localization.localize(self.platform["message"][1])) return - self.storageDirectory = storageDirectory - self.torrentFilesPath = os.path.join(self.storageDirectory, torrentFilesDirectory) + os.sep if xbmcvfs.exists(torrentFile): self.torrentFile = torrentFile e=self.lt.bdecode(xbmcvfs.File(self.torrentFile,'rb').read()) @@ -232,10 +239,22 @@ class Libtorrent: return subs def setUploadLimit(self, bytesPerSecond): - self.session.set_upload_rate_limit(int(bytesPerSecond)) + try: + session_settings = self.session.get_settings() + session_settings['upload_rate_limit'] = int(bytesPerSecond) + self.session.set_settings(session_settings) + except: + #0.16 compatibility + self.session.set_upload_rate_limit(int(bytesPerSecond)) def setDownloadLimit(self, bytesPerSecond): - self.session.set_download_rate_limit(int(bytesPerSecond)) + try: + session_settings = self.session.get_settings() + session_settings['download_rate_limit'] = int(bytesPerSecond) + self.session.set_settings(session_settings) + except: + #0.16 compatibility + self.session.set_download_rate_limit(int(bytesPerSecond)) def md5(self, string): hasher = hashlib.md5() @@ -306,19 +325,16 @@ class Libtorrent: return def initSession(self): - try: - self.session.remove_torrent(self.torrentHandle) - except: - pass self.session = self.lt.session() self.session.set_alert_mask(self.lt.alert.category_t.error_notification | self.lt.alert.category_t.status_notification | self.lt.alert.category_t.storage_notification) + #self.session.set_alert_mask(self.lt.alert.category_t.all_categories) self.session.start_dht() self.session.add_dht_router("router.bittorrent.com", 6881) self.session.add_dht_router("router.utorrent.com", 6881) self.session.add_dht_router("router.bitcomet.com", 6881) - self.session.start_lsd() - self.session.start_upnp() - self.session.start_natpmp() + #self.session.start_lsd() + #self.session.start_upnp() + #self.session.start_natpmp() self.session.listen_on(6881, 6891) #tribler example never tested @@ -331,22 +347,39 @@ class Libtorrent: #self.session.add_extension("smart_ban") # Session settings - session_settings = self.session.settings() - # - session_settings.announce_to_all_tiers = True - session_settings.announce_to_all_trackers = True - session_settings.connection_speed = 100 - session_settings.peer_connect_timeout = 2 - session_settings.rate_limit_ip_overhead = True - session_settings.request_timeout = 1 - session_settings.torrent_connect_boost = 100 - session_settings.user_agent = 'uTorrent/3430(40298)' + try: + session_settings = self.session.get_settings() + # + session_settings['announce_to_all_tiers'] = True + session_settings['announce_to_all_trackers'] = True + session_settings['connection_speed'] = 100 + session_settings['peer_connect_timeout'] = 2 + session_settings['rate_limit_ip_overhead'] = True + session_settings['request_timeout'] = 1 + session_settings['torrent_connect_boost'] = 100 + session_settings['user_agent'] = 'uTorrent/3430(40298)' + #session_settings['cache_size'] = 0 + #session_settings['use_read_cache'] = False + + except: + #0.15 compatibility + log('[initSession]: Session settings 0.15 compatibility') + session_settings = self.session.settings() + + session_settings.announce_to_all_tiers = True + session_settings.announce_to_all_trackers = True + session_settings.connection_speed = 100 + session_settings.peer_connect_timeout = 2 + session_settings.rate_limit_ip_overhead = True + session_settings.request_timeout = 1 + session_settings.torrent_connect_boost = 100 + session_settings.user_agent = 'uTorrent/3430(40298)' # self.session.set_settings(session_settings) def encryptSession(self): # Encryption settings - print '[Torrenter v2]: Encryption enabling...' + log('Encryption enabling...') try: encryption_settings = self.lt.pe_settings() encryption_settings.out_enc_policy = self.lt.enc_policy(self.lt.enc_policy.forced) @@ -354,22 +387,26 @@ class Libtorrent: encryption_settings.allowed_enc_level = self.lt.enc_level.both encryption_settings.prefer_rc4 = True self.session.set_pe_settings(encryption_settings) - print '[Torrenter v2]: Encryption on!' + log('Encryption on!') except Exception, e: - print '[Torrenter v2]: Encryption failed! Exception: ' + str(e) + log('Encryption failed! Exception: ' + str(e)) pass def startSession(self): - if None == self.magnetLink: - self.torrentHandle = self.session.add_torrent({'ti': self.torrentFileInfo, - 'save_path': self.storageDirectory, - 'flags': 0x300, - 'paused': False, - #'auto_managed': False, - #'storage_mode': self.lt.storage_mode_t.storage_mode_allocate, - }) - else: + if self.magnetLink: self.torrentFileInfo = self.getMagnetInfo() + torrent_info={'ti': self.torrentFileInfo, + 'save_path': self.storageDirectory, + #'storage_mode': self.lt.storage_mode_t(1), + 'paused': False, + #'auto_managed': False, + #'duplicate_is_error': True + } + if self.save_resume_data: + log('loading resume data') + torrent_info['resume_data']=self.save_resume_data + self.torrentHandle = self.session.add_torrent(torrent_info) + self.torrentHandle.set_sequential_download(True) self.torrentHandle.set_max_connections(60) self.torrentHandle.set_max_uploads(-1) @@ -419,15 +456,30 @@ class Libtorrent: self.session.remove_torrent(self.torrentHandle) except: log('RuntimeError: invalid torrent handle used') - self.session.stop_natpmp() - self.session.stop_upnp() - self.session.stop_lsd() + #self.session.stop_natpmp() + #self.session.stop_upnp() + #self.session.stop_lsd() self.session.stop_dht() + def resume_data(self): + self.torrentHandle.save_resume_data() + received=False + while not received: + self.session.wait_for_alert(1000) + a = self.session.pop_alert() + log('[save_resume_data]: ['+str(type(a))+'] the alert '+str(a)+' is received') + if type(a) == self.lt.save_resume_data_alert: + received = True + debug('[save_resume_data]: '+str(dir(a))) + self.save_resume_data=self.lt.bencode(a.resume_data) + log('[save_resume_data]: the torrent resume data are saved') + def debug(self): try: # print str(self.getFilePath(0)) s = self.torrentHandle.status() + #get_cache_status=self.session.get_cache_status() + #log('get_cache_status - %s/%s' % (str(get_cache_status.blocks_written), str(get_cache_status.blocks_read))) # get_settings=self.torrentHandle.status # print s.num_pieces # priorities = self.torrentHandle.piece_priorities() @@ -438,15 +490,23 @@ class Libtorrent: 'downloading', 'finished', 'seeding', 'allocating'] log('[%s] %.2f%% complete (down: %.1f kb/s up: %.1f kB/s peers: %d) %s %s %s' % \ (self.lt.version, s.progress * 100, s.download_rate / 1000, - s.upload_rate / 1000, s.num_peers, state_str[s.state], - self.get_debug_info('dht_state'), self.get_debug_info('trackers_sum'))) + s.upload_rate / 1000, s.num_peers, state_str[s.state], self.get_debug_info('dht_state'), self.get_debug_info('trackers_sum'))) + #log('%s %s' % (self.get_debug_info('dht_state'), self.get_debug_info('trackers_sum'))) debug('TRACKERS:' +str(self.torrentHandle.trackers())) + + received=self.session.pop_alert() + while received: + debug('[debug]: ['+str(type(received))+'] the alert '+str(received)+' is received') + #if type(received) == self.lt.torrent_finished_alert: + # self.session.pause() + received = self.session.pop_alert() + #log('is_dht_running:' +str(self.session.is_dht_running())) #log('dht_state:' +str(self.session.dht_state())) #i = 0 # for t in s.pieces: # if t: i=i+1 - # print str(self.session.pop_alert()) + #print str(self.session.pop_alert()) # print str(s.pieces[self.startPart:self.endPart]) # print 'True pieces: %d' % i # print s.current_tracker @@ -469,10 +529,13 @@ class Libtorrent: for url, fails, verified in trackers: fails_sum+=fails if verified: verified_sum+=1 - result=result+'Trakers: verified %d/%d, fails=%d' %(verified_sum, len(trackers)-1, fails_sum) + result=result+'Trackers: verified %d/%d, fails=%d' %(verified_sum, len(trackers)-1, fails_sum) if info=='dht_state': is_dht_running='ON' if self.session.is_dht_running() else 'OFF' - nodes=self.session.dht_state().get('nodes') + try: + nodes=self.session.dht_state().get('nodes') + except: + nodes=None nodes=len(nodes) if nodes else 0 result='DHT: %s (%d)' % (is_dht_running, nodes) return result diff --git a/Localization.py b/Localization.py index 8e80eaf..389a88f 100644 --- a/Localization.py +++ b/Localization.py @@ -266,6 +266,7 @@ def localize(text): 'Torrenter Tracker Install':'Установка трекеров в Torrenter', 'Ask to save':'Спросить о сохранении', 'Would you like to save this file?':'Хотите сохранить данный файл?', + 'Your storage path is not writable or not local! Please change it in settings!':'Ваше хранилище не доступно для записи или не локально! Измените в настройках!', }, 'uk': { diff --git a/Player.py b/Player.py index b477c3a..d20bbd5 100644 --- a/Player.py +++ b/Player.py @@ -30,7 +30,7 @@ import Downloader import xbmcgui import xbmcvfs import Localization -from functions import calculate, showMessage, clearStorage, DownloadDB, get_ids_video +from functions import calculate, showMessage, clearStorage, DownloadDB, get_ids_video, log, debug ROOT = sys.modules["__main__"].__root__ RESOURCES_PATH = os.path.join(ROOT, 'resources') @@ -140,7 +140,7 @@ class TorrentPlayer(xbmc.Player): self.userStorageDirectory = userStorageDirectory self.torrentUrl = torrentUrl xbmc.Player.__init__(self) - print ("[TorrentPlayer] Initalized") + log("[TorrentPlayer] Initalized") self.params = params self.get = self.params.get self.contentId = int(self.get("url")) @@ -157,35 +157,37 @@ class TorrentPlayer(xbmc.Player): if self.buffer(): while True: if self.setup_play(): - # print '************************************* GOING LOOP' - #self.torrent.continueSession(self.contentId) + debug('************************************* GOING LOOP') + self.torrent.startSession() + self.torrent.continueSession(self.contentId) self.loop() else: break - # print '************************************* GO NEXT?' + debug('************************************* GO NEXT?') if self.next_dl and self.next_dling and isinstance(self.next_contentId, int) and self.iterator == 100: self.contentId = self.next_contentId continue - # print '************************************* NO! break' + debug('************************************* NO! break') break self.torrent.stopSession() self.torrent.threadComplete = True self.torrent.checkThread() if '1' != self.__settings__.getSetting("keep_files") and 'Saved Files' not in self.userStorageDirectory: + xbmc.sleep(1000) clearStorage(self.userStorageDirectory) else: if self.seeding_status: - showMessage(Localization.localize('Information'), - Localization.localize('Torrent is seeding. To stop it use Download Status.'), forced=True) + showMessage(self.localize('Information'), + self.localize('Torrent is seeding. To stop it use Download Status.'), forced=True) else: if self.seeding: self.db_delete() - showMessage(Localization.localize('Information'), - Localization.localize('Torrent downloading is stopped.'), forced=True) + showMessage(self.localize('Information'), + self.localize('Torrent downloading is stopped.'), forced=True) def init(self): self.next_dl = True if self.__settings__.getSetting('next_dl') == 'true' and self.ids_video else False - print '[TorrentPlayer]: init - ' + str(self.next_dl) + log('[TorrentPlayer]: init - ' + str(self.next_dl)) self.next_contentId = False self.display_name = '' self.downloadedSize = 0 @@ -212,21 +214,21 @@ class TorrentPlayer(xbmc.Player): self.torrent.status = False self.fullSize = self.torrent.getFileSize(self.contentId) Offset = calculate(self.fullSize) - # print 'Offset: '+str(Offset) + debug('Offset: '+str(Offset)) # mp4 fix label = os.path.basename(self.torrent.getFilePath(self.contentId)) isMP4 = False if '.' in label and str(label.split('.')[-1]).lower() == 'mp4': isMP4 = True - # print 'setup_torrent: '+str((self.contentId, Offset, isMP4, label, ext)) + debug('setup_torrent: '+str((self.contentId, Offset, isMP4, label))) self.torrent.continueSession(self.contentId, Offset=Offset, isMP4=isMP4) def buffer(self): iterator = 0 progressBar = xbmcgui.DialogProgress() - progressBar.create(Localization.localize('Please Wait') + str(' [%s]' % str(self.torrent.lt.version)), - Localization.localize('Seeds searching.')) + progressBar.create(self.localize('Please Wait') + str(' [%s]' % str(self.torrent.lt.version)), + self.localize('Seeds searching.')) if self.subs_dl: subs = self.torrent.getSubsIds(os.path.basename(self.torrent.getFilePath(self.contentId))) if len(subs) > 0: @@ -242,41 +244,43 @@ class TorrentPlayer(xbmc.Player): if status.state == 0 or (status.progress == 0 and status.num_pieces > 0): iterator = int(status.num_pieces * 100 / num_pieces) if iterator > 99: iterator = 99 - progressBar.update(iterator, Localization.localize('Checking preloaded files...'), ' ', ' ') + progressBar.update(iterator, self.localize('Checking preloaded files...'), ' ', ' ') elif status.state == 3: - dialogText = Localization.localize('Preloaded: ') + str(downloadedSize / 1024 / 1024) + ' MB / ' + str( + dialogText = self.localize('Preloaded: ') + str(downloadedSize / 1024 / 1024) + ' MB / ' + str( self.fullSize / 1024 / 1024) + ' MB' peersText = ' [%s: %s; %s: %s]' % ( - Localization.localize('Seeds'), str(self.torrent.getSeeds()), Localization.localize('Peers'), + self.localize('Seeds'), str(self.torrent.getSeeds()), self.localize('Peers'), str(self.torrent.getPeers()),) speedsText = '%s: %s Mbit/s; %s: %s Mbit/s' % ( - Localization.localize('Downloading'), str(self.torrent.getDownloadRate() * 8 / 1000000), - Localization.localize('Uploading'), str(self.torrent.getUploadRate() * 8 / 1000000)) + self.localize('Downloading'), str(self.torrent.getDownloadRate() * 8 / 1000000), + self.localize('Uploading'), str(self.torrent.getUploadRate() * 8 / 1000000)) if self.debug: peersText=peersText + ' ' + self.torrent.get_debug_info('dht_state') - dialogText=dialogText.replace(Localization.localize('Preloaded: '),'') + ' ' + self.torrent.get_debug_info('trackers_sum') - progressBar.update(iterator, Localization.localize('Seeds searching.') + peersText, dialogText, + dialogText=dialogText.replace(self.localize('Preloaded: '),'') + ' ' + self.torrent.get_debug_info('trackers_sum') + progressBar.update(iterator, self.localize('Seeds searching.') + peersText, dialogText, speedsText) else: - progressBar.update(iterator, Localization.localize('UNKNOWN STATUS'), ' ', ' ') + progressBar.update(iterator, self.localize('UNKNOWN STATUS'), ' ', ' ') if progressBar.iscanceled(): progressBar.update(0) progressBar.close() self.torrent.threadComplete = True self.torrent.checkThread() return + #self.torrent.torrentHandle.flush_cache() + self.torrent.resume_data() + self.torrent.session.remove_torrent(self.torrent.torrentHandle) progressBar.update(0) progressBar.close() - self.torrent.continueSession(self.contentId) return True def setup_subs(self, label, path): iterator = 0 subs = self.torrent.getSubsIds(label) - # print str(subs) + debug('[setup_subs] subs: '+str(subs)) if len(subs) > 0: - showMessage(Localization.localize('Information'), - Localization.localize('Downloading and copy subtitles. Please wait.'), forced=True) + showMessage(self.localize('Information'), + self.localize('Downloading and copy subtitles. Please wait.'), forced=True) for ind, title in subs: self.torrent.continueSession(ind) while iterator < 100: @@ -292,7 +296,7 @@ class TorrentPlayer(xbmc.Player): ext = temp.split('.')[-1] temp = temp[:len(temp) - len(ext) - 1] + '.' + addition + '.' + ext newFileName = os.path.join(os.path.dirname(path), temp) - # print str((os.path.join(os.path.dirname(os.path.dirname(path)),title),newFileName)) + debug('[setup_subs]: '+str((os.path.join(os.path.dirname(os.path.dirname(path)),title),newFileName))) if not xbmcvfs.exists(newFileName): xbmcvfs.copy(os.path.join(os.path.dirname(os.path.dirname(path)), title), newFileName) @@ -326,7 +330,7 @@ class TorrentPlayer(xbmc.Player): 'season': int(seasonId), 'tvshowtitle': title}) except: - print '[TorrentPlayer] Operation INFO failed!' + log('[TorrentPlayer] Operation INFO failed!') thumbnail = self.get("thumbnail") if thumbnail: @@ -354,7 +358,7 @@ class TorrentPlayer(xbmc.Player): def onPlayBackStarted(self): for f in self.on_playback_started: f() - print(str(("video", "play", self.display_name))) + log('[onPlayBackStarted]: '+(str(("video", "play", self.display_name)))) def onPlayBackResumed(self): for f in self.on_playback_resumed: @@ -364,12 +368,12 @@ class TorrentPlayer(xbmc.Player): def onPlayBackPaused(self): for f in self.on_playback_paused: f() - print(str(("video", "pause", self.display_name))) + log('[onPlayBackPaused]: '+(str(("video", "pause", self.display_name)))) def onPlayBackStopped(self): for f in self.on_playback_stopped: f() - print(str(("video", "stop", self.display_name))) + log('[onPlayBackStopped]: '+(str(("video", "stop", self.display_name)))) @contextmanager def attach(self, callback, *events): @@ -403,7 +407,7 @@ class TorrentPlayer(xbmc.Player): self.next_contentId = int(self.ids_video[next_contentId_index]) else: self.next_contentId = False - # print str(self.next_contentId)+'xxxxxx23' + debug('[loop] next_contentId: '+str(self.next_contentId)) if not self.seeding_run and self.iterator == 100 and self.seeding: self.seeding_run = True self.seed(self.contentId) @@ -411,8 +415,8 @@ class TorrentPlayer(xbmc.Player): # xbmc.sleep(7000) if self.iterator == 100 and self.next_dl and not self.next_dling and isinstance(self.next_contentId, int) and self.next_contentId != False: - showMessage(Localization.localize('Torrent Downloading'), - Localization.localize('Starting download next episode!'), forced=True) + showMessage(self.localize('Torrent Downloading'), + self.localize('Starting download next episode!'), forced=True) self.torrent.stopSession() # xbmc.sleep(1000) path = self.torrent.getFilePath(self.next_contentId) @@ -422,10 +426,10 @@ class TorrentPlayer(xbmc.Player): def _get_status_lines(self, s): return [ - self.display_name.decode('utf-8'), - "%.2f%% %s %s %s" % (s.progress * 100, Localization.localize(STATE_STRS[s.state]).decode('utf-8'), self.torrent.get_debug_info('dht_state'), self.torrent.get_debug_info('trackers_sum')), - "D:%.2f%s U:%.2f%s S:%d P:%d" % (s.download_rate / 1000, Localization.localize('kb/s').decode('utf-8'), - s.upload_rate / 1000, Localization.localize('kb/s').decode('utf-8'), + self.display_name.decode('utf-8')+'; '+self.torrent.get_debug_info('dht_state'), + "%.2f%% %s; %s" % (s.progress * 100, self.localize(STATE_STRS[s.state]).decode('utf-8'), self.torrent.get_debug_info('trackers_sum')), + "D:%.2f%s U:%.2f%s S:%d P:%d" % (s.download_rate / 1000, self.localize('kb/s').decode('utf-8'), + s.upload_rate / 1000, self.localize('kb/s').decode('utf-8'), s.num_seeds, s.num_peers) ] @@ -450,3 +454,9 @@ class TorrentPlayer(xbmc.Player): contentList.append((filedict.get('title'), str(filedict.get('ind')))) contentList = sorted(contentList, key=lambda x: x[0]) return get_ids_video(contentList) + + def localize(self, string): + try: + return Localization.localize(string) + except: + return string diff --git a/changelog.txt b/changelog.txt index 7a81166..170d0e8 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,9 @@ English changelog at http://bit.ly/1MfSVUP +[B]Version 2.3.5[/B] +[+] Проигрыватель: Уменьшена просадка после загрузки буфера +[+] Проигрыватель: Проверка на запись хранилища + [B]Version 2.3.4[/B] [+] Проигрыватель: Новая настройка - спрашивать о сохранении файла [+] Списки Медиа: Исправлен TheMovieDB для python 2.6 diff --git a/functions.py b/functions.py index dc219d7..038a632 100644 --- a/functions.py +++ b/functions.py @@ -131,13 +131,16 @@ def log(msg): xbmc.log("### [%s]: %s" % (__plugin__,'ERROR LOG',), level=xbmc.LOGNOTICE ) -def debug(msg): +def debug(msg, forced=False): + level=xbmc.LOGDEBUG + if getSettingAsBool('debug') and forced: + level=xbmc.LOGNOTICE try: - xbmc.log("### [%s]: %s" % (__plugin__,msg,), level=xbmc.LOGDEBUG ) + xbmc.log("### [%s]: %s" % (__plugin__,msg,), level=level ) except UnicodeEncodeError: - xbmc.log("### [%s]: %s" % (__plugin__,msg.encode("utf-8", "ignore"),), level=xbmc.LOGDEBUG ) + xbmc.log("### [%s]: %s" % (__plugin__,msg.encode("utf-8", "ignore"),), level=level ) except: - xbmc.log("### [%s]: %s" % (__plugin__,'ERROR DEBUG',), level=xbmc.LOGDEBUG ) + xbmc.log("### [%s]: %s" % (__plugin__,'ERROR DEBUG',), level=level ) def showMessage(heading, message, times=10000, forced=False): @@ -1115,6 +1118,7 @@ class Searchers(): addons_dir = os.path.join(xbmc.translatePath('special://home'),'addons') addons_dirsList = xbmcvfs.listdir(addons_dir)[0] for searcherDir in addons_dirsList: + #if len(searchersDict)>1: break if re.match('^torrenter\.searcher\.(\w+)$', searcherDir): name=searcherDir.replace('torrenter.searcher.', '') path=os.path.join(addons_dir, searcherDir) @@ -1122,7 +1126,6 @@ class Searchers(): 'path':path, 'searcher':os.path.join(path,name+'.py'), 'type':'external'} - #if len(searchersDict)>1: break return searchersDict def dic(self, providers=[]): @@ -1864,7 +1867,6 @@ def windows_check(): """ return platform.system() in ('Windows', 'Microsoft') - def vista_check(): import platform """ @@ -1872,4 +1874,13 @@ def vista_check(): :returns: True or False :rtype: bool """ - return platform.release() == "Vista" \ No newline at end of file + return platform.release() == "Vista" + +def is_writable(path): + try: + open(os.path.join(path, 'temp'), 'w') + except: + return False + else: + os.remove(os.path.join(path, 'temp')) + return True \ No newline at end of file