Skip to content

Commit

Permalink
Support updating existing things/items/sitemaps
Browse files Browse the repository at this point in the history
Signed-off-by: Jimmy Tanagra <[email protected]>
  • Loading branch information
jimtng committed Sep 30, 2023
1 parent 2d166f3 commit b625512
Show file tree
Hide file tree
Showing 16 changed files with 572 additions and 43 deletions.
7 changes: 6 additions & 1 deletion lib/openhab/core/emulate_hash.rb
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,12 @@ def merge(*others, &block)
def merge!(*others, &block)
return self if others.empty?

replace(merge(*others, &block))
# don't call replace here so we don't touch other keys
others.shift.merge(*others, &block).each do |key, value|
value = yield key, self[key], value if key?(key) && block
store(key, value)
end
self
end
alias_method :update, :merge!

Expand Down
16 changes: 16 additions & 0 deletions lib/openhab/core/items/group_item.rb
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,9 @@ def inspect
alias_method :to_s, :inspect
end

# @!attribute [r] function
# @return [GroupFunction] Returns the function of this GroupItem

# Override because we want to send them to the base item if possible
%i[command update].each do |method|
define_method(method) do |command|
Expand Down Expand Up @@ -152,6 +155,19 @@ def #{type}_item? # def date_time_item?
RUBY
end

#
# Compares all attributes of the item with another item.
#
# @param other [Item] The item to compare with
# @return [true,false] true if all attributes are equal, false otherwise
#
# @!visibility private
def config_eql?(other)
return false unless super

base_item&.type == other.base_item&.type && function&.inspect == other.function&.inspect
end

private

# Add base type and function details
Expand Down
28 changes: 25 additions & 3 deletions lib/openhab/core/items/item.rb
Original file line number Diff line number Diff line change
Expand Up @@ -264,12 +264,17 @@ def thing
#
# @return [Array<Thing>] An array of things or an empty array
def things
registry = Things::Links::Provider.registry
channels = registry.get_bound_channels(name).to_a
channels.map(&:thing_uid).uniq.map { |tuid| EntityLookup.lookup_thing(tuid) }.compact
Things::Links::Provider.registry.get_bound_things(name).map { |thing| Things::Proxy.new(thing) }
end
alias_method :all_linked_things, :things

# Returns all of the item's links (channels and link configurations).
#
# @return [Array<ItemChannelLink>] An array of ItemChannelLink or an empty array
def links
Things::Links::Provider.registry.get_links(name)
end

# @return [String]
def inspect
s = "#<OpenHAB::Core::Items::#{type}Item#{type_details} #{name} #{label.inspect} state=#{raw_state.inspect}"
Expand All @@ -286,6 +291,23 @@ def provider
Provider.registry.provider_for(self)
end

#
# Compares all attributes except metadata and channels/links of the item with another item.
#
# @param other [Item] The item to compare with
# @return [true,false] true if all attributes are equal, false otherwise
#
# @!visibility private
def config_eql?(other)
# GenericItem#equals checks whether other has the same name and class
return false unless equals(other)

%i[label category tags group_names].all? do |method|
# Don't use #send here. It is defined in GenericItem for sending commands
public_send(method) == other.public_send(method)
end
end

def_type_predicate(:color)
def_type_predicate(:contact)
def_type_predicate(:date_time)
Expand Down
5 changes: 5 additions & 0 deletions lib/openhab/core/items/number_item.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,11 @@ def format_type(command)
super
end

# @!visibility private
def config_eql?(other)
super && dimension == other.dimension
end

protected

# Adds the unit dimension
Expand Down
6 changes: 4 additions & 2 deletions lib/openhab/core/items/registry.rb
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,15 @@ def to_a
# Enter the Item Builder DSL.
#
# @param (see Core::Provider.current)
# @param update [true, false] Update existing items with the same name.
# @yield Block executed in the context of a {DSL::Items::Builder}
# @return [Object] The return value of the block.
#
# @see DSL::Items::Builder
#
def build(preferred_provider = nil, &block)
DSL::Items::BaseBuilderDSL.new(preferred_provider).instance_eval_with_dummy_items(&block)
def build(preferred_provider = nil, update: false, &block)
DSL::Items::BaseBuilderDSL.new(preferred_provider, update: update)
.instance_eval_with_dummy_items(&block)
end

