~deb-thumbnailer-team/deb-thumbnailer/main

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
#!/usr/bin/python
# -*- coding: utf-8 -*-

__author__="Alex Eftimie - alex@eftimie.ro"
__date__ ="$Jun 28, 2010 05:00:40 PM$"

import os
import shutil
import string
import subprocess
import sys

import tempfile
import urllib
import fnmatch

from gi.repository import Gtk
from gi.repository import Gio
from gi.repository import GdkPixbuf

import hashlib
import logging
import xdg.BaseDirectory

VERSION='0.8.9'

#LOG_FILENAME = '/tmp/deb-thumbnailer.log'
#logging.basicConfig(filename=LOG_FILENAME,level=logging.DEBUG)

class DebThumbnailer(object):
    """ create thumbnails from a given deb """

    # the gconf path
    CONFIG_DIR = '/apps/deb-thumbnailer'

    # special names to watch for 
    SPECIAL_ICON_NAMES = [ "opera-browser", 
                           "apps/opera.png",
                           "product_logo_128.png",
                           "AdobeAIR.png",
                           "AdobeReader[0-9]*\.png"
                          ]
    
    def __init__(self, debfile):
        self._debfile = debfile
        self.icon_theme = Gtk.IconTheme.get_default()
        # init settings for "icon_default"
        #  If True the generic deb icon is in the background and the app icon
        #  in the forground. Otherwise its reversed.
        settings = Gio.Settings("com.ubuntu.deb-thumbnailer")
        self._app_icon_is_foreground = settings.get_boolean("icon-default")

    @property
    def pkgname(self):
        return self._pkgname_from_deb()

    def _pkgname_from_deb(self):
        """ return the binary package name of the given deb """
        pkgname = subprocess.Popen(
            ["dpkg-deb", "-f", self._debfile, "Package"],
            stdout=subprocess.PIPE).communicate()[0].strip()
        return pkgname

    def pkg_overlay(self, background, source, destination, percent=0.5, 
                size=48, overlay_alpha=180):
        """ Merges background with source scalled by percent, 
            aligned bottom-right 
            Saves the result into destination file
        """
        if not self._app_icon_is_foreground:
            background, source = source, background
        bg = GdkPixbuf.Pixbuf.new_from_file(background)
        if not self._app_icon_is_foreground:
            bg = bg.scale_simple(size, size, GdkPixbuf.InterpType.BILINEAR)
        if source != '':
            ov = GdkPixbuf.Pixbuf.new_from_file(source)
            # calc position relative to bg
            width, height = bg.get_width(), bg.get_height()
            nw, nh = int(percent * width), int(percent * height)
            x, y = width - nw, height - nh
            # scale source relative to bg size
            scale = nw/float(ov.get_width())
            ov.composite(bg, x, y, nw, nh, x, y, scale, scale,
                         GdkPixbuf.InterpType.BILINEAR, overlay_alpha)
        bg.savev(destination, 'png', [], [])

    def get_filelist_from_deb(self):
        """ return all files inside a deb as a string list """
        # a alternative is apt_inst.DebFile()
        p1 = subprocess.Popen(["dpkg", "--fsys-tarfile", self._debfile],
                              stdout=subprocess.PIPE)
        p2 = subprocess.Popen(["tar", "tf","-"], 
                              stdin=p1.stdout,
                              stdout=subprocess.PIPE)
        return map(string.strip, p2.stdout.readlines())

    def extract_file_from_deb(self, filename, targetdir="."):
        """ extract the given filename into targetdir """
        # a alternative is apt_inst.DebFile() (again)
        p1 = subprocess.Popen(["dpkg", "--fsys-tarfile", self._debfile],
                              stdout=subprocess.PIPE)
        p2 = subprocess.Popen(["tar", "xf","-", filename],
                              stdin=p1.stdout,
                              stdout=subprocess.PIPE,
                              cwd=targetdir)
        return p2.wait()

    def get_icons_from_deb(self, tmpdir):
        """ extract icons from deb package """
        logging.debug("get_icons_from_deb() %s in %s" % (self._debfile, tmpdir))
        icons = []
        debfile = os.path.abspath(self._debfile)
        if not os.path.exists(debfile):
            raise IOError("file '%s' not found" % debfile)
        for line in self.get_filelist_from_deb():
            # FIXME: also test for SPECIAL_ICON_NAMES
            if (line.startswith("./usr/share/icons") or 
                line.startswith("./usr/share/pixmaps")):
                icons.append(line)
            for i in self.SPECIAL_ICON_NAMES:
                if (line.find(i) != -1):
                    icons.append(line)
        icons.sort()
        logging.debug("found icons: '%s'" % icons)
        # FIXME: this will only extract the last found icon, try to
        #        be smarter
        if icons:
            res = self.extract_file_from_deb(icons[-1], tmpdir)
        return icons

    def get_hashname_for_debfile(self):
        md = hashlib.md5()
        md.update(os.path.basename(self._debfile))
        hash_name = md.hexdigest()
        if not self._app_icon_is_foreground:
            hash_name += '1'
        return hash_name

    def process_deb(self, size=48):
        """ Unpacks a .deb file and returns the icon path """
        debfile = self._debfile
        tmpdir = tempfile.mkdtemp('-deb-thumbnailer')
        icons = []
    
        # Get package name safely (read from debian/control)
        pname = self._pkgname_from_deb()
    
        # asking system for app icon (faster)
        if pname == 'deb-thumbnailer':
            icons = ['', self.icon_theme.lookup_icon("emblem-default", size, 0).get_filename()]
        else:
            if pname == 'opera':
                pname = 'opera-browser'
            iconInfo = self.icon_theme.lookup_icon(pname, size, 0)
            if iconInfo:
                icon = iconInfo.get_filename()
                icons = ['', icon]
        if not len(icons) == 0:
            iconFile = os.path.split(icons[1])[1]
            shutil.copy(icons[1], os.path.join(tmpdir, iconFile))
            icons[1] = iconFile
    
        # searching in package
        if len(icons) == 0:
            icons = self.get_icons_from_deb(tmpdir)

        # Go back 
        if len(icons) > 0:
            tmpfile = tempfile.mktemp('-deb-thumbnailer')
            shutil.copyfile(tmpdir + '/' + icons[-1].replace("./", ""), tmpfile)
            # FIXME: move this info some generic quirks code
            # fix for opera
            if pname == 'opera':
                for i, icon in enumerate(icons):
                    if 'browser' in icon:
                        shutil.copyfile(tmpdir + '/' + icon.replace("./", ""), tmpfile)
            shutil.rmtree(tmpdir)
            return tmpfile
        else:
            logging.debug("No icon found for %s" % os.path.split(debfile)[1])
        shutil.rmtree(tmpdir)
        return None
    
    def get_package_icon(self, size):
        """ return the filename of the pkg icon """
        icon_info = self.icon_theme.lookup_icon("application-x-deb", size, 0)
        pkgIcon = icon_info.get_filename()
        return pkgIcon

    def create_overlay_icon_for_deb(self, output, size):
        """ this creates a output icon file for the deb """
        picon = self.get_package_icon(size)
        icon = self.process_deb(size=size)
        if icon:
            self.pkg_overlay(picon, icon, output, size=size)
            if os.path.exists(icon):
                os.unlink(icon)

    def cache_icon(self, icon):
        """ this caches the a icon in the users xdg cachedir along 
        with a custom gvfs attribute"""
        targetdir = os.path.join(xdg.BaseDirectory.xdg_cache_home, 
                                 "deb-thumbnailer")
        if not os.path.exists(targetdir):
            os.makedirs(targetdir)
        hash_name = self.get_hashname_for_debfile()+'.png'
        shutil.copy(icon, os.path.join(targetdir, hash_name))
        dt_path = os.path.join(targetdir, hash_name)
        if os.path.exists(dt_path):
            f=Gio.File.new_for_path(self._debfile)
            f.set_attribute_string("metadata::custom-icon", 
                                   "file://%s" % dt_path, 0, None)
        return dt_path

