Skip to content
This repository has been archived by the owner on Mar 15, 2018. It is now read-only.

Commit

Permalink
Added compat_mode param to search API for d2c (bug 706385)
Browse files Browse the repository at this point in the history
  • Loading branch information
robhudson committed May 10, 2012
1 parent dd8e779 commit 61ab1d5
Show file tree
Hide file tree
Showing 11 changed files with 334 additions and 43 deletions.
107 changes: 106 additions & 1 deletion apps/addons/models.py
Expand Up @@ -10,6 +10,7 @@
from datetime import datetime, timedelta

from django.conf import settings
from django.core.cache import cache
from django.db import models, transaction
from django.dispatch import receiver
from django.db.models import Q, Max, signals as dbsignals
Expand All @@ -27,7 +28,7 @@
from amo.decorators import use_master
from amo.fields import DecimalCharField
from amo.helpers import absolutify, shared_url
from amo.utils import (chunked, JSONEncoder, send_mail, slugify,
from amo.utils import (cache_ns_key, chunked, JSONEncoder, send_mail, slugify,
sorted_groupby, to_language, urlparams)
from amo.urlresolvers import get_outgoing_url, reverse
from compat.models import CompatReport
Expand Down Expand Up @@ -592,6 +593,110 @@ def latest_version(self):

return self._latest_version

def compatible_version(self, app_id, app_version=None,
compat_mode='strict'):
"""Returns the newest compatible version given the input."""
if not app_id:
return None

log.info(u'Checking compatibility for add-on ID:%s, APP:%s, V:%s,'
' Mode:%s' % (self.id, app_id, app_version, compat_mode))
valid_file_statuses = ','.join(map(str, amo.REVIEWED_STATUSES))
data = dict(id=self.id, app_id=app_id,
valid_file_statuses=valid_file_statuses)
if app_version:
data.update(version_int=version_int(app_version))
else:
# We can't perform the search queries for strict or normal without
# an app version.
compat_mode = 'ignore'

ns_key = cache_ns_key('d2c-versions:%s' % self.id)
cache_key = '%s:%s:%s:%s' % (ns_key, app_id, app_version, compat_mode)
version_id = cache.get(cache_key)
if version_id != None:
log.info(u'Found compatible version in cache: %s => %s' % (
cache_key, version_id))
if version_id == 0:
return None
else:
try:
return Version.objects.get(pk=version_id)
except Version.DoesNotExist:
pass

raw_sql = ["""
SELECT versions.*
FROM versions
INNER JOIN addons
ON addons.id = versions.addon_id AND addons.id = %(id)s
INNER JOIN applications_versions
ON applications_versions.version_id = versions.id
INNER JOIN applications
ON applications_versions.application_id = applications.id
AND applications.id = %(app_id)s
INNER JOIN appversions appmin
ON appmin.id = applications_versions.min
INNER JOIN appversions appmax
ON appmax.id = applications_versions.max
INNER JOIN files
ON files.version_id = versions.id
WHERE
files.status IN (%(valid_file_statuses)s)
"""]

if app_version:
raw_sql.append('AND appmin.version_int <= %(version_int)s ')

if compat_mode == 'ignore':
pass # No further SQL modification required.

elif compat_mode == 'normal':
raw_sql.append("""AND
CASE WHEN files.strict_compatibility = 1 OR
files.binary_components = 1
THEN appmax.version_int >= %(version_int)s ELSE 1 END
""")
# Filter out versions found in compat overrides
raw_sql.append("""AND
NOT versions.id IN (
SELECT version_id FROM incompatible_versions
WHERE app_id=%(app_id)s AND
(min_app_version='0' AND
max_app_version_int >= %(version_int)s) OR
(min_app_version_int <= %(version_int)s AND
max_app_version='*') OR
(min_app_version_int <= %(version_int)s AND
max_app_version_int >= %(version_int)s)) """)

else: # Not defined or 'strict'.
raw_sql.append('AND appmax.version_int >= %(version_int)s ')

raw_sql.append('ORDER BY versions.id DESC LIMIT 1;')

version = Version.objects.raw(''.join(raw_sql) % data)
if version:
version = version[0]
version_id = version.id
else:
version = None
version_id = 0

log.info(u'Caching compat version %s => %s' % (cache_key, version_id))
cache.set(cache_key, version_id)

return version

def invalidate_d2c_versions(self):
"""Invalidates the cache of compatible versions.
Call this when there is an event that may change what compatible
versions are returned so they are recalculated.
"""
key = cache_ns_key('d2c-versions:%s' % self.id, increment=True)
log.info('Incrementing d2c-versions namespace for add-on [%s]: %s' % (
self.id, key))

@property
def current_version(self):
"Returns the current_version field or updates it if needed."
Expand Down
11 changes: 10 additions & 1 deletion apps/addons/tasks.py
Expand Up @@ -11,14 +11,15 @@

import amo
from amo.decorators import set_modified_on, write
from amo.utils import attach_trans_dict, sorted_groupby
from amo.utils import attach_trans_dict, cache_ns_key, sorted_groupby
from market.models import AddonPremium
from tags.models import Tag
from versions.models import Version
from . import cron, search # Pull in tasks from cron.
from .models import (Addon, Category, CompatOverride, DeviceType,
IncompatibleVersions, Preview)


log = logging.getLogger('z.task')


Expand Down Expand Up @@ -206,6 +207,8 @@ def update_incompatible_appversions(data, **kw):
"""Updates the incompatible_versions table for this version."""
log.info('Updating incompatible_versions for %s versions.' % len(data))

addon_ids = set()

for version_id in data:
# This is here to handle both post_save and post_delete hooks.
IncompatibleVersions.objects.filter(version=version_id).delete()
Expand All @@ -217,6 +220,8 @@ def update_incompatible_appversions(data, **kw):
'cleared.' % version_id)
return

