prusalink 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,108 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Prusalink
5
+ # The printer's current file transfer (upload or download).
6
+ #
7
+ # @example
8
+ # transfer = Prusalink::Transfer.get # => nil when nothing is transferring
9
+ # transfer.transfer_type # => "FROM_WEB"
10
+ # transfer.progress # => 42.25
11
+ # transfer.stop
12
+ class Transfer < BaseResource
13
+ extend T::Sig
14
+ include AutoInitializable
15
+ include Deserializable
16
+
17
+ resource_path("/api/v1/transfer")
18
+
19
+ # @!attribute [r] id
20
+ # @return [Integer, nil] The transfer's id. Not documented in the API's Transfer schema, so it may be
21
+ # absent; {Status#transfer} always includes it.
22
+ auto_attr :id, type: Integer
23
+
24
+ # @!attribute [r] transfer_type
25
+ # @return [String] The transfer's source/destination: "NO_TRANSFER", "FROM_WEB", "FROM_CONNECT",
26
+ # "FROM_PRINTER", "FROM_SLICER", "FROM_CLIENT", "TO_CONNECT" or "TO_CLIENT".
27
+ # Renamed from the API's `type` field.
28
+ auto_attr :transfer_type, type: String
29
+
30
+ # @!attribute [r] display_name
31
+ # @return [String] Long filename of the transferred file
32
+ auto_attr :display_name, type: String
33
+
34
+ # @!attribute [r] path
35
+ # @return [String] Destination path, e.g. "/local"
36
+ auto_attr :path, type: String
37
+
38
+ # @!attribute [r] url
39
+ # @return [String, nil] Source URL, for downloads
40
+ auto_attr :url, type: String
41
+
42
+ # @!attribute [r] size
43
+ # @return [Integer, nil] Total size in bytes
44
+ auto_attr :size, type: Integer
45
+
46
+ # @!attribute [r] progress
47
+ # @return [Float] Completion, in percent
48
+ auto_attr :progress, type: Float
49
+
50
+ # @!attribute [r] transferred
51
+ # @return [Integer] Transferred data, in bytes
52
+ auto_attr :transferred, type: Integer
53
+
54
+ # @!attribute [r] time_remaining
55
+ # @return [Integer, nil] Estimated time left, in seconds
56
+ auto_attr :time_remaining, type: Integer
57
+
58
+ # @!attribute [r] time_transferring
59
+ # @return [Integer] Time spent transferring, in seconds
60
+ auto_attr :time_transferring, type: Integer
61
+
62
+ # @!attribute [r] to_print
63
+ # @return [Boolean] Whether the file will be printed once the transfer finishes
64
+ auto_attr :to_print, type: T::Boolean
65
+
66
+ # @!attribute [r] extra
67
+ # @return [Hash] Any additional fields returned by the API
68
+ auto_attr :extra, type: Hash
69
+
70
+ auto_initialize!
71
+
72
+ deserialize_config do
73
+ rename type: :transfer_type
74
+ end
75
+
76
+ # Retrieve the current transfer.
77
+ #
78
+ # @return [Transfer, nil] nil when nothing is transferring (the printer answers 204 No Content)
79
+ # @raise [Prusalink::Exceptions::Error] if the request fails
80
+ sig { returns(T.nilable(Transfer)) }
81
+ def self.get
82
+ fetch_resource
83
+ end
84
+
85
+ # Stop the transfer with the given id.
86
+ #
87
+ # @param id [Integer] The transfer's id
88
+ # @return [Boolean] true on success
89
+ # @raise [Prusalink::Exceptions::NotFoundError] if no transfer has this id
90
+ # @raise [Prusalink::Exceptions::ConflictError] if the transfer can't be stopped
91
+ sig { params(id: Integer).returns(T::Boolean) }
92
+ def self.stop(id)
93
+ delete(path: "/api/v1/transfer/#{id}")
94
+ end
95
+
96
+ # Stop this transfer. See {Transfer.stop}.
97
+ #
98
+ # @return [Boolean] true on success
99
+ # @raise [ArgumentError] if the printer didn't report the transfer's id
100
+ sig { returns(T::Boolean) }
101
+ def stop
102
+ transfer_id = id
103
+ raise ArgumentError, "This transfer has no id. Use Transfer.stop with Status#transfer's id" if transfer_id.nil?
104
+
105
+ self.class.stop(transfer_id)
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,27 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ module Prusalink
5
+ # Common type aliases for use with auto_attr and Sorbet signatures.
6
+ module Types
7
+ # A hash with symbol keys and untyped values (common for API responses)
8
+ Hash = T::Hash[Symbol, T.untyped]
9
+
10
+ # A boolean type (true or false)
11
+ Boolean = T.any(TrueClass, FalseClass)
12
+
13
+ # A nullable boolean (true, false or nil)
14
+ NilableBoolean = T.nilable(T.any(TrueClass, FalseClass))
15
+
16
+ # A number that PrusaLink may send as an integer or a float
17
+ Number = T.any(Integer, Float)
18
+
19
+ # Printer states reported by /api/v1/status
20
+ # IDLE, BUSY, PRINTING, PAUSED, FINISHED, STOPPED, ERROR, ATTENTION, READY
21
+ PrinterState = String
22
+
23
+ # Job states reported by /api/v1/job
24
+ # PRINTING, PAUSED, FINISHED, STOPPED, ERROR
25
+ JobState = String
26
+ end
27
+ end
@@ -0,0 +1,7 @@
1
+ # typed: strict
2
+ # frozen_string_literal: true
3
+
4
+ module Prusalink
5
+ # This gem's version
6
+ VERSION = "0.1.0"
7
+ end
data/lib/prusalink.rb ADDED
@@ -0,0 +1,77 @@
1
+ # typed: true
2
+ # frozen_string_literal: true
3
+
4
+ require "active_support/all"
5
+ require "zeitwerk"
6
+ require "sorbet-runtime"
7
+
8
+ loader = Zeitwerk::Loader.for_gem
9
+ loader.ignore("#{__dir__}/tapioca")
10
+ loader.setup
11
+
12
+ # Load Tapioca compiler if available
13
+ require_relative "tapioca/dsl/compilers/auto_initializable" if defined?(Tapioca)
14
+
15
+ # Ruby client for PrusaLink, the local REST API of Prusa 3D printers.
16
+ #
17
+ # @example Configure the gem once, then call resources
18
+ # Prusalink.configure(host: "http://prusa-mk4.local", password: "from-the-printer-screen")
19
+ # Prusalink::Status.get.printer.state # => "PRINTING"
20
+ #
21
+ # @attr [Prusalink::Client] client The configured API client
22
+ module Prusalink
23
+ class << self
24
+ extend T::Sig
25
+
26
+ # Configure the API client with the printer's address and credentials.
27
+ #
28
+ # PrusaLink uses HTTP Digest authentication. The username is "maker" by default and the
29
+ # password is shown on the printer (Settings > Network > PrusaLink). Older firmware uses an
30
+ # API key instead: pass `api_key:` and leave the password out.
31
+ #
32
+ # @example Digest authentication (current firmware)
33
+ # Prusalink.configure(host: "http://192.168.1.42", password: "s3cr3t")
34
+ #
35
+ # @example API key (older firmware)
36
+ # Prusalink.configure(host: "http://192.168.1.42", api_key: "abcdef")
37
+ #
38
+ # @param [String] host Printer's address
39
+ # @param [String, nil] password PrusaLink password (Digest auth)
40
+ # @param [String] username PrusaLink username (Digest auth)
41
+ # @param [String, nil] api_key PrusaLink API key (older firmware)
42
+ # @param [Numeric, nil] open_timeout Seconds to wait for the connection to open
43
+ # @param [Numeric, nil] timeout Seconds to wait for a read or a write
44
+ # @return [nil]
45
+ sig do
46
+ params(
47
+ host: String,
48
+ password: T.nilable(String),
49
+ username: String,
50
+ api_key: T.nilable(String),
51
+ open_timeout: T.nilable(Numeric),
52
+ timeout: T.nilable(Numeric)
53
+ ).void
54
+ end
55
+ def configure(host:, password: nil, username: Client::DEFAULT_USERNAME, api_key: nil,
56
+ open_timeout: Client::DEFAULT_OPEN_TIMEOUT, timeout: Client::DEFAULT_TIMEOUT)
57
+ self.client = Client.new(host: host, password: password, username: username, api_key: api_key,
58
+ open_timeout: open_timeout, timeout: timeout)
59
+
60
+ nil
61
+ end
62
+
63
+ # The API client used for the requests in the current thread
64
+ # @return [Client, nil]
65
+ sig { returns(T.nilable(Client)) }
66
+ def client
67
+ Thread.current[:prusalink_client]
68
+ end
69
+
70
+ # Sets the API client used for the requests in the current thread
71
+ # @param [Client, nil] client
72
+ sig { params(client: T.nilable(Client)).void }
73
+ def client=(client)
74
+ Thread.current[:prusalink_client] = client
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,88 @@
1
+ # typed: false
2
+ # frozen_string_literal: true
3
+
4
+ module Tapioca
5
+ module Dsl
6
+ module Compilers
7
+ # Tapioca compiler for classes using AutoInitializable module.
8
+ #
9
+ # This compiler generates RBI files with proper Sorbet type signatures
10
+ # for classes that include the AutoInitializable module, replacing the
11
+ # need for dynamic code generation at runtime.
12
+ class AutoInitializable < Tapioca::Dsl::Compiler
13
+ ConstantType = type_member { { upper: Module } }
14
+
15
+ # Gather all classes that include AutoInitializable module.
16
+ sig { override.returns(T::Enumerable[Module]) }
17
+ def self.gather_constants
18
+ # Ensure our classes are loaded (fallback if require.rb doesn't work)
19
+ unless defined?(::Prusalink::AutoInitializable)
20
+ gem_root = File.expand_path("../../../..", __dir__)
21
+ require File.join(gem_root, "lib", "prusalink")
22
+
23
+ Zeitwerk::Registry.loaders.each(&:eager_load) if defined?(Zeitwerk::Registry)
24
+ end
25
+
26
+ all_modules.select do |constant|
27
+ constant.is_a?(Class) &&
28
+ constant.respond_to?(:auto_attrs) &&
29
+ constant.included_modules.any? { |mod| mod.name == "Prusalink::AutoInitializable" }
30
+ end
31
+ end
32
+
33
+ # Generates RBI content for the AutoInitializable class.
34
+ sig { override.void }
35
+ def decorate
36
+ auto_attrs = constant.auto_attrs
37
+ return if auto_attrs.empty?
38
+
39
+ root.create_path(constant) do |klass|
40
+ auto_attrs.each do |name, config|
41
+ sorbet_type = generate_sorbet_type(config)
42
+
43
+ klass.create_method(
44
+ name.to_s,
45
+ return_type: sorbet_type,
46
+ class_method: false
47
+ )
48
+ end
49
+ end
50
+ end
51
+
52
+ private
53
+
54
+ # Generates Sorbet type string for a configuration.
55
+ def generate_sorbet_type(config)
56
+ type = config[:type]
57
+
58
+ # Handle the case where type is already a Sorbet type
59
+ return type.to_s if type.respond_to?(:to_s) && (type.to_s.start_with?("T.") || type.to_s.start_with?("T::"))
60
+
61
+ # Convert type to string representation
62
+ base_type = case type
63
+ when Class
64
+ # For regular classes, use the class name
65
+ qualified_name_of(type)
66
+ when String
67
+ # For primitive types passed as strings
68
+ type
69
+ when Symbol
70
+ # For primitive types passed as symbols
71
+ type.to_s.capitalize
72
+ else
73
+ # Handle T.untyped case and other untyped cases
74
+ "T.untyped"
75
+ end
76
+
77
+ # Handle array types
78
+ base_type = "T::Array[#{base_type}]" if config[:array]
79
+
80
+ # Handle nilable types
81
+ base_type = "T.nilable(#{base_type})" if config[:nilable]
82
+
83
+ base_type
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
metadata ADDED
@@ -0,0 +1,158 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: prusalink
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Sophie Déziel
8
+ bindir: exe
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: activesupport
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - ">="
17
+ - !ruby/object:Gem::Version
18
+ version: '0'
19
+ type: :runtime
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - ">="
24
+ - !ruby/object:Gem::Version
25
+ version: '0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: faraday
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '2.2'
33
+ - - "<"
34
+ - !ruby/object:Gem::Version
35
+ version: '3'
36
+ type: :runtime
37
+ prerelease: false
38
+ version_requirements: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - ">="
41
+ - !ruby/object:Gem::Version
42
+ version: '2.2'
43
+ - - "<"
44
+ - !ruby/object:Gem::Version
45
+ version: '3'
46
+ - !ruby/object:Gem::Dependency
47
+ name: net-http-digest_auth
48
+ requirement: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - "~>"
51
+ - !ruby/object:Gem::Version
52
+ version: '1.4'
53
+ type: :runtime
54
+ prerelease: false
55
+ version_requirements: !ruby/object:Gem::Requirement
56
+ requirements:
57
+ - - "~>"
58
+ - !ruby/object:Gem::Version
59
+ version: '1.4'
60
+ - !ruby/object:Gem::Dependency
61
+ name: sorbet-runtime
62
+ requirement: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: 0.5.10908
67
+ - - "<"
68
+ - !ruby/object:Gem::Version
69
+ version: 0.7.0
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: 0.5.10908
77
+ - - "<"
78
+ - !ruby/object:Gem::Version
79
+ version: 0.7.0
80
+ - !ruby/object:Gem::Dependency
81
+ name: zeitwerk
82
+ requirement: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - "~>"
85
+ - !ruby/object:Gem::Version
86
+ version: '2.6'
87
+ type: :runtime
88
+ prerelease: false
89
+ version_requirements: !ruby/object:Gem::Requirement
90
+ requirements:
91
+ - - "~>"
92
+ - !ruby/object:Gem::Version
93
+ version: '2.6'
94
+ description: A type-safe Ruby wrapper for PrusaLink, the local web API of Prusa 3D
95
+ printers (MK4, MK3.9, MK3.5, XL, MINI, Core One and MK3 with a Raspberry Pi). Supports
96
+ printer status, jobs, storage, transfers and file uploads with HTTP Digest authentication.
97
+ email:
98
+ - courrier@sophiedeziel.com
99
+ executables: []
100
+ extensions: []
101
+ extra_rdoc_files: []
102
+ files:
103
+ - CHANGELOG.md
104
+ - LICENSE.txt
105
+ - README.md
106
+ - lib/prusalink.rb
107
+ - lib/prusalink/auto_initializable.rb
108
+ - lib/prusalink/base_resource.rb
109
+ - lib/prusalink/client.rb
110
+ - lib/prusalink/deserializable.rb
111
+ - lib/prusalink/digest_auth.rb
112
+ - lib/prusalink/exceptions.rb
113
+ - lib/prusalink/files.rb
114
+ - lib/prusalink/files/file.rb
115
+ - lib/prusalink/files/folder.rb
116
+ - lib/prusalink/files/refs.rb
117
+ - lib/prusalink/info.rb
118
+ - lib/prusalink/job.rb
119
+ - lib/prusalink/job/file.rb
120
+ - lib/prusalink/server_version.rb
121
+ - lib/prusalink/status.rb
122
+ - lib/prusalink/status/camera.rb
123
+ - lib/prusalink/status/job.rb
124
+ - lib/prusalink/status/printer.rb
125
+ - lib/prusalink/status/storage.rb
126
+ - lib/prusalink/status/transfer.rb
127
+ - lib/prusalink/storage.rb
128
+ - lib/prusalink/transfer.rb
129
+ - lib/prusalink/types.rb
130
+ - lib/prusalink/version.rb
131
+ - lib/tapioca/dsl/compilers/auto_initializable.rb
132
+ homepage: https://github.com/sophiedeziel/prusalink
133
+ licenses:
134
+ - MIT
135
+ metadata:
136
+ allowed_push_host: https://rubygems.org
137
+ homepage_uri: https://github.com/sophiedeziel/prusalink
138
+ source_code_uri: https://github.com/sophiedeziel/prusalink
139
+ changelog_uri: https://github.com/sophiedeziel/prusalink/blob/main/CHANGELOG.md
140
+ rubygems_mfa_required: 'true'
141
+ rdoc_options: []
142
+ require_paths:
143
+ - lib
144
+ required_ruby_version: !ruby/object:Gem::Requirement
145
+ requirements:
146
+ - - ">="
147
+ - !ruby/object:Gem::Version
148
+ version: '3.3'
149
+ required_rubygems_version: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
154
+ requirements: []
155
+ rubygems_version: 4.0.20
156
+ specification_version: 4
157
+ summary: A type-safe Ruby wrapper for the PrusaLink REST API with full Sorbet integration.
158
+ test_files: []