#
Expand Down
2 changes: 1 addition & 1 deletion lib/openhab/core/registry.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ def provider_for(key)
# So take an extra step to verify that the provider really holds the given instance.
# by using equal? to compare the object's identity.
# Only ManagedProviders have a #get method to look up the object by uid.
if !provider.is_a?(org.openhab.core.common.registry.ManagedProvider) || provider.get(key.uid).equal?(key)
if !provider.is_a?(ManagedProvider) || provider.get(key.uid).equal?(key)
provider
end
elsif (element = identifierToElement[key])
Expand Down
7 changes: 4 additions & 3 deletions lib/openhab/core/sitemaps/provider.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ def unregister
#
# Enter the Sitemap Builder DSL.
#
# @param update [true, false] When true, existing sitemaps with the same name will be updated.
# @yield Block executed in the context of a {DSL::Sitemaps::Builder}
# @return [void]
#
Expand All @@ -82,8 +83,8 @@ def unregister
# end
# end
#
def build(&block)
DSL::Sitemaps::Builder.new(self).instance_eval(&block)
def build(update: false, &block)
DSL::Sitemaps::Builder.new(self, update: update).instance_eval(&block)
end
# rubocop:enable Layout/LineLength

Expand Down Expand Up @@ -123,7 +124,7 @@ def notify_listeners_about_removed_element(element)
@listeners.each { |l| l.model_changed(element.name, org.openhab.core.model.core.EventType::REMOVED) }
end

def notify_listeners_about_updated_element(element)
def notify_listeners_about_updated_element(_old_element, element)
@listeners.each { |l| l.model_changed(element.name, org.openhab.core.model.core.EventType::MODIFIED) }
end
end
Expand Down
6 changes: 2 additions & 4 deletions lib/openhab/core/things/links/provider.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,11 @@ def registry
end

# @!visibility private
def link(item, channel, config = {})
def create_link(item, channel, config = {})
config = Configuration.new(config.transform_keys(&:to_s))
channel = ChannelUID.new(channel) if channel.is_a?(String)
channel = channel.uid if channel.is_a?(Channel)
link = org.openhab.core.thing.link.ItemChannelLink.new(item.name, channel, config)

current.add(link)
org.openhab.core.thing.link.ItemChannelLink.new(item.name, channel, config)
end
end

Expand Down
6 changes: 4 additions & 2 deletions lib/openhab/core/things/registry.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,13 @@ def to_a
#
# Enter the Thing Builder DSL.
# @param (see Core::Provider.current)
# @param update [true, false]
# When true, existing items with the same name will be redefined if they're different.
# @yield Block executed in the context of a {DSL::Things::Builder}.
# @return [Object] The result of the block.
#
def build(preferred_provider = nil, &block)
DSL::Things::Builder.new(preferred_provider).instance_eval(&block)
def build(preferred_provider = nil, update: false, &block)
DSL::Things::Builder.new(preferred_provider, update: update).instance_eval(&block)
end

#
Expand Down
10 changes: 10 additions & 0 deletions lib/openhab/core/things/thing.rb
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,16 @@ def actions(scope = nil)
$actions.get(scope || uid.binding_id, uid.to_s)
end

#
# Compares all attributes of the thing with another thing.
#
# @param other [Thing] The thing to compare with
# @return [true,false] true if all attributes are equal, false otherwise
#
def config_eql?(other)
%i[uid label channels bridge_uid location configuration].all? { |method| send(method) == other.send(method) }
end

#
# Delegate missing methods to the thing's default actions scope.
#
Expand Down
78 changes: 64 additions & 14 deletions lib/openhab/dsl/items/builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -118,23 +118,75 @@ class BaseBuilderDSL
class ProviderWrapper
attr_reader :provider

def initialize(provider)
def initialize(provider, update:)
@provider = provider
@update = update
end

