Skip to content

Commit 3a94b2d

Browse files
committed
Added stationarypad, closes #47
1 parent f4db1bd commit 3a94b2d

File tree

6 files changed

+118
-43
lines changed

6 files changed

+118
-43
lines changed

README.md

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,7 @@
11
# osxmetadata
2-
<!-- ALL-CONTRIBUTORS-BADGE:START - Do not remove or modify this section -->
3-
[![All Contributors](https://img.shields.io/badge/all_contributors-1-orange.svg?style=flat)](#contributors-)
4-
<!-- ALL-CONTRIBUTORS-BADGE:END -->
5-
62
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/python/black)
73
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
4+
[![All Contributors](https://img.shields.io/badge/all_contributors-1-orange.svg?style=flat)](#contributors-)
85

96

107
## What is osxmetadata?
@@ -35,7 +32,7 @@ The command line tool can also be run via `python -m osxmetadata`. Running it w
3532

3633
```
3734
Usage: osxmetadata [OPTIONS] FILE
38-
35+
3936
Read/write metadata from file(s).
4037
4138
Options:
@@ -189,7 +186,13 @@ rating kMDItemStarRating, com.apple.metadata:kMDItemStarRating;
189186
an iTunes track. An integer.
190187
stationary kMDItemFSIsStationery,
191188
com.apple.metadata:kMDItemFSIsStationery; Boolean indicating
192-
if this file is stationery.
189+
if this file is stationery. Note: this is not what the
190+
Finder uses for Stationary Pad flag. See also
191+
'stationarypad'.
192+
stationarypad stationarypad, com.apple.FinderInfo; Marks the file as
193+
stationary (a template that can be re-used). Setting this to
194+
True is the same as checking the 'Stationary Pad' box in
195+
Finder Info.
193196
tags _kMDItemUserTags, com.apple.metadata:_kMDItemUserTags;
194197
Finder tags; searchable in Spotlight using "tag:tag_name".
195198
If you want tags/keywords visible in the Finder, use this
@@ -225,7 +228,8 @@ Information about commonly used MacOS metadata attributes is available from [App
225228
|kMDItemParticipants|participants|com.apple.metadata:kMDItemParticipants|The list of people who are visible in an image or movie or written about in a document. A list of strings.|
226229
|kMDItemProjects|projects|com.apple.metadata:kMDItemProjects|The list of projects that this file is part of. For example, if you were working on a movie all of the files could be marked as belonging to the project “My Movie”. A list of strings.|
227230
|kMDItemStarRating|rating|com.apple.metadata:kMDItemStarRating|User rating of this item. For example, the stars rating of an iTunes track. An integer.|
228-
|kMDItemFSIsStationery|stationary|com.apple.metadata:kMDItemFSIsStationery|Boolean indicating if this file is stationery.|
231+
|kMDItemFSIsStationery|stationary|com.apple.metadata:kMDItemFSIsStationery|Boolean indicating if this file is stationery. Note: this is not what the Finder uses for Stationary Pad flag. See also 'stationarypad'.|
232+
|stationarypad|stationarypad|com.apple.FinderInfo|Marks the file as stationary (a template that can be re-used). Setting this to True is the same as checking the 'Stationary Pad' box in Finder Info.|
229233
|_kMDItemUserTags|tags|com.apple.metadata:_kMDItemUserTags|Finder tags; searchable in Spotlight using "tag:tag_name". If you want tags/keywords visible in the Finder, use this instead of kMDItemKeywords. A list of Tag objects.|
230234
|kMDItemVersion|version|com.apple.metadata:kMDItemVersion|The version number of this file. A string.|
231235
|kMDItemWhereFroms|wherefroms|com.apple.metadata:kMDItemWhereFroms|Describes where the file was obtained from (e.g. URL downloaded from). A list of strings.|

osxmetadata/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
""" osxmetadata version """
22

3-
__version__ = "0.99.22"
3+
__version__ = "0.99.23"

osxmetadata/attributes.py

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@
55
from collections import namedtuple # pylint: disable=syntax-error
66

77
from .classes import (
8-
_AttributeFinderColor,
98
_AttributeFinderInfo,
9+
_AttributeFinderInfoColor,
10+
_AttributeFinderInfoStationaryPad,
1011
_AttributeList,
1112
_AttributeTagsList,
1213
)
@@ -327,7 +328,7 @@
327328
append=False,
328329
update=False,
329330
help="Boolean indicating if this file is stationery. "
330-
+ "Note: this is not what the Finder uses for Stationary Pad flag.",
331+
+ "Note: this is not what the Finder uses for Stationary Pad flag. See also 'stationarypad'.",
331332
api_help=None,
332333
),
333334
"findercolor": Attribute(
@@ -337,7 +338,7 @@
337338
type_=str,
338339
list=False,
339340
as_list=False,
340-
class_=_AttributeFinderColor,
341+
class_=_AttributeFinderInfoColor,
341342
append=False,
342343
update=False,
343344
help="Color tag set by the Finder. Colors can also be set by _kMDItemUserTags. "
@@ -346,20 +347,20 @@
346347
+ "processing of FinderInfo color tag.",
347348
api_help=None,
348349
),
349-
# "stationarypad": Attribute(
350-
# name="stationarypad",
351-
# short_constant="stationarypad",
352-
# constant=FinderInfo,
353-
# type_=str,
354-
# list=False,
355-
# as_list=False,
356-
# class_=_AttributeFinderStationaryPad,
357-
# append=False,
358-
# update=False,
359-
# help="Marks the file as stationary (a template that can be re-used). "
360-
# +"Setting this to True is the same as checking the Stationary Pad box in Finder Info.",
361-
# api_help=None,
362-
# ),
350+
"stationarypad": Attribute(
351+
name="stationarypad",
352+
short_constant="stationarypad",
353+
constant=FinderInfo,
354+
type_=str,
355+
list=False,
356+
as_list=False,
357+
class_=_AttributeFinderInfoStationaryPad,
358+
append=False,
359+
update=False,
360+
help="Marks the file as stationary (a template that can be re-used). "
361+
+ "Setting this to True is the same as checking the 'Stationary Pad' box in Finder Info.",
362+
api_help=None,
363+
),
363364
}
364365

365366
# used for formatting output of --list

osxmetadata/classes.py

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,9 @@ def __eq__(self, other):
152152
self._load_data()
153153
return self.data == other
154154

155+
def __ne__(self, other):
156+
return not self.__eq__(other)
157+
155158

156159
class _AttributeFinderInfo:
157160
"""represents info stored in com.apple.FinderInfo"""
@@ -301,7 +304,7 @@ def set_finderinfo_stationarypad(self, value):
301304
"""set the Stationary Pad flag of com.apple.FinderInfo"""
302305

303306
value = 1 if value_to_bool(value) else 0
304-
307+
305308
# stationary pad is encoded as a single bit
306309
bit = bitstring.BitArray(uint=value, length=1)
307310

@@ -341,7 +344,7 @@ def __getitem__(self, key):
341344
return self.data[key]
342345

343346

344-
class _AttributeFinderColor(_AttributeFinderInfo):
347+
class _AttributeFinderInfoColor(_AttributeFinderInfo):
345348
def __init__(self, attribute, xattr_, osxmetadata_obj):
346349
super().__init__(attribute, xattr_, osxmetadata_obj)
347350

@@ -363,6 +366,35 @@ def __eq__(self, other):
363366
self._load_data()
364367
return self.data.get("color") == other
365368

369+
def __ne__(self, other):
370+
return not self.__eq__(other)
371+
372+
373+
class _AttributeFinderInfoStationaryPad(_AttributeFinderInfo):
374+
def __init__(self, attribute, xattr_, osxmetadata_obj):
375+
super().__init__(attribute, xattr_, osxmetadata_obj)
376+
377+
def set_value(self, value):
378+
self.data = {"stationarypad": value_to_bool(value)}
379+
self._write_data()
380+
381+
def get_value(self):
382+
self._load_data()
383+
return self.data.get("stationarypad", None)
384+
385+
def __repr__(self):
386+
return repr(self.get_value())
387+
388+
def __eq__(self, other):
389+
self._load_data()
390+
return self.data.get("stationarypad") == other
391+
392+
def __ne__(self, other):
393+
return not self.__eq__(other)
394+
395+
def __bool__(self):
396+
return bool(self.data.get("stationarypad", None))
397+
366398

367399
class _AttributeTagsList(_AttributeList, _AttributeFinderInfo):
368400
"""represents a _kMDItemUserTag attribute list"""

osxmetadata/osxmetadata.py

Lines changed: 30 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,19 @@
9191
# TODO: check what happens if OSXMetaData.__init__ called with invalid file--should result in error but saw one case where it didn't
9292
# TODO: cleartags does not always clear colors--this is a new behavior, did Mac OS change something in implementation of colors?
9393

94+
# all attributes that are part of com.apple.FinderInfo
95+
_FINDERINFO_ATTRIBUTES = ["finderinfo", "findercolor", "stationarypad"]
96+
97+
# all attributes that use an Attribute class (defined in attributes.py)
98+
_ATTRIBUTE_CLASS_ATTRIBUTES = ["tags", *_FINDERINFO_ATTRIBUTES]
99+
100+
# all attributes related to stationary pad
101+
_FINDERINFO_STATIONARYPAD_ATTRIBUTES = ["finderinfo", "stationarypad"]
102+
103+
# all attributes related to Finder color
104+
_FINDERINFO_COLOR_ATTRIBUTES = ["finderinfo", "findercolor"]
105+
106+
94107
# AppleScript for manipulating Finder comments
95108
_scpt_set_finder_comment = applescript.AppleScript(
96109
"""
@@ -117,37 +130,38 @@ class OSXMetaData:
117130
"""Create an OSXMetaData object to access file metadata"""
118131

119132
__slots__ = [
133+
"__init",
134+
"_attrs",
120135
"_fname",
121136
"_posix_name",
122-
"_attrs",
123137
"_tz_aware",
124-
"__init",
125138
"authors",
126139
"comment",
127140
"copyright",
128141
"creator",
129142
"description",
130143
"downloadeddate",
144+
"duedate",
145+
"findercolor",
131146
"findercomment",
147+
"finderinfo",
132148
"headline",
133149
"keywords",
134-
"tags",
135-
"wherefroms",
136-
"finderinfo",
137-
"duedate",
138-
"rating",
139150
"participants",
140151
"projects",
141-
"version",
152+
"rating",
142153
"stationary",
143-
"findercolor",
154+
"stationarypad",
155+
"tags",
156+
"version",
157+
"wherefroms",
144158
]
145159

146160
def __init__(self, fname, tz_aware=False):
147161
"""Create an OSXMetaData object to access file metadata
148162
fname: filename to operate on
149163
timezone_aware: bool; if True, date/time attributes will return
150-
timezone aware datetime.dateime attributes; if False (default)
164+
timezone aware datetime.datetime attributes; if False (default)
151165
date/time attributes will return timezone naive objects"""
152166
self._fname = pathlib.Path(fname)
153167
self._posix_name = self._fname.resolve().as_posix()
@@ -293,7 +307,7 @@ def get_attribute(self, attribute_name):
293307
attribute = ATTRIBUTES[attribute_name]
294308

295309
# user tags and finderinfo need special processing
296-
if attribute.name in ["tags", "finderinfo", "findercolor"]:
310+
if attribute.name in _ATTRIBUTE_CLASS_ATTRIBUTES:
297311
return getattr(self, attribute.name).get_value()
298312

299313
# must be a "normal" metadata attribute
@@ -363,7 +377,7 @@ def set_attribute(self, attribute_name, value):
363377
attribute = ATTRIBUTES[attribute_name]
364378

365379
# user tags and Finder info need special processing
366-
if attribute.name in ["tags", "finderinfo", "findercolor"]:
380+
if attribute.name in _ATTRIBUTE_CLASS_ATTRIBUTES:
367381
return getattr(self, attribute.name).set_value(value)
368382

369383
# verify type is correct
@@ -414,7 +428,7 @@ def append_attribute(self, attribute_name, value, update=False):
414428
else:
415429
new_value.extend(value)
416430
return self.tags.set_value(new_value)
417-
if attribute.name in ["finderinfo", "findercolor"]:
431+
if attribute.name in _FINDERINFO_ATTRIBUTES:
418432
raise ValueError(f"cannot append or update {attribute.name}")
419433

420434
value = validate_attribute_value(attribute, value)
@@ -505,14 +519,14 @@ def clear_attribute(self, attribute_name):
505519
# code following will also clear the attribute for Finder Comment
506520
self.clear_finder_comment(self._posix_name)
507521

508-
if attribute.name in ["finderinfo", "findercolor", "tags"]:
522+
if attribute.name in ["tags", *_FINDERINFO_COLOR_ATTRIBUTES]:
509523
# don't clear the entire FinderInfo attribute, just delete the bits we know about
510524
self.finderinfo.set_finderinfo_color(FINDER_COLOR_NONE)
511525

512-
if attribute.name in ["finderinfo", "stationarypad"]:
526+
if attribute.name in _FINDERINFO_STATIONARYPAD_ATTRIBUTES:
513527
self.finderinfo.set_finderinfo_stationarypad(False)
514528

515-
if attribute.name not in ["finderinfo", "findercolor"]:
529+
if attribute.name not in _FINDERINFO_ATTRIBUTES:
516530
# remove the entire attribute
517531
self._attrs.remove(attribute.constant)
518532
except (IOError, OSError):

tests/test_finderinfo.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,3 +187,27 @@ def test_finderinfo_stationarypad_and_color(temp_file):
187187
meta.finderinfo = {"stationarypad": False}
188188
assert meta.finderinfo.color == FINDER_COLOR_GREEN
189189
assert not meta.finderinfo.stationarypad
190+
191+
192+
def test_stationarypad(temp_file):
193+
"""Test stationarypad attribute"""
194+
from osxmetadata import OSXMetaData
195+
196+
meta = OSXMetaData(temp_file)
197+
assert not meta.stationarypad
198+
199+
meta.stationarypad = True
200+
assert meta.stationarypad
201+
202+
meta.stationarypad = 0
203+
assert not meta.stationarypad
204+
205+
meta.stationarypad = 1
206+
assert meta.stationarypad
207+
208+
meta.stationarypad = False
209+
assert not meta.stationarypad
210+
assert not meta.get_attribute("stationarypad")
211+
212+
meta.set_attribute("stationarypad", True)
213+
assert meta.get_attribute("stationarypad")

0 commit comments

Comments
 (0)