addon_ids.add(version.addon_id)

try:
compat = CompatOverride.objects.get(addon=version.addon)
except CompatOverride.DoesNotExist:
Expand Down Expand Up @@ -247,3 +252,7 @@ def update_incompatible_appversions(data, **kw):
log.info('Added incompatible version for version ID [%d]: '
'app:%d, %s -> %s' % (version_id, range.app.id, range.min,
range.max))

# Increment namespace cache of compat versions.
for addon_id in addon_ids:
cache_ns_key('d2c-versions:%s' % addon_id, increment=True)
15 changes: 5 additions & 10 deletions apps/addons/tests/test_update.py
Expand Up @@ -455,13 +455,12 @@ def check(self, expected):
eq_(self.get(app_version=version, compat_mode=mode),
expected['-'.join([version, mode])])

def test_extension(self):
def test_baseline(self):
# Tests simple add-on (non-binary-components, non-strict).
self.check(self.expected)

def test_binary_extension(self):
# Tests add-on with binary flag.

def test_binary_components(self):
# Tests add-on with binary_components flag.
self.update_files(binary_components=True)
self.expected.update({
'8.0-normal': None,
Expand All @@ -471,7 +470,6 @@ def test_binary_extension(self):
def test_extension_compat_override(self):
# Tests simple add-on (non-binary-components, non-strict) with a compat
# override.

self.create_override(min_version='1.3', max_version='1.4')
self.expected.update({
'6.0-normal': self.ver_1_2,
Expand All @@ -480,10 +478,9 @@ def test_extension_compat_override(self):
})
self.check(self.expected)

def test_binary_extension_compat_override(self):
def test_binary_component_compat_override(self):
# Tests simple add-on (non-binary-components, non-strict) with a compat
# override.

self.update_files(binary_components=True)
self.create_override(min_version='1.3', max_version='1.4')
self.expected.update({
Expand All @@ -493,9 +490,8 @@ def test_binary_extension_compat_override(self):
})
self.check(self.expected)

def test_strict_extension(self):
def test_strict_opt_in(self):
# Tests add-on with opt-in strict compatibility

self.update_files(strict_compatibility=True)
self.expected.update({
'8.0-normal': None,
Expand All @@ -505,7 +501,6 @@ def test_strict_extension(self):
def test_compat_override_max_addon_wildcard(self):
# Tests simple add-on (non-binary-components, non-strict) with a compat
# override that contains a max wildcard.

self.create_override(min_version='1.2', max_version='1.*',
min_app_version='5.0', max_app_version='6.*')
self.expected.update({
Expand Down
31 changes: 18 additions & 13 deletions apps/api/templates/api/includes/addon.xml
@@ -1,11 +1,16 @@
{% cache addon, extra=[api_version] %}
{% cache addon, extra=[api_version, version, compat_mode] %}
{% set new_api = (api_version >= 1.5) %}
{% if is_d2c %}
{% set version = addon.compat_version %}
{% else %}
{% set version = addon.current_version %}
{% endif %}
<addon id="{{ addon.id }}">
<name>{{ addon.name }}</name>
<type id="{{ addon.type }}">{{ amo.ADDON_TYPE[addon.type] }}</type>
<guid>{{ addon.guid }}</guid>
<slug>{{ addon.slug }}</slug>
<version>{{ addon.current_version.version }}</version>
<version>{{ version.version }}</version>
<status id="{{ addon.status }}">{{ amo.STATUS_CHOICES[addon.status] }}</status>
<authors>
{% if not addon.is_persona() %}
Expand All @@ -23,22 +28,22 @@
</authors>
<summary>{{ addon.summary|strip_html(new_api) }}</summary>
<description>{{ addon.description|strip_html(new_api) }}</description>
{%- if addon.current_version and addon.current_version.license -%}
{%- if version and version.license -%}
<license>
<name>{{ addon.current_version.license.name }}</name>
<name>{{ version.license.name }}</name>
<url>
{%- if not addon.current_version.license.url -%}
{{ addon.current_version.license_url()|absolutify }}
{%- if not version.license.url -%}
{{ version.license_url()|absolutify }}
{%- else -%}
{{ addon.current_version.license.url|absolutify }}
{{ version.license.url|absolutify }}
{%- endif -%}
</url>
</license>
{%- endif -%}
<icon>{{ addon.get_icon_url(32, use_default=False) }}</icon>
<compatible_applications>
{%- if addon.current_version -%}
{%- for app in addon.current_version.compatible_apps.values() %}
{%- if version -%}
{%- for app in version.compatible_apps.values() %}
{%- if amo.APP_IDS.get(app.application_id) -%}
<application>
<name>{{ amo.APP_IDS[app.application_id].pretty }}</name>
Expand All @@ -52,8 +57,8 @@
{%- endif -%}
</compatible_applications>
<all_compatible_os>
{%- if addon.current_version -%}
{% for os in addon.current_version.supported_platforms -%}
{%- if version -%}
{% for os in version.supported_platforms -%}
<os>{{ os.api_name }}</os>
{%- endfor -%}
{%- endif -%}
Expand All @@ -80,8 +85,8 @@
{%- endif -%}
<rating>{{ addon.average_rating|round|int }}</rating>
<learnmore>{{ addon.get_url_path()|absolutify|urlparams(src='api') }}</learnmore>
{%- if addon.current_version -%}
{%- for file in addon.current_version.all_files -%}
{%- if version -%}
{%- for file in version.all_files -%}
<install hash="{{ file.hash }}"
os="{{ amo.PLATFORMS[file.platform_id].api_name }}"
size="{{ file.size * 1024 }}">{{ file.get_url_path('api', addon) }}</install>
Expand Down

0 comments on commit 61ab1d5

Please sign in to comment.