# @!visibility private
def add(builder)
item = builder.build
provider.add(item)
# make sure to add the item to the registry before linking it
builder.channels.each do |(channel, config)|
if DSL.items.key?(builder.name)
raise ArgumentError, "Item #{builder.name} already exists" unless @update

# Use provider.get because openHAB's ManagedProvider does not support the #[] method.
unless (old_item = provider.get(builder.name))
raise FrozenError, "Item #{builder.name} is managed by #{DSL.items[builder.name].provider}"
end

item = builder.build
if item.config_eql?(old_item)
logger.debug { "Not replacing existing item #{item.uid} because it is identical" }
item = old_item
else
logger.debug { "Replacing existing item #{item.uid} because it is not identical" }
provider.update(item)
end
item.metadata.merge!(builder.metadata)
item.metadata
.reject { |namespace, _| builder.metadata.key?(namespace) }
.each do |namespace, metadata|
item.metadata.delete(namespace) if metadata.provider == Core::Items::Metadata::Provider.current
end
else
item = builder.build
item.metadata.merge!(builder.metadata)
provider.add(item)
end

item.update(builder.state) unless builder.state.nil?

# fill in partial channel names from group's thing id
channels = builder.channels.map do |(channel, config)|
if !channel.include?(":") &&
(group = builder.groups.find { |g| g.is_a?(GroupItemBuilder) && g.thing })
thing = group.thing
channel = "#{thing}:#{channel}"
end
Core::Things::Links::Provider.link(item, channel, config)
[channel, config]
end

# make sure to add the item to the registry before linking it
provider = Core::Things::Links::Provider.current
channels.each do |(channel, config)|
new_link = Core::Things::Links::Provider.create_link(item, channel, config)
current_link = provider.get(new_link.uid)
if current_link
next if current_link.configuration == config

provider.remove(current_link.uid)
end

provider.add(new_link)
end

# remove links not in the new item
provider.all.each do |link|
next unless link.item_name == item.name
next if channels.any? { |(channel, _config)| channel == link.linked_uid }

provider.remove(link.uid)
end

item
end
end
Expand All @@ -143,8 +195,8 @@ def add(builder)
# @return [org.openhab.core.items.ItemProvider]
attr_reader :provider

def initialize(provider)
@provider = ProviderWrapper.new(Core::Items::Provider.current(provider))
def initialize(provider, update:)
@provider = ProviderWrapper.new(Core::Items::Provider.current(provider), update: update)
end
end

Expand Down Expand Up @@ -549,12 +601,10 @@ def build
tags.each do |tag|
item.add_tag(tag)
end
item.metadata.merge!(metadata)
item.metadata["autoupdate"] = autoupdate.to_s unless autoupdate.nil?
item.metadata["expire"] = expire if expire
item.metadata["stateDescription"] = { "pattern" => format } if format
item.metadata["unit"] = unit if unit
item.state = item.format_update(state) unless state.nil?
metadata["autoupdate"] = autoupdate.to_s unless autoupdate.nil?
metadata["expire"] = expire if expire
metadata["stateDescription"] = { "pattern" => format } if format
metadata["unit"] = unit if unit
item
end

Expand Down
11 changes: 8 additions & 3 deletions lib/openhab/dsl/sitemaps/builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ module Sitemaps
# Base sitemap builder DSL
class Builder
# @!visibility private
def initialize(provider)
def initialize(provider, update:)
@provider = provider
@update = update
end

# (see SitemapBuilder#initialize)
Expand All @@ -23,8 +24,12 @@ def initialize(provider)
def sitemap(name, label = nil, icon: nil, &block)
sitemap = SitemapBuilder.new(name, label, icon: icon)
sitemap.instance_eval_with_dummy_items(&block) if block
@provider.add(sitemap.build)
sitemap
sitemap = sitemap.build
if @update && @provider.get(sitemap.uid)
@provider.update(sitemap)
else
@provider.add(sitemap)
end
end
end

Expand Down
Loading

0 comments on commit b625512

Please sign in to comment.