Add library to check if a file is hidden

- Add `beets.util.hidden` which adds a `is_hidden` function to check
   whether or not a file is hidden on the current platform.
 - Add tests for `beets.util.hidden`.
This commit is contained in:
Jack Wilsdon 2016-05-06 00:15:09 +01:00
parent bf29c24a57
commit 412bde5de2
2 changed files with 217 additions and 0 deletions

143
beets/util/hidden.py Normal file
View file

@ -0,0 +1,143 @@
"""Simple library to work out if a file is hidden on different platforms."""
import ctypes
import ctypes.util
import os.path
import sys
# Adjustments for CoreFoundation functions on OS X.
_CF_FUNCTION_MAPPINGS = {
'CFRelease': {
'argtypes': [ctypes.c_void_p],
'restype': None
},
'CFURLCreateFromFileSystemRepresentation': {
'argtypes': [
ctypes.c_void_p,
ctypes.c_char_p,
ctypes.c_long,
ctypes.c_int
],
'restype': ctypes.c_void_p
},
'CFURLCopyResourcePropertyForKey': {
'argtypes': [
ctypes.c_void_p,
ctypes.c_void_p,
ctypes.c_void_p,
ctypes.c_void_p
],
'restype': ctypes.c_void_p
},
'CFBooleanGetValue': {
'argtypes': [ctypes.c_void_p],
'restype': ctypes.c_int
}
}
def _dict_attr_copy(source, destination):
"""Copy dict values from source on to destination as attributes."""
for k, v in source.iteritems():
if isinstance(v, dict):
_dict_attr_copy(v, getattr(destination, k))
else:
setattr(destination, k, v)
def _is_hidden_osx(path):
"""Return whether or not a file is hidden on OS X.
This uses CoreFoundation alongside CFURL to work out if a file has the
"hidden" flag.
"""
# Load CoreFoundation.
cf_path = ctypes.util.find_library('CoreFoundation')
cf = ctypes.cdll.LoadLibrary(cf_path)
# Copy the adjustments on to the library.
_dict_attr_copy(_CF_FUNCTION_MAPPINGS, cf)
# Create a URL from the path.
url = cf.CFURLCreateFromFileSystemRepresentation(None, path, len(path),
False)
# Retrieve the hidden key.
is_hidden_key = ctypes.c_void_p.in_dll(cf, 'kCFURLIsHiddenKey')
# Create a void pointer and get the address of it.
val = ctypes.c_void_p(0)
val_address = ctypes.addressof(val)
# Get the value (whether or not the file is hidden) for the hidden key and
# store it in val.
success = cf.CFURLCopyResourcePropertyForKey(url, is_hidden_key,
val_address, None)
# Check if we were able to get the value for the hidden key.
if success:
# Retrieve the result as a boolean.
result = cf.CFBooleanGetValue(val)
# Release the value and URL.
cf.CFRelease(val)
cf.CFRelease(url)
return bool(result)
else:
return False
def _is_hidden_win(path):
"""Return whether or not a file is hidden on Windows.
This uses GetFileAttributes to work out if a file has the "hidden" flag
(FILE_ATTRIBUTE_HIDDEN).
"""
# FILE_ATTRIBUTE_HIDDEN = 2 (0x2) from GetFileAttributes documentation.
hidden_mask = 2
# Retrieve the attributes for the file.
attrs = ctypes.windll.kernel32.GetFileAttributesW(path)
# Ensure we have valid attribues and compare them against the mask.
return attrs >= 0 and bool(attrs & hidden_mask)
def _is_hidden_dot(path):
"""Return whether or not a file starts with a dot.
Files starting with a dot are seen as "hidden" files on Unix-based OSes.
"""
return os.path.basename(path).startswith('.')
def is_hidden(path):
"""Return whether or not a file is hidden.
This method works differently depending on the platform it is called on.
On OS X, it uses both the result of `is_hidden_osx` and `is_hidden_dot` to
work out if a file is hidden.
On Windows, it uses the result of `is_hidden_win` to work out if a file is
hidden.
On any other operating systems (i.e. Linux), it uses `is_hidden_dot` to
work out if a file is hidden.
"""
# Convert the path to unicode if it is not already.
if not isinstance(path, unicode):
path = path.decode('utf-8')
# Run platform specific functions depending on the platform
if sys.platform == 'darwin':
return _is_hidden_osx(path) or _is_hidden_dot(path)
elif sys.platform == 'win32':
return _is_hidden_win(path)
else:
return _is_hidden_dot(path)
__all__ = ['is_hidden']

74
test/test_hidden.py Normal file
View file

@ -0,0 +1,74 @@
# -*- coding: utf-8 -*-
# This file is part of beets.
# Copyright 2016, Fabrice Laporte.
#
# Permission is hereby granted, free of charge, to any person obtaining
# a copy of this software and associated documentation files (the
# "Software"), to deal in the Software without restriction, including
# without limitation the rights to use, copy, modify, merge, publish,
# distribute, sublicense, and/or sell copies of the Software, and to
# permit persons to whom the Software is furnished to do so, subject to
# the following conditions:
#
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
"""Tests for the 'hidden' utility."""
from __future__ import division, absolute_import, print_function
from test._common import unittest
import sys
import tempfile
from beets.util import hidden
import subprocess
import errno
import ctypes
class HiddenFileTest(unittest.TestCase):
def setUp(self):
pass
def test_osx_hidden(self):
if not sys.platform == 'darwin':
self.skipTest('sys.platform is not darwin')
return
with tempfile.NamedTemporaryFile(delete=False) as f:
try:
command = ["chflags", "hidden", f.name]
subprocess.Popen(command).wait()
except OSError as e:
if e.errno == errno.ENOENT:
self.skipTest("unable to find chflags")
else:
raise e
self.assertTrue(hidden.is_hidden(f.name))
def test_windows_hidden(self):
if not sys.platform == 'windows':
self.skipTest('sys.platform is not windows')
return
# FILE_ATTRIBUTE_HIDDEN = 2 (0x2) from GetFileAttributes documentation.
hidden_mask = 2
with tempfile.NamedTemporaryFile() as f:
# Hide the file using
success = ctypes.windll.kernel32.SetFileAttributesW(f.name,
hidden_mask)
if not success:
self.skipTest("unable to set file attributes")
self.assertTrue(hidden.is_hidden(f.name))
def test_other_hidden(self):
if sys.platform == 'darwin' or sys.platform == 'windows':
self.skipTest('sys.platform is known')
return
with temfile.NamedTemporaryFile(prefix='.tmp') as f:
self.assertTrue(hidden.is_hidden(f.name))