# helpers for the cmdline
def reset_custom_icon_for_deb(debfile):
    # FIXME: Gio has a bug that it does not allow valuep to be None
    #f=Gio.File.new_for_path(debfile)
    #f.set_attribute("metadata::custom-icon", 
    #                Gio.FileAttributeType.INVALID,
    #                None, 
    #                0, None)
    subprocess.call(["gvfs-set-attribute", debfile, 
                     "-t","unset", "metadata::custom-icon"])
def reset_icons_in_path(path="~"):
    """ reseting all icons to .deb only in home folder """
    path = os.path.expanduser(path)
    for path, dirnames, filenames in os.walk(path):
        if path.endswith("Trash"):
            continue
        for deb in fnmatch.filter(filenames, "*.deb"):
            if os.path.exists(deb):
                reset_custom_icon_for_deb(deb)

def rm_thumbnails():
    reset_icons_in_path()
    targetdir = os.path.join(xdg.BaseDirectory.xdg_cache_home,
                             "deb-thumbnailer")
    if os.path.isdir(targetdir):
        shutil.rmtree(targetdir)

if __name__ == "__main__":
    proceed = False
    # nautilus calls it with input, output, size
    if len(sys.argv) == 4:
        #print sys.argv
        try:
            size = int(sys.argv[3])
        except ValueError:
            size = 128
        input = sys.argv[1].replace('file://', '')
        if not os.path.exists(input):
            input = urllib.url2pathname(input)
        output = urllib.url2pathname(sys.argv[2]).replace('file://', '')
        thumbnailer = DebThumbnailer(input)
        thumbnailer.create_overlay_icon_for_deb(output, size)
        # output is always there, looks like nautilus is creating it, so
        # we need to check if the data makes sense 
        # FIXME: do this in a more elegant way
        if os.path.exists(output) and os.path.getsize(output) > 0:
            thumbnailer.cache_icon(output)
            sys.exit(0)
        # tell nautilus we have no data
        sys.exit(1)
    # options?
    elif len(sys.argv) > 1 and sys.argv[1] == '--recreate':
        rm_thumbnails()
        sys.exit(0)
    # debfile?
    elif len(sys.argv) > 1 and os.path.exists(sys.argv[1]):
        input = sys.argv[1]
        if os.path.splitext(input)[1] == '.deb':
            size = 48
            thumbnailer = DebThumbnailer(input)
            output = thumbnailer.pkgname + ".icon.png"
            thumbnailer.create_overlay_icon_for_deb(output, size)
        else:
            logging.debug("File %s is not an .deb file" % os.path.basename(input))
    else:
        print "Deb thumbnailer %s" % VERSION
        print "Usage: %s input output size" % sys.argv[0]
        print "Use --recreate to delete the thumbnail cache"
        sys.exit(1)