finder_colors icon indicating copy to clipboard operation
finder_colors copied to clipboard

Catalina issue

Open SamSimp opened this issue 5 years ago • 4 comments

Hi - when run against a file with a colour tag I get the error:

unsupported operand type(s) for &: 'str' and 'int'

I wonder if you you are still maintaining the code and if I can assist with troubleshooting?

BR

Sam

SamSimp avatar Jan 10 '20 21:01 SamSimp

I've had some limited success rewriting bits of this. However, I'm not expert, and am not 100% sure about what's going on.

It seems that either OSX has changed the way tags are stored or a newer version of xattr outputs them in a different format. At the moment, xattr reads (and needs to write) a bytes object. A first issue (with the "get colour" function) seems to arise from calling ord() on the bytes object. A second issue (when trying to change the colour) seems to arise from manipulating the bytes object such that a new value can be added.

You can solve the first problem (and retrieve the colour info) simply by removing the ord stuff. I'm not sure what the masking does or if this will cause a problem but this works fine for me.

As for the second problem: the line that throws the error is trying to insert the new colour code into the appropriate place in the bytes object by concatenating [first half of the old object] + [new code, at index 9] + [second half of old the object]. It seems Python is only happy for you to concatenate strings in this context, hence the error. chr(COLORS[color] is a string, whereas previous[:9] is still bytes. Assuming the existing code worked in the first place, it looks like either:

(i) Everything used to be bytes, and you used to be able to concatenate them. (Unlikely.) (ii) Everything used to be strings, and you used to be able to concatenate them + read/write strings with xattr into OSX easily. (Likely.)

Anyway, to get it working, you first have to ensure that the new object to be written, new, is a bytes object, otherwise xattr won't work. I just used bytes() on it with utf8 encoding. I experienced issues simply calling str() on previous[:9] and then converting back to bytes. Something odd happens and you end up with doubled \\ between the bytes. In the end, since these are usually 00 anyway if you don't use other tags (I don't know what the other indices do, tbh), I figured I may as well just pad the bytes object with sufficient 00s either side of the new colour code, then convert that to bytes. And it worked!

I figured this out by inspecting the BLANK object in the original code. That object is a string! Hence I could get colours to assign when the folder didn't already have a colour on it, but the program crashed when they did. Essentially, I removed all differentiation between cases where the folder has a colour and doesn't, and just made it overwrite the whole tag with a new tag containing the colour.

This is working well enough for my purposes, but obviously isn't an ideal solution. I hope someone who understands what's going on better than I do can come and take a look.

Here's my modified code:

_FINDER_INFO_TAG = u'com.apple.FinderInfo'

COLORS = {'none': 0, 'gray': 2, 'green': 4, 'purple': 6, 
          'blue': 8, 'yellow': 10, 'red': 12, 'orange': 14}
NAMES  = {0: 'none', 2: 'gray', 4: 'green', 6: 'purple', 
          8: 'blue', 10 : 'yellow', 12 : 'red', 14 : 'orange' }

#retrieve colour tag
def get(filename):
    try:
        attrs = xattr(filename)
        color_num = attrs.get(_FINDER_INFO_TAG)[9] #remove mask + ord()
        return NAMES[color_num]

    except IOError as err:
        if err.errno == 93: # attribute not found...
            return NAMES[0]
        # else
        raise err
    except KeyError as err:
        return NAMES[0]

#set colour tag
def set(filename, color):
    attrs = xattr(filename)
    new = bytes(9*chr(0) + chr(COLORS[color]) + 22*chr(0),'utf8')
"""
Just overwrite all the tags with a new object. Pad either side of the colour code at index 9 with 00 and convert to bytes so xattr can write.
"""
    attrs.set(_FINDER_INFO_TAG, new)

lexipenia avatar Mar 17 '20 14:03 lexipenia

@danthedeckie Are you still around? :)

lexipenia avatar Mar 17 '20 14:03 lexipenia

Just bumped into the the same issues, the following worked for me. Unfortunately the underlying macos code only returns one color tag even if multiple set:

diff --git a/finder_colors.py b/finder_colors.py
index 95f3a4d..c4dd653 100755
--- a/finder_colors.py
+++ b/finder_colors.py
@@ -69,7 +69,7 @@ def get(filename):

     try:
         attrs = xattr(filename)
-        color_num = ord(attrs.get(_FINDER_INFO_TAG)[9]) & 14
+        color_num = attrs.get(_FINDER_INFO_TAG)[9] & 14
         # & 14 to mask with "1110" (ie ignore all other bits).
         return NAMES[color_num]

@@ -98,7 +98,7 @@ def set(filename, color): # pylint: disable=W0622
         + prev_color_extra_bits) \
         + previous[10:]

-    attrs.set(_FINDER_INFO_TAG, new)
+    attrs.set(_FINDER_INFO_TAG, new.encode())
     return new

zoltansebestyen avatar Mar 25 '20 19:03 zoltansebestyen

The new location for tags (colors as well as "Home", "Work", etc. arbitrary labels), is the xattr com.apple.metadata:_kMDItemUserTags:, while the com.apple.FinderInfo seems to only contain the last-set color tag, and even then it seems to only contain it if that metadata attribute existed previously (it is not set for files that didn't have that xattr prior to color assignment in Finder, it seems). The attribute appears to be a flat bplist, at least for Big Sur, here's an example of a file I set to Red, then Orange, then Yellow. You can now have multiple colors (tags) assigned.

com.apple.FinderInfo:
00000000  00 00 00 00 00 00 00 00 00 0A 00 00 00 00 00 00  |................|
00000010  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00  |................|
00000020
com.apple.metadata:_kMDItemUserTags:
00000000  62 70 6C 69 73 74 30 30 A3 01 02 03 55 52 65 64  |bplist00....URed|
00000010  0A 36 58 4F 72 61 6E 67 65 0A 37 58 59 65 6C 6C  |.6XOrange.7XYell|
00000020  6F 77 0A 35 08 0C 12 1B 00 00 00 00 00 00 01 01  |ow.5............|
00000030  00 00 00 00 00 00 00 04 00 00 00 00 00 00 00 00  |................|
00000040  00 00 00 00 00 00 00 24                          |.......$|
00000048

Here is the mdls output for that file:

kMDItemUserTags                        = (
    Red,
    Orange,
    Yellow
)

ido avatar Jul 31 '21 17:07 ido