1#
2# BitBake Toaster Implementation
3#
4# Copyright (C) 2013        Intel Corporation
5#
6# SPDX-License-Identifier: GPL-2.0-only
7#
8
9import re
10
11from django.db.models import F, Q, Sum
12from django.db import IntegrityError
13from django.shortcuts import render, redirect, get_object_or_404
14from django.utils.http import urlencode
15from orm.models import Build, Target, Task, Layer, Layer_Version, Recipe
16from orm.models import LogMessage, Variable, Package_Dependency, Package
17from orm.models import Task_Dependency, Package_File
18from orm.models import Target_Installed_Package, Target_File
19from orm.models import TargetKernelFile, TargetSDKFile, Target_Image_File
20from orm.models import BitbakeVersion, CustomImageRecipe
21
22from django.urls import reverse, resolve
23from django.core.exceptions import ObjectDoesNotExist
24from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
25from django.http import HttpResponseNotFound, JsonResponse
26from django.utils import timezone
27from datetime import timedelta, datetime
28from toastergui.templatetags.projecttags import json as jsonfilter
29from decimal import Decimal
30import json
31import os
32from os.path import dirname
33import mimetypes
34
35import logging
36
37logger = logging.getLogger("toaster")
38
39# Project creation and managed build enable
40project_enable = ('1' == os.environ.get('TOASTER_BUILDSERVER'))
41is_project_specific = ('1' == os.environ.get('TOASTER_PROJECTSPECIFIC'))
42
43class MimeTypeFinder(object):
44    # setting this to False enables additional non-standard mimetypes
45    # to be included in the guess
46    _strict = False
47
48    # returns the mimetype for a file path as a string,
49    # or 'application/octet-stream' if the type couldn't be guessed
50    @classmethod
51    def get_mimetype(self, path):
52        guess = mimetypes.guess_type(path, self._strict)
53        guessed_type = guess[0]
54        if guessed_type is None:
55            guessed_type = 'application/octet-stream'
56        return guessed_type
57
58# single point to add global values into the context before rendering
59def toaster_render(request, page, context):
60    context['project_enable'] = project_enable
61    context['project_specific'] = is_project_specific
62    return render(request, page, context)
63
64
65# all new sessions should come through the landing page;
66# determine in which mode we are running in, and redirect appropriately
67def landing(request):
68    # in build mode, we redirect to the command-line builds page
69    # if there are any builds for the default (cli builds) project
70    default_project = Project.objects.get_or_create_default_project()
71    default_project_builds = Build.objects.filter(project = default_project)
72
73    # we only redirect to projects page if there is a user-generated project
74    num_builds = Build.objects.all().count()
75    user_projects = Project.objects.filter(is_default = False)
76    has_user_project = user_projects.count() > 0
77
78    if num_builds == 0 and has_user_project:
79        return redirect(reverse('all-projects'), permanent = False)
80
81    if num_builds > 0:
82        return redirect(reverse('all-builds'), permanent = False)
83
84    context = {'lvs_nos' : Layer_Version.objects.all().count()}
85
86    return toaster_render(request, 'landing.html', context)
87
88def objtojson(obj):
89    from django.db.models.query import QuerySet
90    from django.db.models import Model
91
92    if isinstance(obj, datetime):
93        return obj.isoformat()
94    elif isinstance(obj, timedelta):
95        return obj.total_seconds()
96    elif isinstance(obj, QuerySet) or isinstance(obj, set):
97        return list(obj)
98    elif isinstance(obj, Decimal):
99        return str(obj)
100    elif type(obj).__name__ == "RelatedManager":
101        return [x.pk for x in obj.all()]
102    elif hasattr( obj, '__dict__') and isinstance(obj, Model):
103        d = obj.__dict__
104        nd = dict(d)
105        for di in d.keys():
106            if di.startswith("_"):
107                del nd[di]
108            elif isinstance(d[di], Model):
109                nd[di] = d[di].pk
110            elif isinstance(d[di], int) and hasattr(obj, "get_%s_display" % di):
111                nd[di] = getattr(obj, "get_%s_display" % di)()
112        return nd
113    elif isinstance( obj, type(lambda x:x)):
114        import inspect
115        return inspect.getsourcelines(obj)[0]
116    else:
117        raise TypeError("Unserializable object %s (%s) of type %s" % ( obj, dir(obj), type(obj)))
118
119
120def _lv_to_dict(prj, x = None):
121    if x is None:
122        def wrapper(x):
123            return _lv_to_dict(prj, x)
124        return wrapper
125
126    return {"id": x.pk,
127            "name": x.layer.name,
128            "tooltip": "%s | %s" % (x.layer.vcs_url,x.get_vcs_reference()),
129            "detail": "(%s" % x.layer.vcs_url + (")" if x.release is None else " | "+x.get_vcs_reference()+")"),
130            "giturl": x.layer.vcs_url,
131            "layerdetailurl" : reverse('layerdetails', args=(prj.id,x.pk)),
132            "revision" : x.get_vcs_reference(),
133           }
134
135
136def _build_page_range(paginator, index = 1):
137    try:
138        page = paginator.page(index)
139    except PageNotAnInteger:
140        page = paginator.page(1)
141    except  EmptyPage:
142        page = paginator.page(paginator.num_pages)
143
144
145    page.page_range = [page.number]
146    crt_range = 0
147    for i in range(1,5):
148        if (page.number + i) <= paginator.num_pages:
149            page.page_range = page.page_range + [ page.number + i]
150            crt_range +=1
151        if (page.number - i) > 0:
152            page.page_range =  [page.number -i] + page.page_range
153            crt_range +=1
154        if crt_range == 4:
155            break
156    return page
157
158
159def _verify_parameters(g, mandatory_parameters):
160    miss = []
161    for mp in mandatory_parameters:
162        if not mp in g:
163            miss.append(mp)
164    if len(miss):
165        return miss
166    return None
167
168def _redirect_parameters(view, g, mandatory_parameters, *args, **kwargs):
169    try:
170        from urllib import unquote, urlencode
171    except ImportError:
172        from urllib.parse import unquote, urlencode
173    url = reverse(view, kwargs=kwargs)
174    params = {}
175    for i in g:
176        params[i] = g[i]
177    for i in mandatory_parameters:
178        if not i in params:
179            params[i] = unquote(str(mandatory_parameters[i]))
180
181    return redirect(url + "?%s" % urlencode(params), permanent = False, **kwargs)
182
183class RedirectException(Exception):
184    def __init__(self, view, g, mandatory_parameters, *args, **kwargs):
185        super(RedirectException, self).__init__()
186        self.view = view
187        self.g = g
188        self.mandatory_parameters = mandatory_parameters
189        self.oargs  = args
190        self.okwargs = kwargs
191
192    def get_redirect_response(self):
193        return _redirect_parameters(self.view, self.g, self.mandatory_parameters, self.oargs, **self.okwargs)
194
195FIELD_SEPARATOR = ":"
196AND_VALUE_SEPARATOR = "!"
197OR_VALUE_SEPARATOR = "|"
198DESCENDING = "-"
199
200def __get_q_for_val(name, value):
201    if "OR" in value or "AND" in value:
202        result = None
203        for x in value.split("OR"):
204             x = __get_q_for_val(name, x)
205             result = result | x if result else x
206        return result
207    if "AND" in value:
208        result = None
209        for x in value.split("AND"):
210            x = __get_q_for_val(name, x)
211            result = result & x if result else x
212        return result
213    if value.startswith("NOT"):
214        value = value[3:]
215        if value == 'None':
216            value = None
217        kwargs = { name : value }
218        return ~Q(**kwargs)
219    else:
220        if value == 'None':
221            value = None
222        kwargs = { name : value }
223        return Q(**kwargs)
224
225def _get_filtering_query(filter_string):
226
227    search_terms = filter_string.split(FIELD_SEPARATOR)
228    and_keys = search_terms[0].split(AND_VALUE_SEPARATOR)
229    and_values = search_terms[1].split(AND_VALUE_SEPARATOR)
230
231    and_query = None
232    for kv in zip(and_keys, and_values):
233        or_keys = kv[0].split(OR_VALUE_SEPARATOR)
234        or_values = kv[1].split(OR_VALUE_SEPARATOR)
235        query = None
236        for key, val in zip(or_keys, or_values):
237            x = __get_q_for_val(key, val)
238            query = query | x if query else x
239
240        and_query = and_query & query if and_query else query
241
242    return and_query
243
244def _get_toggle_order(request, orderkey, toggle_reverse = False):
245    if toggle_reverse:
246        return "%s:+" % orderkey if request.GET.get('orderby', "") == "%s:-" % orderkey else "%s:-" % orderkey
247    else:
248        return "%s:-" % orderkey if request.GET.get('orderby', "") == "%s:+" % orderkey else "%s:+" % orderkey
249
250def _get_toggle_order_icon(request, orderkey):
251    if request.GET.get('orderby', "") == "%s:+"%orderkey:
252        return "down"
253    elif request.GET.get('orderby', "") == "%s:-"%orderkey:
254        return "up"
255    else:
256        return None
257
258# we check that the input comes in a valid form that we can recognize
259def _validate_input(field_input, model):
260
261    invalid = None
262
263    if field_input:
264        field_input_list = field_input.split(FIELD_SEPARATOR)
265
266        # Check we have only one colon
267        if len(field_input_list) != 2:
268            invalid = "We have an invalid number of separators: " + field_input + " -> " + str(field_input_list)
269            return None, invalid
270
271        # Check we have an equal number of terms both sides of the colon
272        if len(field_input_list[0].split(AND_VALUE_SEPARATOR)) != len(field_input_list[1].split(AND_VALUE_SEPARATOR)):
273            invalid = "Not all arg names got values"
274            return None, invalid + str(field_input_list)
275
276        # Check we are looking for a valid field
277        valid_fields = [f.name for f in model._meta.get_fields()]
278        for field in field_input_list[0].split(AND_VALUE_SEPARATOR):
279            if True in [field.startswith(x) for x in valid_fields]:
280                break
281        else:
282           return None, (field, valid_fields)
283
284    return field_input, invalid
285
286# uses search_allowed_fields in orm/models.py to create a search query
287# for these fields with the supplied input text
288def _get_search_results(search_term, queryset, model):
289    search_object = None
290    for st in search_term.split(" "):
291        queries = None
292        for field in model.search_allowed_fields:
293            query = Q(**{field + '__icontains': st})
294            queries = queries | query if queries else query
295
296        search_object = search_object & queries if search_object else queries
297    queryset = queryset.filter(search_object)
298
299    return queryset
300
301
302# function to extract the search/filter/ordering parameters from the request
303# it uses the request and the model to validate input for the filter and orderby values
304def _search_tuple(request, model):
305    ordering_string, invalid = _validate_input(request.GET.get('orderby', ''), model)
306    if invalid:
307        raise BaseException("Invalid ordering model:" + str(model) + str(invalid))
308
309    filter_string, invalid = _validate_input(request.GET.get('filter', ''), model)
310    if invalid:
311        raise BaseException("Invalid filter " + str(invalid))
312
313    search_term = request.GET.get('search', '')
314    return (filter_string, search_term, ordering_string)
315
316
317# returns a lazy-evaluated queryset for a filter/search/order combination
318def _get_queryset(model, queryset, filter_string, search_term, ordering_string, ordering_secondary=''):
319    if filter_string:
320        filter_query = _get_filtering_query(filter_string)
321        queryset = queryset.filter(filter_query)
322    else:
323        queryset = queryset.all()
324
325    if search_term:
326        queryset = _get_search_results(search_term, queryset, model)
327
328    if ordering_string:
329        column, order = ordering_string.split(':')
330        if column == re.sub('-','',ordering_secondary):
331            ordering_secondary=''
332        if order.lower() == DESCENDING:
333            column = '-' + column
334        if ordering_secondary:
335            queryset = queryset.order_by(column, ordering_secondary)
336        else:
337            queryset = queryset.order_by(column)
338
339    # insure only distinct records (e.g. from multiple search hits) are returned
340    return queryset.distinct()
341
342# returns the value of entries per page and the name of the applied sorting field.
343# if the value is given explicitly as a GET parameter it will be the first selected,
344# otherwise the cookie value will be used.
345def _get_parameters_values(request, default_count, default_order):
346    current_url = resolve(request.path_info).url_name
347    pagesize = request.GET.get('count', request.session.get('%s_count' % current_url, default_count))
348    orderby = request.GET.get('orderby', request.session.get('%s_orderby' % current_url, default_order))
349    return (pagesize, orderby)
350
351
352# set cookies for parameters. this is usefull in case parameters are set
353# manually from the GET values of the link
354def _set_parameters_values(pagesize, orderby, request):
355    from django.urls import resolve
356    current_url = resolve(request.path_info).url_name
357    request.session['%s_count' % current_url] = pagesize
358    request.session['%s_orderby' % current_url] =orderby
359
360# date range: normalize GUI's dd/mm/yyyy to date object
361def _normalize_input_date(date_str,default):
362    date_str=re.sub('/', '-', date_str)
363    # accept dd/mm/yyyy to d/m/yy
364    try:
365        date_in = datetime.strptime(date_str, "%d-%m-%Y")
366    except ValueError:
367        # courtesy try with two digit year
368        try:
369            date_in = datetime.strptime(date_str, "%d-%m-%y")
370        except ValueError:
371            return default
372    date_in = date_in.replace(tzinfo=default.tzinfo)
373    return date_in
374
375# convert and normalize any received date range filter, for example:
376# "completed_on__gte!completed_on__lt:01/03/2015!02/03/2015_daterange" to
377# "completed_on__gte!completed_on__lt:2015-03-01!2015-03-02"
378def _modify_date_range_filter(filter_string):
379    # was the date range radio button selected?
380    if 0 >  filter_string.find('_daterange'):
381        return filter_string,''
382    # normalize GUI dates to database format
383    filter_string = filter_string.replace('_daterange','').replace(':','!');
384    filter_list = filter_string.split('!');
385    if 4 != len(filter_list):
386        return filter_string
387    today = timezone.localtime(timezone.now())
388    date_id = filter_list[1]
389    date_from = _normalize_input_date(filter_list[2],today)
390    date_to = _normalize_input_date(filter_list[3],today)
391    # swap dates if manually set dates are out of order
392    if  date_to < date_from:
393        date_to,date_from = date_from,date_to
394    # convert to strings, make 'date_to' inclusive by moving to begining of next day
395    date_from_str = date_from.strftime("%Y-%m-%d")
396    date_to_str = (date_to+timedelta(days=1)).strftime("%Y-%m-%d")
397    filter_string=filter_list[0]+'!'+filter_list[1]+':'+date_from_str+'!'+date_to_str
398    daterange_selected = re.sub('__.*','', date_id)
399    return filter_string,daterange_selected
400
401def _add_daterange_context(queryset_all, request, daterange_list):
402    # calculate the exact begining of local today and yesterday
403    today_begin = timezone.localtime(timezone.now())
404    yesterday_begin = today_begin - timedelta(days=1)
405    # add daterange persistent
406    context_date = {}
407    context_date['last_date_from'] = request.GET.get('last_date_from',timezone.localtime(timezone.now()).strftime("%d/%m/%Y"))
408    context_date['last_date_to'  ] = request.GET.get('last_date_to'  ,context_date['last_date_from'])
409    # calculate the date ranges, avoid second sort for 'created'
410    # fetch the respective max range from the database
411    context_date['daterange_filter']=''
412    for key in daterange_list:
413        queryset_key = queryset_all.order_by(key)
414        try:
415            context_date['dateMin_'+key]=timezone.localtime(getattr(queryset_key.first(),key)).strftime("%d/%m/%Y")
416        except AttributeError:
417            context_date['dateMin_'+key]=timezone.localtime(timezone.now())
418        try:
419            context_date['dateMax_'+key]=timezone.localtime(getattr(queryset_key.last(),key)).strftime("%d/%m/%Y")
420        except AttributeError:
421            context_date['dateMax_'+key]=timezone.localtime(timezone.now())
422    return context_date,today_begin,yesterday_begin
423
424
425##
426# build dashboard for a single build, coming in as argument
427# Each build may contain multiple targets and each target
428# may generate multiple image files. display them all.
429#
430def builddashboard( request, build_id ):
431    template = "builddashboard.html"
432    if Build.objects.filter( pk=build_id ).count( ) == 0 :
433        return redirect( builds )
434    build = Build.objects.get( pk = build_id );
435    layerVersionId = Layer_Version.objects.filter( build = build_id );
436    recipeCount = Recipe.objects.filter( layer_version__id__in = layerVersionId ).count( );
437    tgts = Target.objects.filter( build_id = build_id ).order_by( 'target' );
438
439    # set up custom target list with computed package and image data
440    targets = []
441    ntargets = 0
442
443    # True if at least one target for this build has an SDK artifact
444    # or image file
445    has_artifacts = False
446
447    for t in tgts:
448        elem = {}
449        elem['target'] = t
450
451        target_has_images = False
452        image_files = []
453
454        npkg = 0
455        pkgsz = 0
456        package = None
457        # Chunk the query to avoid "too many SQL variables" error
458        package_set = t.target_installed_package_set.all()
459        package_set_len = len(package_set)
460        for ps_start in range(0,package_set_len,500):
461            ps_stop = min(ps_start+500,package_set_len)
462            for package in Package.objects.filter(id__in = [x.package_id for x in package_set[ps_start:ps_stop]]):
463                pkgsz = pkgsz + package.size
464                if package.installed_name:
465                    npkg = npkg + 1
466        elem['npkg'] = npkg
467        elem['pkgsz'] = pkgsz
468        ti = Target_Image_File.objects.filter(target_id = t.id)
469        for i in ti:
470            ndx = i.file_name.rfind('/')
471            if ndx < 0:
472                ndx = 0;
473            f = i.file_name[ndx + 1:]
474            image_files.append({
475                'id': i.id,
476                'path': f,
477                'size': i.file_size,
478                'suffix': i.suffix
479            })
480        if len(image_files) > 0:
481            target_has_images = True
482        elem['targetHasImages'] = target_has_images
483
484        elem['imageFiles'] = image_files
485        elem['target_kernel_artifacts'] = t.targetkernelfile_set.all()
486
487        target_sdk_files = t.targetsdkfile_set.all()
488        target_sdk_artifacts_count = target_sdk_files.count()
489        elem['target_sdk_artifacts_count'] = target_sdk_artifacts_count
490        elem['target_sdk_artifacts'] = target_sdk_files
491
492        if target_has_images or target_sdk_artifacts_count > 0:
493            has_artifacts = True
494
495        targets.append(elem)
496
497    ##
498    # how many packages in this build - ignore anonymous ones
499    #
500
501    packageCount = 0
502    packages = Package.objects.filter( build_id = build_id )
503    for p in packages:
504        if ( p.installed_name ):
505            packageCount = packageCount + 1
506
507    logmessages = list(LogMessage.objects.filter( build = build_id ))
508
509    context = {
510            'build'           : build,
511            'project'         : build.project,
512            'hasArtifacts'    : has_artifacts,
513            'ntargets'        : ntargets,
514            'targets'         : targets,
515            'recipecount'     : recipeCount,
516            'packagecount'    : packageCount,
517            'logmessages'     : logmessages,
518    }
519    return toaster_render( request, template, context )
520
521
522
523def generateCoveredList2( revlist = None ):
524    if not revlist:
525        revlist = []
526    covered_list =  [ x for x in revlist if x.outcome == Task.OUTCOME_COVERED ]
527    while len(covered_list):
528        revlist =  [ x for x in revlist if x.outcome != Task.OUTCOME_COVERED ]
529        if len(revlist) > 0:
530            return revlist
531
532        newlist = _find_task_revdep_list(covered_list)
533
534        revlist = list(set(revlist + newlist))
535        covered_list =  [ x for x in revlist if x.outcome == Task.OUTCOME_COVERED ]
536    return revlist
537
538def task( request, build_id, task_id ):
539    template = "task.html"
540    tasks_list = Task.objects.filter( pk=task_id )
541    if tasks_list.count( ) == 0:
542        return redirect( builds )
543    task_object = tasks_list[ 0 ];
544    dependencies = sorted(
545        _find_task_dep( task_object ),
546        key=lambda t:'%s_%s %s'%(t.recipe.name, t.recipe.version, t.task_name))
547    reverse_dependencies = sorted(
548        _find_task_revdep( task_object ),
549        key=lambda t:'%s_%s %s'%( t.recipe.name, t.recipe.version, t.task_name ))
550    coveredBy = '';
551    if ( task_object.outcome == Task.OUTCOME_COVERED ):
552#        _list = generateCoveredList( task )
553        coveredBy = sorted(generateCoveredList2( _find_task_revdep( task_object ) ), key = lambda x: x.recipe.name)
554    log_head = ''
555    log_body = ''
556    if task_object.outcome == task_object.OUTCOME_FAILED:
557        pass
558
559    uri_list= [ ]
560    variables = Variable.objects.filter(build=build_id)
561    v=variables.filter(variable_name='SSTATE_DIR')
562    if v.count() > 0:
563        uri_list.append(v[0].variable_value)
564    v=variables.filter(variable_name='SSTATE_MIRRORS')
565    if (v.count() > 0):
566        for mirror in v[0].variable_value.split('\\n'):
567            s=re.sub('.* ','',mirror.strip(' \t\n\r'))
568            if len(s):
569                uri_list.append(s)
570
571    context = {
572            'build'           : Build.objects.filter( pk = build_id )[ 0 ],
573            'object'          : task_object,
574            'task'            : task_object,
575            'covered_by'      : coveredBy,
576            'deps'            : dependencies,
577            'rdeps'           : reverse_dependencies,
578            'log_head'        : log_head,
579            'log_body'        : log_body,
580            'showing_matches' : False,
581            'uri_list'        : uri_list,
582            'task_in_tasks_table_pg':  int(task_object.order / 25) + 1
583    }
584    if request.GET.get( 'show_matches', "" ):
585        context[ 'showing_matches' ] = True
586        context[ 'matching_tasks' ] = Task.objects.filter(
587            sstate_checksum=task_object.sstate_checksum ).filter(
588            build__completed_on__lt=task_object.build.completed_on).exclude(
589            order__isnull=True).exclude(outcome=Task.OUTCOME_NA).order_by('-build__completed_on')
590
591    return toaster_render( request, template, context )
592
593def recipe(request, build_id, recipe_id, active_tab="1"):
594    template = "recipe.html"
595    if Recipe.objects.filter(pk=recipe_id).count() == 0 :
596        return redirect(builds)
597
598    recipe_object = Recipe.objects.get(pk=recipe_id)
599    layer_version = Layer_Version.objects.get(pk=recipe_object.layer_version_id)
600    layer  = Layer.objects.get(pk=layer_version.layer_id)
601    tasks_list  = Task.objects.filter(recipe_id = recipe_id, build_id = build_id).exclude(order__isnull=True).exclude(task_name__endswith='_setscene').exclude(outcome=Task.OUTCOME_NA)
602    package_count = Package.objects.filter(recipe_id = recipe_id).filter(build_id = build_id).filter(size__gte=0).count()
603
604    if active_tab != '1' and active_tab != '3' and active_tab != '4' :
605        active_tab = '1'
606    tab_states = {'1': '', '3': '', '4': ''}
607    tab_states[active_tab] = 'active'
608
609    context = {
610            'build'   : Build.objects.get(pk=build_id),
611            'object'  : recipe_object,
612            'layer_version' : layer_version,
613            'layer'   : layer,
614            'tasks'   : tasks_list,
615            'package_count' : package_count,
616            'tab_states' : tab_states,
617    }
618    return toaster_render(request, template, context)
619
620def recipe_packages(request, build_id, recipe_id):
621    template = "recipe_packages.html"
622    if Recipe.objects.filter(pk=recipe_id).count() == 0 :
623        return redirect(builds)
624
625    (pagesize, orderby) = _get_parameters_values(request, 10, 'name:+')
626    mandatory_parameters = { 'count': pagesize,  'page' : 1, 'orderby': orderby }
627    retval = _verify_parameters( request.GET, mandatory_parameters )
628    if retval:
629        return _redirect_parameters( 'recipe_packages', request.GET, mandatory_parameters, build_id = build_id, recipe_id = recipe_id)
630    (filter_string, search_term, ordering_string) = _search_tuple(request, Package)
631
632    recipe_object = Recipe.objects.get(pk=recipe_id)
633    queryset = Package.objects.filter(recipe_id = recipe_id).filter(build_id = build_id).filter(size__gte=0)
634    package_count = queryset.count()
635    queryset = _get_queryset(Package, queryset, filter_string, search_term, ordering_string, 'name')
636
637    packages = _build_page_range(Paginator(queryset, pagesize),request.GET.get('page', 1))
638
639    context = {
640            'build'   : Build.objects.get(pk=build_id),
641            'recipe'  : recipe_object,
642            'objects'  : packages,
643            'object_count' : package_count,
644            'tablecols':[
645                {
646                    'name':'Package',
647                    'orderfield': _get_toggle_order(request,"name"),
648                    'ordericon': _get_toggle_order_icon(request,"name"),
649                    'orderkey': "name",
650                },
651                {
652                    'name':'Version',
653                },
654                {
655                    'name':'Size',
656                    'orderfield': _get_toggle_order(request,"size", True),
657                    'ordericon': _get_toggle_order_icon(request,"size"),
658                    'orderkey': 'size',
659                    'dclass': 'sizecol span2',
660                },
661           ]
662       }
663    response = toaster_render(request, template, context)
664    _set_parameters_values(pagesize, orderby, request)
665    return response
666
667from django.http import HttpResponse
668def xhr_dirinfo(request, build_id, target_id):
669    top = request.GET.get('start', '/')
670    return HttpResponse(_get_dir_entries(build_id, target_id, top), content_type = "application/json")
671
672from django.utils.functional import Promise
673from django.utils.encoding import force_text
674class LazyEncoder(json.JSONEncoder):
675    def default(self, obj):
676        if isinstance(obj, Promise):
677            return force_text(obj)
678        return super(LazyEncoder, self).default(obj)
679
680from toastergui.templatetags.projecttags import filtered_filesizeformat
681import os
682def _get_dir_entries(build_id, target_id, start):
683    node_str = {
684        Target_File.ITYPE_REGULAR   : '-',
685        Target_File.ITYPE_DIRECTORY : 'd',
686        Target_File.ITYPE_SYMLINK   : 'l',
687        Target_File.ITYPE_SOCKET    : 's',
688        Target_File.ITYPE_FIFO      : 'p',
689        Target_File.ITYPE_CHARACTER : 'c',
690        Target_File.ITYPE_BLOCK     : 'b',
691    }
692    response = []
693    objects  = Target_File.objects.filter(target__exact=target_id, directory__path=start)
694    target_packages = Target_Installed_Package.objects.filter(target__exact=target_id).values_list('package_id', flat=True)
695    for o in objects:
696        # exclude root inode '/'
697        if o.path == '/':
698            continue
699        try:
700            entry = {}
701            entry['parent'] = start
702            entry['name'] = os.path.basename(o.path)
703            entry['fullpath'] = o.path
704
705            # set defaults, not all dentries have packages
706            entry['installed_package'] = None
707            entry['package_id'] = None
708            entry['package'] = None
709            entry['link_to'] = None
710            if o.inodetype == Target_File.ITYPE_DIRECTORY:
711                entry['isdir'] = 1
712                # is there content in directory
713                entry['childcount'] = Target_File.objects.filter(target__exact=target_id, directory__path=o.path).all().count()
714            else:
715                entry['isdir'] = 0
716
717                # resolve the file to get the package from the resolved file
718                resolved_id = o.sym_target_id
719                resolved_path = o.path
720                if target_packages.count():
721                    while resolved_id != "" and resolved_id is not None:
722                        tf = Target_File.objects.get(pk=resolved_id)
723                        resolved_path = tf.path
724                        resolved_id = tf.sym_target_id
725
726                    thisfile=Package_File.objects.all().filter(path__exact=resolved_path, package_id__in=target_packages)
727                    if thisfile.count():
728                        p = Package.objects.get(pk=thisfile[0].package_id)
729                        entry['installed_package'] = p.installed_name
730                        entry['package_id'] = str(p.id)
731                        entry['package'] = p.name
732                # don't use resolved path from above, show immediate link-to
733                if o.sym_target_id != "" and o.sym_target_id is not None:
734                    entry['link_to'] = Target_File.objects.get(pk=o.sym_target_id).path
735            entry['size'] = filtered_filesizeformat(o.size)
736            if entry['link_to'] is not None:
737                entry['permission'] = node_str[o.inodetype] + o.permission
738            else:
739                entry['permission'] = node_str[o.inodetype] + o.permission
740            entry['owner'] = o.owner
741            entry['group'] = o.group
742            response.append(entry)
743
744        except Exception as e:
745            print("Exception ", e)
746            traceback.print_exc()
747
748    # sort by directories first, then by name
749    rsorted = sorted(response, key=lambda entry :  entry['name'])
750    rsorted = sorted(rsorted, key=lambda entry :  entry['isdir'], reverse=True)
751    return json.dumps(rsorted, cls=LazyEncoder).replace('</', '<\\/')
752
753def dirinfo(request, build_id, target_id, file_path=None):
754    template = "dirinfo.html"
755    objects = _get_dir_entries(build_id, target_id, '/')
756    packages_sum = Package.objects.filter(id__in=Target_Installed_Package.objects.filter(target_id=target_id).values('package_id')).aggregate(Sum('installed_size'))
757    dir_list = None
758    if file_path is not None:
759        """
760        Link from the included package detail file list page and is
761        requesting opening the dir info to a specific file path.
762        Provide the list of directories to expand and the full path to
763        highlight in the page.
764        """
765        # Aassume target's path separator matches host's, that is, os.sep
766        sep = os.sep
767        dir_list = []
768        head = file_path
769        while head != sep:
770            (head, tail) = os.path.split(head)
771            if head != sep:
772                dir_list.insert(0, head)
773
774    build = Build.objects.get(pk=build_id)
775
776    context = { 'build': build,
777                'project': build.project,
778                'target': Target.objects.get(pk=target_id),
779                'packages_sum': packages_sum['installed_size__sum'],
780                'objects': objects,
781                'dir_list': dir_list,
782                'file_path': file_path,
783              }
784    return toaster_render(request, template, context)
785
786def _find_task_dep(task_object):
787    tdeps = Task_Dependency.objects.filter(task=task_object).filter(depends_on__order__gt=0)
788    tdeps = tdeps.exclude(depends_on__outcome=Task.OUTCOME_NA).select_related("depends_on")
789    return [x.depends_on for x in tdeps]
790
791def _find_task_revdep(task_object):
792    tdeps = Task_Dependency.objects.filter(depends_on=task_object).filter(task__order__gt=0)
793    tdeps = tdeps.exclude(task__outcome = Task.OUTCOME_NA).select_related("task", "task__recipe", "task__build")
794
795    # exclude self-dependencies to prevent infinite dependency loop
796    # in generateCoveredList2()
797    tdeps = tdeps.exclude(task=task_object)
798
799    return [tdep.task for tdep in tdeps]
800
801def _find_task_revdep_list(tasklist):
802    tdeps = Task_Dependency.objects.filter(depends_on__in=tasklist).filter(task__order__gt=0)
803    tdeps = tdeps.exclude(task__outcome=Task.OUTCOME_NA).select_related("task", "task__recipe", "task__build")
804
805    # exclude self-dependencies to prevent infinite dependency loop
806    # in generateCoveredList2()
807    tdeps = tdeps.exclude(task=F('depends_on'))
808
809    return [tdep.task for tdep in tdeps]
810
811def _find_task_provider(task_object):
812    task_revdeps = _find_task_revdep(task_object)
813    for tr in task_revdeps:
814        if tr.outcome != Task.OUTCOME_COVERED:
815            return tr
816    for tr in task_revdeps:
817        trc = _find_task_provider(tr)
818        if trc is not None:
819            return trc
820    return None
821
822def configuration(request, build_id):
823    template = 'configuration.html'
824
825    var_names = ('BB_VERSION', 'BUILD_SYS', 'NATIVELSBSTRING', 'TARGET_SYS',
826                 'MACHINE', 'DISTRO', 'DISTRO_VERSION', 'TUNE_FEATURES', 'TARGET_FPU')
827    context = dict(Variable.objects.filter(build=build_id, variable_name__in=var_names)\
828                                           .values_list('variable_name', 'variable_value'))
829    build = Build.objects.get(pk=build_id)
830    context.update({'objectname': 'configuration',
831                    'object_search_display':'variables',
832                    'filter_search_display':'variables',
833                    'build': build,
834                    'project': build.project,
835                    'targets': Target.objects.filter(build=build_id)})
836    return toaster_render(request, template, context)
837
838
839def configvars(request, build_id):
840    template = 'configvars.html'
841    (pagesize, orderby) = _get_parameters_values(request, 100, 'variable_name:+')
842    mandatory_parameters = { 'count': pagesize,  'page' : 1, 'orderby' : orderby, 'filter' : 'description__regex:.+' }
843    retval = _verify_parameters( request.GET, mandatory_parameters )
844    (filter_string, search_term, ordering_string) = _search_tuple(request, Variable)
845    if retval:
846        # if new search, clear the default filter
847        if search_term and len(search_term):
848            mandatory_parameters['filter']=''
849        return _redirect_parameters( 'configvars', request.GET, mandatory_parameters, build_id = build_id)
850
851    queryset = Variable.objects.filter(build=build_id).exclude(variable_name__istartswith='B_').exclude(variable_name__istartswith='do_')
852    queryset_with_search =  _get_queryset(Variable, queryset, None, search_term, ordering_string, 'variable_name').exclude(variable_value='',vhistory__file_name__isnull=True)
853    queryset = _get_queryset(Variable, queryset, filter_string, search_term, ordering_string, 'variable_name')
854    # remove records where the value is empty AND there are no history files
855    queryset = queryset.exclude(variable_value='',vhistory__file_name__isnull=True)
856
857    variables = _build_page_range(Paginator(queryset, pagesize), request.GET.get('page', 1))
858
859    # show all matching files (not just the last one)
860    file_filter= search_term + ":"
861    if filter_string.find('/conf/') > 0:
862        file_filter += 'conf/(local|bblayers).conf'
863    if filter_string.find('conf/machine/') > 0:
864        file_filter += 'conf/machine/'
865    if filter_string.find('conf/distro/') > 0:
866        file_filter += 'conf/distro/'
867    if filter_string.find('/bitbake.conf') > 0:
868        file_filter += '/bitbake.conf'
869    build_dir=re.sub("/tmp/log/.*","",Build.objects.get(pk=build_id).cooker_log_path)
870
871    build = Build.objects.get(pk=build_id)
872
873    context = {
874                'objectname': 'configvars',
875                'object_search_display':'BitBake variables',
876                'filter_search_display':'variables',
877                'file_filter': file_filter,
878                'build': build,
879                'project': build.project,
880                'objects' : variables,
881                'total_count':queryset_with_search.count(),
882                'default_orderby' : 'variable_name:+',
883                'search_term':search_term,
884            # Specifies the display of columns for the table, appearance in "Edit columns" box, toggling default show/hide, and specifying filters for columns
885                'tablecols' : [
886                {'name': 'Variable',
887                 'qhelp': "BitBake is a generic task executor that considers a list of tasks with dependencies and handles metadata that consists of variables in a certain format that get passed to the tasks",
888                 'orderfield': _get_toggle_order(request, "variable_name"),
889                 'ordericon':_get_toggle_order_icon(request, "variable_name"),
890                },
891                {'name': 'Value',
892                 'qhelp': "The value assigned to the variable",
893                },
894                {'name': 'Set in file',
895                 'qhelp': "The last configuration file that touched the variable value",
896                 'clclass': 'file', 'hidden' : 0,
897                 'orderkey' : 'vhistory__file_name',
898                 'filter' : {
899                    'class' : 'vhistory__file_name',
900                    'label': 'Show:',
901                    'options' : [
902                               ('Local configuration variables', 'vhistory__file_name__contains:'+build_dir+'/conf/',queryset_with_search.filter(vhistory__file_name__contains=build_dir+'/conf/').count(), 'Select this filter to see variables set by the <code>local.conf</code> and <code>bblayers.conf</code> configuration files inside the <code>/build/conf/</code> directory'),
903                               ('Machine configuration variables', 'vhistory__file_name__contains:conf/machine/',queryset_with_search.filter(vhistory__file_name__contains='conf/machine').count(), 'Select this filter to see variables set by the configuration file(s) inside your layers <code>/conf/machine/</code> directory'),
904                               ('Distro configuration variables', 'vhistory__file_name__contains:conf/distro/',queryset_with_search.filter(vhistory__file_name__contains='conf/distro').count(), 'Select this filter to see variables set by the configuration file(s) inside your layers <code>/conf/distro/</code> directory'),
905                               ('Layer configuration variables', 'vhistory__file_name__contains:conf/layer.conf',queryset_with_search.filter(vhistory__file_name__contains='conf/layer.conf').count(), 'Select this filter to see variables set by the <code>layer.conf</code> configuration file inside your layers'),
906                               ('bitbake.conf variables', 'vhistory__file_name__contains:/bitbake.conf',queryset_with_search.filter(vhistory__file_name__contains='/bitbake.conf').count(), 'Select this filter to see variables set by the <code>bitbake.conf</code> configuration file'),
907                               ]
908                             },
909                },
910                {'name': 'Description',
911                 'qhelp': "A brief explanation of the variable",
912                 'clclass': 'description', 'hidden' : 0,
913                 'dclass': "span4",
914                 'filter' : {
915                    'class' : 'description',
916                    'label': 'Show:',
917                    'options' : [
918                               ('Variables with description', 'description__regex:.+', queryset_with_search.filter(description__regex='.+').count(), 'We provide descriptions for the most common BitBake variables. The list of descriptions lives in <code>meta/conf/documentation.conf</code>'),
919                               ]
920                            },
921                },
922                ],
923            }
924
925    response = toaster_render(request, template, context)
926    _set_parameters_values(pagesize, orderby, request)
927    return response
928
929def bfile(request, build_id, package_id):
930    template = 'bfile.html'
931    files = Package_File.objects.filter(package = package_id)
932    build = Build.objects.get(pk=build_id)
933    context = {
934        'build': build,
935        'project': build.project,
936        'objects' : files
937    }
938    return toaster_render(request, template, context)
939
940
941# A set of dependency types valid for both included and built package views
942OTHER_DEPENDS_BASE = [
943    Package_Dependency.TYPE_RSUGGESTS,
944    Package_Dependency.TYPE_RPROVIDES,
945    Package_Dependency.TYPE_RREPLACES,
946    Package_Dependency.TYPE_RCONFLICTS,
947    ]
948
949# value for invalid row id
950INVALID_KEY = -1
951
952"""
953Given a package id, target_id retrieves two sets of this image and package's
954dependencies.  The return value is a dictionary consisting of two other
955lists: a list of 'runtime' dependencies, that is, having RDEPENDS
956values in source package's recipe, and a list of other dependencies, that is
957the list of possible recipe variables as found in OTHER_DEPENDS_BASE plus
958the RRECOMMENDS or TRECOMMENDS value.
959The lists are built in the sort order specified for the package runtime
960dependency views.
961"""
962def _get_package_dependencies(package_id, target_id = INVALID_KEY):
963    runtime_deps = []
964    other_deps = []
965    other_depends_types = OTHER_DEPENDS_BASE
966
967    if target_id != INVALID_KEY :
968        rdepends_type = Package_Dependency.TYPE_TRDEPENDS
969        other_depends_types +=  [Package_Dependency.TYPE_TRECOMMENDS]
970    else :
971        rdepends_type = Package_Dependency.TYPE_RDEPENDS
972        other_depends_types += [Package_Dependency.TYPE_RRECOMMENDS]
973
974    package = Package.objects.get(pk=package_id)
975    if target_id != INVALID_KEY :
976        alldeps = package.package_dependencies_source.filter(target_id__exact = target_id)
977    else :
978        alldeps = package.package_dependencies_source.all()
979    for idep in alldeps:
980        dep_package = Package.objects.get(pk=idep.depends_on_id)
981        dep_entry = Package_Dependency.DEPENDS_DICT[idep.dep_type]
982        if dep_package.version == '' :
983            version = ''
984        else :
985            version = dep_package.version + "-" + dep_package.revision
986        installed = False
987        if target_id != INVALID_KEY :
988            if Target_Installed_Package.objects.filter(target_id__exact = target_id, package_id__exact = dep_package.id).count() > 0:
989                installed = True
990        dep =   {
991                'name' : dep_package.name,
992                'version' : version,
993                'size' : dep_package.size,
994                'dep_type' : idep.dep_type,
995                'dep_type_display' : dep_entry[0].capitalize(),
996                'dep_type_help' : dep_entry[1] % (dep_package.name, package.name),
997                'depends_on_id' : dep_package.id,
998                'installed' : installed,
999                }
1000
1001        if target_id != INVALID_KEY:
1002                dep['alias'] = _get_package_alias(dep_package)
1003
1004        if idep.dep_type == rdepends_type :
1005            runtime_deps.append(dep)
1006        elif idep.dep_type in other_depends_types :
1007            other_deps.append(dep)
1008
1009    rdep_sorted = sorted(runtime_deps, key=lambda k: k['name'])
1010    odep_sorted = sorted(
1011            sorted(other_deps, key=lambda k: k['name']),
1012            key=lambda k: k['dep_type'])
1013    retvalues = {'runtime_deps' : rdep_sorted, 'other_deps' : odep_sorted}
1014    return retvalues
1015
1016# Return the count of packages dependent on package for this target_id image
1017def _get_package_reverse_dep_count(package, target_id):
1018    return package.package_dependencies_target.filter(target_id__exact=target_id, dep_type__exact = Package_Dependency.TYPE_TRDEPENDS).count()
1019
1020# Return the count of the packages that this package_id is dependent on.
1021# Use one of the two RDEPENDS types, either TRDEPENDS if the package was
1022# installed, or else RDEPENDS if only built.
1023def _get_package_dependency_count(package, target_id, is_installed):
1024    if is_installed :
1025        return package.package_dependencies_source.filter(target_id__exact = target_id,
1026            dep_type__exact = Package_Dependency.TYPE_TRDEPENDS).count()
1027    else :
1028        return package.package_dependencies_source.filter(dep_type__exact = Package_Dependency.TYPE_RDEPENDS).count()
1029
1030def _get_package_alias(package):
1031    alias = package.installed_name
1032    if alias is not None and alias != '' and alias != package.name:
1033        return alias
1034    else:
1035        return ''
1036
1037def _get_fullpackagespec(package):
1038    r = package.name
1039    version_good = package.version is not None and  package.version != ''
1040    revision_good = package.revision is not None and package.revision != ''
1041    if version_good or revision_good:
1042        r += '_'
1043        if version_good:
1044            r += package.version
1045            if revision_good:
1046                r += '-'
1047        if revision_good:
1048            r += package.revision
1049    return r
1050
1051def package_built_detail(request, build_id, package_id):
1052    template = "package_built_detail.html"
1053    if Build.objects.filter(pk=build_id).count() == 0 :
1054        return redirect(builds)
1055
1056    # follow convention for pagination w/ search although not used for this view
1057    queryset = Package_File.objects.filter(package_id__exact=package_id)
1058    (pagesize, orderby) = _get_parameters_values(request, 25, 'path:+')
1059    mandatory_parameters = { 'count': pagesize,  'page' : 1, 'orderby' : orderby }
1060    retval = _verify_parameters( request.GET, mandatory_parameters )
1061    if retval:
1062        return _redirect_parameters( 'package_built_detail', request.GET, mandatory_parameters, build_id = build_id, package_id = package_id)
1063
1064    (filter_string, search_term, ordering_string) = _search_tuple(request, Package_File)
1065    paths = _get_queryset(Package_File, queryset, filter_string, search_term, ordering_string, 'path')
1066
1067    package = Package.objects.get(pk=package_id)
1068    package.fullpackagespec = _get_fullpackagespec(package)
1069    context = {
1070            'build' : Build.objects.get(pk=build_id),
1071            'package' : package,
1072            'dependency_count' : _get_package_dependency_count(package, -1, False),
1073            'objects' : paths,
1074            'tablecols':[
1075                {
1076                    'name':'File',
1077                    'orderfield': _get_toggle_order(request, "path"),
1078                    'ordericon':_get_toggle_order_icon(request, "path"),
1079                },
1080                {
1081                    'name':'Size',
1082                    'orderfield': _get_toggle_order(request, "size", True),
1083                    'ordericon':_get_toggle_order_icon(request, "size"),
1084                    'dclass': 'sizecol span2',
1085                },
1086            ]
1087    }
1088    if paths.all().count() < 2:
1089        context['disable_sort'] = True;
1090
1091    response = toaster_render(request, template, context)
1092    _set_parameters_values(pagesize, orderby, request)
1093    return response
1094
1095def package_built_dependencies(request, build_id, package_id):
1096    template = "package_built_dependencies.html"
1097    if Build.objects.filter(pk=build_id).count() == 0 :
1098         return redirect(builds)
1099
1100    package = Package.objects.get(pk=package_id)
1101    package.fullpackagespec = _get_fullpackagespec(package)
1102    dependencies = _get_package_dependencies(package_id)
1103    context = {
1104            'build' : Build.objects.get(pk=build_id),
1105            'package' : package,
1106            'runtime_deps' : dependencies['runtime_deps'],
1107            'other_deps' :   dependencies['other_deps'],
1108            'dependency_count' : _get_package_dependency_count(package, -1,  False)
1109    }
1110    return toaster_render(request, template, context)
1111
1112
1113def package_included_detail(request, build_id, target_id, package_id):
1114    template = "package_included_detail.html"
1115    if Build.objects.filter(pk=build_id).count() == 0 :
1116        return redirect(builds)
1117
1118    # follow convention for pagination w/ search although not used for this view
1119    (pagesize, orderby) = _get_parameters_values(request, 25, 'path:+')
1120    mandatory_parameters = { 'count': pagesize,  'page' : 1, 'orderby' : orderby }
1121    retval = _verify_parameters( request.GET, mandatory_parameters )
1122    if retval:
1123        return _redirect_parameters( 'package_included_detail', request.GET, mandatory_parameters, build_id = build_id, target_id = target_id, package_id = package_id)
1124    (filter_string, search_term, ordering_string) = _search_tuple(request, Package_File)
1125
1126    queryset = Package_File.objects.filter(package_id__exact=package_id)
1127    paths = _get_queryset(Package_File, queryset, filter_string, search_term, ordering_string, 'path')
1128
1129    package = Package.objects.get(pk=package_id)
1130    package.fullpackagespec = _get_fullpackagespec(package)
1131    package.alias = _get_package_alias(package)
1132    target = Target.objects.get(pk=target_id)
1133    context = {
1134            'build' : Build.objects.get(pk=build_id),
1135            'target'  : target,
1136            'package' : package,
1137            'reverse_count' : _get_package_reverse_dep_count(package, target_id),
1138            'dependency_count' : _get_package_dependency_count(package, target_id, True),
1139            'objects': paths,
1140            'tablecols':[
1141                {
1142                    'name':'File',
1143                    'orderfield': _get_toggle_order(request, "path"),
1144                    'ordericon':_get_toggle_order_icon(request, "path"),
1145                },
1146                {
1147                    'name':'Size',
1148                    'orderfield': _get_toggle_order(request, "size", True),
1149                    'ordericon':_get_toggle_order_icon(request, "size"),
1150                    'dclass': 'sizecol span2',
1151                },
1152            ]
1153    }
1154    if paths.all().count() < 2:
1155        context['disable_sort'] = True
1156    response = toaster_render(request, template, context)
1157    _set_parameters_values(pagesize, orderby, request)
1158    return response
1159
1160def package_included_dependencies(request, build_id, target_id, package_id):
1161    template = "package_included_dependencies.html"
1162    if Build.objects.filter(pk=build_id).count() == 0 :
1163        return redirect(builds)
1164
1165    package = Package.objects.get(pk=package_id)
1166    package.fullpackagespec = _get_fullpackagespec(package)
1167    package.alias = _get_package_alias(package)
1168    target = Target.objects.get(pk=target_id)
1169
1170    dependencies = _get_package_dependencies(package_id, target_id)
1171    context = {
1172            'build' : Build.objects.get(pk=build_id),
1173            'package' : package,
1174            'target' : target,
1175            'runtime_deps' : dependencies['runtime_deps'],
1176            'other_deps' :   dependencies['other_deps'],
1177            'reverse_count' : _get_package_reverse_dep_count(package, target_id),
1178            'dependency_count' : _get_package_dependency_count(package, target_id, True)
1179    }
1180    return toaster_render(request, template, context)
1181
1182def package_included_reverse_dependencies(request, build_id, target_id, package_id):
1183    template = "package_included_reverse_dependencies.html"
1184    if Build.objects.filter(pk=build_id).count() == 0 :
1185        return redirect(builds)
1186
1187    (pagesize, orderby) = _get_parameters_values(request, 25, 'package__name:+')
1188    mandatory_parameters = { 'count': pagesize,  'page' : 1, 'orderby': orderby }
1189    retval = _verify_parameters( request.GET, mandatory_parameters )
1190    if retval:
1191        return _redirect_parameters( 'package_included_reverse_dependencies', request.GET, mandatory_parameters, build_id = build_id, target_id = target_id, package_id = package_id)
1192    (filter_string, search_term, ordering_string) = _search_tuple(request, Package_File)
1193
1194    queryset = Package_Dependency.objects.select_related('depends_on').filter(depends_on=package_id, target_id=target_id, dep_type=Package_Dependency.TYPE_TRDEPENDS)
1195    objects = _get_queryset(Package_Dependency, queryset, filter_string, search_term, ordering_string, 'package__name')
1196
1197    package = Package.objects.get(pk=package_id)
1198    package.fullpackagespec = _get_fullpackagespec(package)
1199    package.alias = _get_package_alias(package)
1200    target = Target.objects.get(pk=target_id)
1201    for o in objects:
1202        if o.package.version != '':
1203            o.package.version += '-' + o.package.revision
1204        o.alias = _get_package_alias(o.package)
1205    context = {
1206            'build' : Build.objects.get(pk=build_id),
1207            'package' : package,
1208            'target' : target,
1209            'objects' : objects,
1210            'reverse_count' : _get_package_reverse_dep_count(package, target_id),
1211            'dependency_count' : _get_package_dependency_count(package, target_id, True),
1212            'tablecols':[
1213                {
1214                    'name':'Package',
1215                    'orderfield': _get_toggle_order(request, "package__name"),
1216                    'ordericon': _get_toggle_order_icon(request, "package__name"),
1217                },
1218                {
1219                    'name':'Version',
1220                },
1221                {
1222                    'name':'Size',
1223                    'orderfield': _get_toggle_order(request, "package__size", True),
1224                    'ordericon': _get_toggle_order_icon(request, "package__size"),
1225                    'dclass': 'sizecol span2',
1226                },
1227            ]
1228    }
1229    if objects.all().count() < 2:
1230        context['disable_sort'] = True
1231    response = toaster_render(request, template, context)
1232    _set_parameters_values(pagesize, orderby, request)
1233    return response
1234
1235def image_information_dir(request, build_id, target_id, packagefile_id):
1236    # stubbed for now
1237    return redirect(builds)
1238    # the context processor that supplies data used across all the pages
1239
1240# a context processor which runs on every request; this provides the
1241# projects and non_cli_projects (i.e. projects created by the user)
1242# variables referred to in templates, which used to determine the
1243# visibility of UI elements like the "New build" button
1244def managedcontextprocessor(request):
1245    projects = Project.objects.all()
1246    ret = {
1247        "projects": projects,
1248        "non_cli_projects": projects.exclude(is_default=True),
1249        "DEBUG" : toastermain.settings.DEBUG,
1250        "TOASTER_BRANCH": toastermain.settings.TOASTER_BRANCH,
1251        "TOASTER_REVISION" : toastermain.settings.TOASTER_REVISION,
1252    }
1253    return ret
1254
1255# REST-based API calls to return build/building status to external Toaster
1256# managers and aggregators via JSON
1257
1258def _json_build_status(build_id,extend):
1259    build_stat = None
1260    try:
1261        build = Build.objects.get( pk = build_id )
1262        build_stat = {}
1263        build_stat['id'] = build.id
1264        build_stat['name'] = build.build_name
1265        build_stat['machine'] = build.machine
1266        build_stat['distro'] = build.distro
1267        build_stat['start'] = build.started_on
1268        # look up target name
1269        target= Target.objects.get( build = build )
1270        if target:
1271            if target.task:
1272                build_stat['target'] = '%s:%s' % (target.target,target.task)
1273            else:
1274                build_stat['target'] = '%s' % (target.target)
1275        else:
1276            build_stat['target'] = ''
1277        # look up project name
1278        project = Project.objects.get( build = build )
1279        if project:
1280            build_stat['project'] = project.name
1281        else:
1282            build_stat['project'] = ''
1283        if Build.IN_PROGRESS == build.outcome:
1284            now = timezone.now()
1285            timediff = now - build.started_on
1286            build_stat['seconds']='%.3f' % timediff.total_seconds()
1287            build_stat['clone']='%d:%d' % (build.repos_cloned,build.repos_to_clone)
1288            build_stat['parse']='%d:%d' % (build.recipes_parsed,build.recipes_to_parse)
1289            tf = Task.objects.filter(build = build)
1290            tfc = tf.count()
1291            if tfc > 0:
1292                tfd = tf.exclude(order__isnull=True).count()
1293            else:
1294                tfd = 0
1295            build_stat['task']='%d:%d' % (tfd,tfc)
1296        else:
1297            build_stat['outcome'] = build.get_outcome_text()
1298            timediff = build.completed_on - build.started_on
1299            build_stat['seconds']='%.3f' % timediff.total_seconds()
1300            build_stat['stop'] = build.completed_on
1301            messages = LogMessage.objects.all().filter(build = build)
1302            errors = len(messages.filter(level=LogMessage.ERROR) |
1303                 messages.filter(level=LogMessage.EXCEPTION) |
1304                 messages.filter(level=LogMessage.CRITICAL))
1305            build_stat['errors'] = errors
1306            warnings = len(messages.filter(level=LogMessage.WARNING))
1307            build_stat['warnings'] = warnings
1308        if extend:
1309            build_stat['cooker_log'] = build.cooker_log_path
1310    except Exception as e:
1311        build_state = str(e)
1312    return build_stat
1313
1314def json_builds(request):
1315    build_table = []
1316    builds = []
1317    try:
1318        builds = Build.objects.exclude(outcome=Build.IN_PROGRESS).order_by("-started_on")
1319        for build in builds:
1320            build_table.append(_json_build_status(build.id,False))
1321    except Exception as e:
1322        build_table = str(e)
1323    return JsonResponse({'builds' : build_table, 'count' : len(builds)})
1324
1325def json_building(request):
1326    build_table = []
1327    builds = []
1328    try:
1329        builds = Build.objects.filter(outcome=Build.IN_PROGRESS).order_by("-started_on")
1330        for build in builds:
1331            build_table.append(_json_build_status(build.id,False))
1332    except Exception as e:
1333        build_table = str(e)
1334    return JsonResponse({'building' : build_table, 'count' : len(builds)})
1335
1336def json_build(request,build_id):
1337    return JsonResponse({'build' : _json_build_status(build_id,True)})
1338
1339
1340import toastermain.settings
1341
1342from orm.models import Project, ProjectLayer, ProjectVariable
1343from bldcontrol.models import  BuildEnvironment
1344
1345# we have a set of functions if we're in managed mode, or
1346# a default "page not available" simple functions for interactive mode
1347
1348if True:
1349    from django.contrib.auth.models import User
1350    from django.contrib.auth import authenticate, login
1351
1352    from orm.models import LayerSource, ToasterSetting, Release
1353
1354    import traceback
1355
1356    class BadParameterException(Exception):
1357        ''' The exception raised on invalid POST requests '''
1358        pass
1359
1360    # new project
1361    def newproject(request):
1362        if not project_enable:
1363            return redirect( landing )
1364
1365        template = "newproject.html"
1366        context = {
1367            'email': request.user.email if request.user.is_authenticated else '',
1368            'username': request.user.username if request.user.is_authenticated else '',
1369            'releases': Release.objects.order_by("description"),
1370        }
1371
1372        try:
1373            context['defaultbranch'] = ToasterSetting.objects.get(name = "DEFAULT_RELEASE").value
1374        except ToasterSetting.DoesNotExist:
1375            pass
1376
1377        if request.method == "GET":
1378            # render new project page
1379            return toaster_render(request, template, context)
1380        elif request.method == "POST":
1381            mandatory_fields = ['projectname', 'ptype']
1382            try:
1383                ptype = request.POST.get('ptype')
1384                if ptype == "import":
1385                    mandatory_fields.append('importdir')
1386                else:
1387                    mandatory_fields.append('projectversion')
1388                # make sure we have values for all mandatory_fields
1389                missing = [field for field in mandatory_fields if len(request.POST.get(field, '')) == 0]
1390                if missing:
1391                    # set alert for missing fields
1392                    raise BadParameterException("Fields missing: %s" % ", ".join(missing))
1393
1394                if not request.user.is_authenticated:
1395                    user = authenticate(username = request.POST.get('username', '_anonuser'), password = 'nopass')
1396                    if user is None:
1397                        user = User.objects.create_user(username = request.POST.get('username', '_anonuser'), email = request.POST.get('email', ''), password = "nopass")
1398
1399                        user = authenticate(username = user.username, password = 'nopass')
1400                    login(request, user)
1401
1402                #  save the project
1403                if ptype == "import":
1404                    if not os.path.isdir('%s/conf' % request.POST['importdir']):
1405                        raise BadParameterException("Bad path or missing 'conf' directory (%s)" % request.POST['importdir'])
1406                    from django.core import management
1407                    management.call_command('buildimport', '--command=import', '--name=%s' % request.POST['projectname'], '--path=%s' % request.POST['importdir'], interactive=False)
1408                    prj = Project.objects.get(name = request.POST['projectname'])
1409                    prj.merged_attr = True
1410                    prj.save()
1411                else:
1412                    release = Release.objects.get(pk = request.POST.get('projectversion', None ))
1413                    prj = Project.objects.create_project(name = request.POST['projectname'], release = release)
1414                    prj.user_id = request.user.pk
1415                    if 'mergeattr' == request.POST.get('mergeattr', ''):
1416                        prj.merged_attr = True
1417                    prj.save()
1418
1419                return redirect(reverse(project, args=(prj.pk,)) + "?notify=new-project")
1420
1421            except (IntegrityError, BadParameterException) as e:
1422                # fill in page with previously submitted values
1423                for field in mandatory_fields:
1424                    context.__setitem__(field, request.POST.get(field, "-- missing"))
1425                if isinstance(e, IntegrityError) and "username" in str(e):
1426                    context['alert'] = "Your chosen username is already used"
1427                else:
1428                    context['alert'] = str(e)
1429                return toaster_render(request, template, context)
1430
1431        raise Exception("Invalid HTTP method for this page")
1432
1433    # new project
1434    def newproject_specific(request, pid):
1435        if not project_enable:
1436            return redirect( landing )
1437
1438        project = Project.objects.get(pk=pid)
1439        template = "newproject_specific.html"
1440        context = {
1441            'email': request.user.email if request.user.is_authenticated else '',
1442            'username': request.user.username if request.user.is_authenticated else '',
1443            'releases': Release.objects.order_by("description"),
1444            'projectname': project.name,
1445            'project_pk': project.pk,
1446        }
1447
1448        # WORKAROUND: if we already know release, redirect 'newproject_specific' to 'project_specific'
1449        if '1' == project.get_variable('INTERNAL_PROJECT_SPECIFIC_SKIPRELEASE'):
1450            return redirect(reverse(project_specific, args=(project.pk,)))
1451
1452        try:
1453            context['defaultbranch'] = ToasterSetting.objects.get(name = "DEFAULT_RELEASE").value
1454        except ToasterSetting.DoesNotExist:
1455            pass
1456
1457        if request.method == "GET":
1458            # render new project page
1459            return toaster_render(request, template, context)
1460        elif request.method == "POST":
1461            mandatory_fields = ['projectname', 'ptype']
1462            try:
1463                ptype = request.POST.get('ptype')
1464                if ptype == "build":
1465                    mandatory_fields.append('projectversion')
1466                # make sure we have values for all mandatory_fields
1467                missing = [field for field in mandatory_fields if len(request.POST.get(field, '')) == 0]
1468                if missing:
1469                    # set alert for missing fields
1470                    raise BadParameterException("Fields missing: %s" % ", ".join(missing))
1471
1472                if not request.user.is_authenticated:
1473                    user = authenticate(username = request.POST.get('username', '_anonuser'), password = 'nopass')
1474                    if user is None:
1475                        user = User.objects.create_user(username = request.POST.get('username', '_anonuser'), email = request.POST.get('email', ''), password = "nopass")
1476
1477                        user = authenticate(username = user.username, password = 'nopass')
1478                    login(request, user)
1479
1480                #  save the project
1481                if ptype == "analysis":
1482                    release = None
1483                else:
1484                    release = Release.objects.get(pk = request.POST.get('projectversion', None ))
1485
1486                prj = Project.objects.create_project(name = request.POST['projectname'], release = release, existing_project = project)
1487                prj.user_id = request.user.pk
1488                prj.save()
1489                return redirect(reverse(project_specific, args=(prj.pk,)) + "?notify=new-project")
1490
1491            except (IntegrityError, BadParameterException) as e:
1492                # fill in page with previously submitted values
1493                for field in mandatory_fields:
1494                    context.__setitem__(field, request.POST.get(field, "-- missing"))
1495                if isinstance(e, IntegrityError) and "username" in str(e):
1496                    context['alert'] = "Your chosen username is already used"
1497                else:
1498                    context['alert'] = str(e)
1499                return toaster_render(request, template, context)
1500
1501        raise Exception("Invalid HTTP method for this page")
1502
1503    # Shows the edit project page
1504    def project(request, pid):
1505        project = Project.objects.get(pk=pid)
1506
1507        if '1' == os.environ.get('TOASTER_PROJECTSPECIFIC'):
1508            if request.GET:
1509                #Example:request.GET=<QueryDict: {'setMachine': ['qemuarm']}>
1510                params = urlencode(request.GET).replace('%5B%27','').replace('%27%5D','')
1511                return redirect("%s?%s" % (reverse(project_specific, args=(project.pk,)),params))
1512            else:
1513                return redirect(reverse(project_specific, args=(project.pk,)))
1514        context = {"project": project}
1515        return toaster_render(request, "project.html", context)
1516
1517    # Shows the edit project-specific page
1518    def project_specific(request, pid):
1519        project = Project.objects.get(pk=pid)
1520
1521        # Are we refreshing from a successful project specific update clone?
1522        if Project.PROJECT_SPECIFIC_CLONING_SUCCESS == project.get_variable(Project.PROJECT_SPECIFIC_STATUS):
1523            return redirect(reverse(landing_specific,args=(project.pk,)))
1524
1525        context = {
1526            "project": project,
1527            "is_new" : project.get_variable(Project.PROJECT_SPECIFIC_ISNEW),
1528            "default_image_recipe" : project.get_variable(Project.PROJECT_SPECIFIC_DEFAULTIMAGE),
1529            "mru" : Build.objects.all().filter(project=project,outcome=Build.IN_PROGRESS),
1530            }
1531        if project.build_set.filter(outcome=Build.IN_PROGRESS).count() > 0:
1532            context['build_in_progress_none_completed'] = True
1533        else:
1534            context['build_in_progress_none_completed'] = False
1535        return toaster_render(request, "project.html", context)
1536
1537    # perform the final actions for the project specific page
1538    def project_specific_finalize(cmnd, pid):
1539        project = Project.objects.get(pk=pid)
1540        callback = project.get_variable(Project.PROJECT_SPECIFIC_CALLBACK)
1541        if "update" == cmnd:
1542            # Delete all '_PROJECT_PREPARE_' builds
1543            for b in Build.objects.all().filter(project=project):
1544                delete_build = False
1545                for t in b.target_set.all():
1546                    if '_PROJECT_PREPARE_' == t.target:
1547                        delete_build = True
1548                if delete_build:
1549                    from django.core import management
1550                    management.call_command('builddelete', str(b.id), interactive=False)
1551            # perform callback at this last moment if defined, in case Toaster gets shutdown next
1552            default_target = project.get_variable(Project.PROJECT_SPECIFIC_DEFAULTIMAGE)
1553            if callback:
1554                callback = callback.replace("<IMAGE>",default_target)
1555        if "cancel" == cmnd:
1556            if callback:
1557                callback = callback.replace("<IMAGE>","none")
1558                callback = callback.replace("--update","--cancel")
1559        # perform callback at this last moment if defined, in case this Toaster gets shutdown next
1560        ret = ''
1561        if callback:
1562            ret = os.system('bash -c "%s"' % callback)
1563            project.set_variable(Project.PROJECT_SPECIFIC_CALLBACK,'')
1564        # Delete the temp project specific variables
1565        project.set_variable(Project.PROJECT_SPECIFIC_ISNEW,'')
1566        project.set_variable(Project.PROJECT_SPECIFIC_STATUS,Project.PROJECT_SPECIFIC_NONE)
1567        # WORKAROUND: Release this workaround flag
1568        project.set_variable('INTERNAL_PROJECT_SPECIFIC_SKIPRELEASE','')
1569
1570    # Shows the final landing page for project specific update
1571    def landing_specific(request, pid):
1572        project_specific_finalize("update", pid)
1573        context = {
1574            "install_dir": os.environ['TOASTER_DIR'],
1575        }
1576        return toaster_render(request, "landing_specific.html", context)
1577
1578    # Shows the related landing-specific page
1579    def landing_specific_cancel(request, pid):
1580        project_specific_finalize("cancel", pid)
1581        context = {
1582            "install_dir": os.environ['TOASTER_DIR'],
1583            "status": "cancel",
1584        }
1585        return toaster_render(request, "landing_specific.html", context)
1586
1587    def jsunittests(request):
1588        """ Provides a page for the js unit tests """
1589        bbv = BitbakeVersion.objects.filter(branch="master").first()
1590        release = Release.objects.filter(bitbake_version=bbv).first()
1591
1592        name = "_js_unit_test_prj_"
1593
1594        # If there is an existing project by this name delete it.
1595        # We don't want Lots of duplicates cluttering up the projects.
1596        Project.objects.filter(name=name).delete()
1597
1598        new_project = Project.objects.create_project(name=name,
1599                                                     release=release)
1600        # Add a layer
1601        layer = new_project.get_all_compatible_layer_versions().first()
1602
1603        ProjectLayer.objects.get_or_create(layercommit=layer,
1604                                           project=new_project)
1605
1606        # make sure we have a machine set for this project
1607        ProjectVariable.objects.get_or_create(project=new_project,
1608                                              name="MACHINE",
1609                                              value="qemux86")
1610        context = {'project': new_project}
1611        return toaster_render(request, "js-unit-tests.html", context)
1612
1613    from django.views.decorators.csrf import csrf_exempt
1614    @csrf_exempt
1615    def xhr_testreleasechange(request, pid):
1616        def response(data):
1617            return HttpResponse(jsonfilter(data),
1618                                content_type="application/json")
1619
1620        """ returns layer versions that would be deleted on the new
1621        release__pk """
1622        try:
1623            prj = Project.objects.get(pk = pid)
1624            new_release_id = request.GET['new_release_id']
1625
1626            # If we're already on this project do nothing
1627            if prj.release.pk == int(new_release_id):
1628                return reponse({"error": "ok", "rows": []})
1629
1630            retval = []
1631
1632            for project in prj.projectlayer_set.all():
1633                release = Release.objects.get(pk = new_release_id)
1634
1635                layer_versions = prj.get_all_compatible_layer_versions()
1636                layer_versions = layer_versions.filter(release = release)
1637                layer_versions = layer_versions.filter(layer__name = project.layercommit.layer.name)
1638
1639                # there is no layer_version with the new release id,
1640                # and the same name
1641                if layer_versions.count() < 1:
1642                    retval.append(project)
1643
1644            return response({"error":"ok",
1645                             "rows": [_lv_to_dict(prj) for y in [x.layercommit for x in retval]]
1646                            })
1647
1648        except Exception as e:
1649            return response({"error": str(e) })
1650
1651    def xhr_configvaredit(request, pid):
1652        try:
1653            prj = Project.objects.get(id = pid)
1654            # There are cases where user can add variables which hold values
1655            # like http://, file:/// etc. In such case a simple split(":")
1656            # would fail. One example is SSTATE_MIRRORS variable. So we use
1657            # max_split var to handle them.
1658            max_split = 1
1659            # add conf variables
1660            if 'configvarAdd' in request.POST:
1661                t=request.POST['configvarAdd'].strip()
1662                if ":" in t:
1663                    variable, value = t.split(":", max_split)
1664                else:
1665                    variable = t
1666                    value = ""
1667
1668                pt, created = ProjectVariable.objects.get_or_create(project = prj, name = variable, value = value)
1669            # change conf variables
1670            if 'configvarChange' in request.POST:
1671                t=request.POST['configvarChange'].strip()
1672                if ":" in t:
1673                    variable, value = t.split(":", max_split)
1674                else:
1675                    variable = t
1676                    value = ""
1677
1678                pt, created = ProjectVariable.objects.get_or_create(project = prj, name = variable)
1679                pt.value=value
1680                pt.save()
1681            # remove conf variables
1682            if 'configvarDel' in request.POST:
1683                t=request.POST['configvarDel'].strip()
1684                pt = ProjectVariable.objects.get(pk = int(t)).delete()
1685
1686            # return all project settings, filter out blacklist and elsewhere-managed variables
1687            vars_managed,vars_fstypes,vars_blacklist = get_project_configvars_context()
1688            configvars_query = ProjectVariable.objects.filter(project_id = pid).all()
1689            for var in vars_managed:
1690                configvars_query = configvars_query.exclude(name = var)
1691            for var in vars_blacklist:
1692                configvars_query = configvars_query.exclude(name = var)
1693
1694            return_data = {
1695                "error": "ok",
1696                'configvars': [(x.name, x.value, x.pk) for x in configvars_query]
1697               }
1698            try:
1699                return_data['distro'] = ProjectVariable.objects.get(project = prj, name = "DISTRO").value,
1700            except ProjectVariable.DoesNotExist:
1701                pass
1702            try:
1703                return_data['dl_dir'] = ProjectVariable.objects.get(project = prj, name = "DL_DIR").value,
1704            except ProjectVariable.DoesNotExist:
1705                pass
1706            try:
1707                return_data['fstypes'] = ProjectVariable.objects.get(project = prj, name = "IMAGE_FSTYPES").value,
1708            except ProjectVariable.DoesNotExist:
1709                pass
1710            try:
1711                return_data['image_install:append'] = ProjectVariable.objects.get(project = prj, name = "IMAGE_INSTALL:append").value,
1712            except ProjectVariable.DoesNotExist:
1713                pass
1714            try:
1715                return_data['package_classes'] = ProjectVariable.objects.get(project = prj, name = "PACKAGE_CLASSES").value,
1716            except ProjectVariable.DoesNotExist:
1717                pass
1718            try:
1719                return_data['sstate_dir'] = ProjectVariable.objects.get(project = prj, name = "SSTATE_DIR").value,
1720            except ProjectVariable.DoesNotExist:
1721                pass
1722
1723            return HttpResponse(json.dumps( return_data ), content_type = "application/json")
1724
1725        except Exception as e:
1726            return HttpResponse(json.dumps({"error":str(e) + "\n" + traceback.format_exc()}), content_type = "application/json")
1727
1728
1729    def customrecipe_download(request, pid, recipe_id):
1730        recipe = get_object_or_404(CustomImageRecipe, pk=recipe_id)
1731
1732        file_data = recipe.generate_recipe_file_contents()
1733
1734        response = HttpResponse(file_data, content_type='text/plain')
1735        response['Content-Disposition'] = \
1736                'attachment; filename="%s_%s.bb"' % (recipe.name,
1737                                                     recipe.version)
1738
1739        return response
1740
1741    def importlayer(request, pid):
1742        template = "importlayer.html"
1743        context = {
1744            'project': Project.objects.get(id=pid),
1745        }
1746        return toaster_render(request, template, context)
1747
1748    def layerdetails(request, pid, layerid):
1749        project = Project.objects.get(pk=pid)
1750        layer_version = Layer_Version.objects.get(pk=layerid)
1751
1752        project_layers = ProjectLayer.objects.filter(
1753            project=project).values_list("layercommit_id",
1754                                         flat=True)
1755
1756        context = {
1757            'project': project,
1758            'layer_source': LayerSource.types_dict(),
1759            'layerversion': layer_version,
1760            'layerdeps': {
1761                "list": [
1762                    {
1763                        "id": dep.id,
1764                        "name": dep.layer.name,
1765                        "layerdetailurl": reverse('layerdetails',
1766                                                  args=(pid, dep.pk)),
1767                        "vcs_url": dep.layer.vcs_url,
1768                        "vcs_reference": dep.get_vcs_reference()
1769                    }
1770                    for dep in layer_version.get_alldeps(project.id)]
1771            },
1772            'projectlayers': list(project_layers)
1773        }
1774
1775        return toaster_render(request, 'layerdetails.html', context)
1776
1777
1778    def get_project_configvars_context():
1779        # Vars managed outside of this view
1780        vars_managed = {
1781            'MACHINE', 'BBLAYERS'
1782        }
1783
1784        vars_blacklist  = {
1785            'PARALLEL_MAKE','BB_NUMBER_THREADS',
1786            'BB_DISKMON_DIRS','BB_NUMBER_THREADS','CVS_PROXY_HOST','CVS_PROXY_PORT',
1787            'PARALLEL_MAKE','TMPDIR',
1788            'all_proxy','ftp_proxy','http_proxy ','https_proxy'
1789            }
1790
1791        vars_fstypes = Target_Image_File.SUFFIXES
1792
1793        return(vars_managed,sorted(vars_fstypes),vars_blacklist)
1794
1795    def projectconf(request, pid):
1796
1797        try:
1798            prj = Project.objects.get(id = pid)
1799        except Project.DoesNotExist:
1800            return HttpResponseNotFound("<h1>Project id " + pid + " is unavailable</h1>")
1801
1802        # remove blacklist and externally managed varaibles from this list
1803        vars_managed,vars_fstypes,vars_blacklist = get_project_configvars_context()
1804        configvars = ProjectVariable.objects.filter(project_id = pid).all()
1805        for var in vars_managed:
1806            configvars = configvars.exclude(name = var)
1807        for var in vars_blacklist:
1808            configvars = configvars.exclude(name = var)
1809
1810        context = {
1811            'project':          prj,
1812            'configvars':       configvars,
1813            'vars_managed':     vars_managed,
1814            'vars_fstypes':     vars_fstypes,
1815            'vars_blacklist':   vars_blacklist,
1816        }
1817
1818        try:
1819            context['distro'] =  ProjectVariable.objects.get(project = prj, name = "DISTRO").value
1820            context['distro_defined'] = "1"
1821        except ProjectVariable.DoesNotExist:
1822            pass
1823        try:
1824            if ProjectVariable.objects.get(project = prj, name = "DL_DIR").value == "${TOPDIR}/../downloads":
1825                be = BuildEnvironment.objects.get(pk = str(1))
1826                dl_dir = os.path.join(dirname(be.builddir), "downloads")
1827                context['dl_dir'] =  dl_dir
1828                pv, created = ProjectVariable.objects.get_or_create(project = prj, name = "DL_DIR")
1829                pv.value = dl_dir
1830                pv.save()
1831            else:
1832                context['dl_dir'] = ProjectVariable.objects.get(project = prj, name = "DL_DIR").value
1833            context['dl_dir_defined'] = "1"
1834        except (ProjectVariable.DoesNotExist, BuildEnvironment.DoesNotExist):
1835            pass
1836        try:
1837            context['fstypes'] =  ProjectVariable.objects.get(project = prj, name = "IMAGE_FSTYPES").value
1838            context['fstypes_defined'] = "1"
1839        except ProjectVariable.DoesNotExist:
1840            pass
1841        try:
1842            context['image_install:append'] =  ProjectVariable.objects.get(project = prj, name = "IMAGE_INSTALL:append").value
1843            context['image_install_append_defined'] = "1"
1844        except ProjectVariable.DoesNotExist:
1845            pass
1846        try:
1847            context['package_classes'] =  ProjectVariable.objects.get(project = prj, name = "PACKAGE_CLASSES").value
1848            context['package_classes_defined'] = "1"
1849        except ProjectVariable.DoesNotExist:
1850            pass
1851        try:
1852            if ProjectVariable.objects.get(project = prj, name = "SSTATE_DIR").value == "${TOPDIR}/../sstate-cache":
1853                be = BuildEnvironment.objects.get(pk = str(1))
1854                sstate_dir = os.path.join(dirname(be.builddir), "sstate-cache")
1855                context['sstate_dir'] = sstate_dir
1856                pv, created = ProjectVariable.objects.get_or_create(project = prj, name = "SSTATE_DIR")
1857                pv.value = sstate_dir
1858                pv.save()
1859            else:
1860                context['sstate_dir'] = ProjectVariable.objects.get(project = prj, name = "SSTATE_DIR").value
1861            context['sstate_dir_defined'] = "1"
1862        except (ProjectVariable.DoesNotExist, BuildEnvironment.DoesNotExist):
1863            pass
1864
1865        return toaster_render(request, "projectconf.html", context)
1866
1867    def _file_names_for_artifact(build, artifact_type, artifact_id):
1868        """
1869        Return a tuple (file path, file name for the download response) for an
1870        artifact of type artifact_type with ID artifact_id for build; if
1871        artifact type is not supported, returns (None, None)
1872        """
1873        file_name = None
1874        response_file_name = None
1875
1876        if artifact_type == "cookerlog":
1877            file_name = build.cooker_log_path
1878            response_file_name = "cooker.log"
1879
1880        elif artifact_type == "imagefile":
1881            file_name = Target_Image_File.objects.get(target__build = build, pk = artifact_id).file_name
1882
1883        elif artifact_type == "targetkernelartifact":
1884            target = TargetKernelFile.objects.get(pk=artifact_id)
1885            file_name = target.file_name
1886
1887        elif artifact_type == "targetsdkartifact":
1888            target = TargetSDKFile.objects.get(pk=artifact_id)
1889            file_name = target.file_name
1890
1891        elif artifact_type == "licensemanifest":
1892            file_name = Target.objects.get(build = build, pk = artifact_id).license_manifest_path
1893
1894        elif artifact_type == "packagemanifest":
1895            file_name = Target.objects.get(build = build, pk = artifact_id).package_manifest_path
1896
1897        elif artifact_type == "tasklogfile":
1898            file_name = Task.objects.get(build = build, pk = artifact_id).logfile
1899
1900        elif artifact_type == "logmessagefile":
1901            file_name = LogMessage.objects.get(build = build, pk = artifact_id).pathname
1902
1903        if file_name and not response_file_name:
1904            response_file_name = os.path.basename(file_name)
1905
1906        return (file_name, response_file_name)
1907
1908    def build_artifact(request, build_id, artifact_type, artifact_id):
1909        """
1910        View which returns a build artifact file as a response
1911        """
1912        file_name = None
1913        response_file_name = None
1914
1915        try:
1916            build = Build.objects.get(pk = build_id)
1917            file_name, response_file_name = _file_names_for_artifact(
1918                build, artifact_type, artifact_id
1919            )
1920
1921            if file_name and response_file_name:
1922                fsock = open(file_name, "rb")
1923                content_type = MimeTypeFinder.get_mimetype(file_name)
1924
1925                response = HttpResponse(fsock, content_type = content_type)
1926
1927                disposition = "attachment; filename=" + response_file_name
1928                response["Content-Disposition"] = disposition
1929
1930                return response
1931            else:
1932                return toaster_render(request, "unavailable_artifact.html")
1933        except (ObjectDoesNotExist, IOError):
1934            return toaster_render(request, "unavailable_artifact.html")
1935
1936