# Copyright (C) 2017 Apple Inc. All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR # ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. from webkitpy.common.version_name_map import VersionNameMap, INTERNAL_TABLE from webkitpy.port.config import apple_additions # This class is designed to match device types. Because it is used for matching, 'None' is treated as a wild-card. class DeviceType(object): FIRST_GENERATION = ' (1st generation)' @classmethod def from_string(cls, device_string, version=None): """ Converts a string into a DeviceType object. These strings should be of the form ' ', where is mandatory and is optional. Example input + output: ('iPhone 6 Plus') -> DeviceType(hardware_family='iPhone', hardware_type='6 Plus', software_version=None) ('iPhone', Version(11)) -> DeviceType(hardware_family='iPhone', hardware_type=None, software_version=Version(11)) ('Apple TV 4K') -> DeviceType(hardware_family='TV', hardware_type='4K', software_version=None) :param device_string: String representing a device. :type device_string: str :param version: Version object of software run on the device. :type version: Version :returns: DeviceType object :rtype: DeviceType """ split_str = device_string.split(' ') if len(split_str) == 1: return cls(hardware_family=device_string, software_version=version) family_index = 0 if split_str[family_index].lower() == 'apple': family_index = 1 return cls( hardware_family=split_str[family_index], hardware_type=' '.join(split_str[family_index + 1:]) if len(split_str) > family_index + 1 else None, software_version=version) def _define_software_variant_from_hardware_family(self): if self.hardware_family is None: return if self.software_variant: return self.software_variant = None if self.hardware_family.lower().split(' ')[-1].startswith('watch'): self.hardware_family = 'Apple Watch' self.software_variant = 'watchOS' elif self.hardware_family.lower().split(' ')[-1].startswith('tv'): self.hardware_family = 'Apple TV' self.software_variant = 'tvOS' elif self.hardware_family.lower().startswith('ipad') or self.hardware_family.lower().startswith('iphone'): self.software_variant = 'iOS' def check_consistency(self): if self.hardware_family is not None: if self.hardware_family == 'Apple Watch': assert self.software_variant == 'watchOS' elif self.hardware_family == 'Apple TV': assert self.software_variant == 'tvOS' elif self.hardware_family in ('iPhone', 'iPad'): assert self.software_variant == 'iOS' if self.hardware_type is not None: assert self.hardware_family is not None def __init__(self, hardware_family=None, hardware_type=None, software_version=None, software_variant=None): """ :param hardware_family: iPhone, iPad, Apple Watch and Apple TV are all examples. :type hardware_family: str :param hardware_type: 6s, Series 2 - 42mm, 4k are all examples :type hardware_type: str :param software_version: Version object representing software the device is running. :type software_version: Version :param software_variant: Groups together hardware families which share an OS, like iPad and iPhone. iOS, tvOS and watchOS are examples. :type software_variant: str """ if hardware_family is None and hardware_type is None and software_version is None and software_variant is None: raise ValueError('Cannot instantiate DeviceType with no arguments') self.hardware_family = hardware_family self.hardware_type = hardware_type self.software_version = software_version self.software_variant = software_variant self._define_software_variant_from_hardware_family() self.check_consistency() @property def standardized_hardware_type(self): if not self.hardware_type: return None if self.hardware_type.lower().endswith(self.FIRST_GENERATION): return self.hardware_type[:-len(self.FIRST_GENERATION)] return self.hardware_type def __str__(self): version = None if self.software_version and apple_additions(): version = VersionNameMap.map().to_name(self.software_version, platform=self.software_variant.lower(), table=INTERNAL_TABLE) elif self.software_version: version = VersionNameMap.map().to_name(self.software_version, platform=self.software_variant.lower()) return u'{hardware_family}{hardware_type} running {version}'.format( hardware_family=self.hardware_family if self.hardware_family else 'Device', hardware_type=u' {}'.format(self.hardware_type) if self.hardware_type else '', version=version or self.software_variant, ) # This technique of matching treats 'None' a wild-card. def __eq__(self, other): assert isinstance(other, DeviceType) if self.hardware_family is not None and other.hardware_family is not None and self.hardware_family.lower() != other.hardware_family.lower(): return False if self.standardized_hardware_type is not None and other.standardized_hardware_type is not None and self.standardized_hardware_type.lower() != other.standardized_hardware_type.lower(): return False if self.software_variant is not None and other.software_variant is not None and self.software_variant.lower() != other.software_variant.lower(): return False if self.software_version is not None and other.software_version is not None and self.software_version != other.software_version: return False return True def __contains__(self, other): assert isinstance(other, DeviceType) if self.hardware_family is not None and other.hardware_family is not None and self.hardware_family.lower() != other.hardware_family.lower(): return False if self.standardized_hardware_type is not None and other.standardized_hardware_type is not None and self.standardized_hardware_type.lower() != other.standardized_hardware_type.lower(): return False if self.software_variant is not None and other.software_variant is not None and self.software_variant.lower() != other.software_variant.lower(): return False if self.software_version is not None and other.software_version is not None and not other.software_version in self.software_version: return False return True def __hash__(self): return hash((self.hardware_family, self.standardized_hardware_type, self.software_variant, self.software_version))