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 26, 2023
1 parent 91249cf commit 8ddc165
Show file tree
Hide file tree
Showing 14 changed files with 552 additions and 37 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
7 changes: 7 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,13 @@ def format_type(command)
super
end

# @!visibility private
def config_eql?(other)
return false unless 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
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: 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 @@ -192,6 +192,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
73 changes: 60 additions & 13 deletions lib/openhab/dsl/items/builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -118,23 +118,72 @@ 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)
else
item = builder.build
item.metadata.merge!(builder.metadata)
provider.add(item)
end
# item.metadata.merge!(builder.metadata)

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
[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)|
current_link = provider.all.find { |link| link.item_name == item.name && link.linked_uid == channel }
if current_link
next if current_link.configuration == config

provider.remove(current_link.uid)
end

Core::Things::Links::Provider.link(item, channel, config)
end

# remove links not in the new item
removed_links = provider.all.select do |link|
next unless link.item_name == item.name

channels.none? { |(channel, _config)| channel == link.linked_uid }
end
removed_links.each do |link|
provider.remove(link.uid)
end

item
end
end
Expand All @@ -143,8 +192,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 @@ -537,12 +586,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
32 changes: 25 additions & 7 deletions lib/openhab/dsl/things/builder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,9 @@ class Builder
# @return [org.openhab.core.thing.ManagedThingProvider]
attr_reader :provider

def initialize(provider)
def initialize(provider, update: false)
@provider = Core::Things::Provider.current(provider)
@update = update
end

# Create a new Bridge
Expand All @@ -61,10 +62,28 @@ def thing(*args, **kwargs, &block)
def build(klass, *args, **kwargs, &block)
builder = klass.new(*args, **kwargs)
builder.instance_eval(&block) if block
thing = provider.add(builder.build)
thing = Core::Things::Proxy.new(thing)
thing = builder.build

if DSL.things.key?(thing.uid)
raise ArgumentError, "Thing #{thing.uid} already exists" unless @update

unless (old_thing = provider.get(thing.uid))
raise FrozenError,
"Thing #{thing.uid} is managed by #{Core::Things::Provider.registry.provider_for(thing.uid)}"
end

if thing.config_eql?(old_thing)
logger.debug { "Not replacing existing thing #{thing.uid}" }
thing = old_thing
else
provider.update(thing)
end
else
provider.add(thing)
end
thing.enable(enabled: builder.enabled) unless builder.enabled.nil?
thing

Core::Things::Proxy.new(thing)
end
end

Expand Down Expand Up @@ -196,6 +215,7 @@ def build
builder = org.openhab.core.thing.binding.builder.ThingBuilder
.create(thing_type_uid, uid)
.with_label(label)
.with_location(location)
.with_configuration(configuration)
.with_bridge(bridge_uid)
.with_channels(channels)
Expand All @@ -210,9 +230,7 @@ def build
builder.with_properties(thing_type.properties)
end

thing = builder.build
Core::Things.manager.set_enabled(uid, enabled) unless enabled.nil?
thing
builder.build
end

private
Expand Down
Loading

0 comments on commit 8ddc165

Please sign in to comment.